> ## Documentation Index
> Fetch the complete documentation index at: https://docs.struct.to/llms.txt
> Use this file to discover all available pages before exploring further.

# Price alerts for your mobile app

> Send a push notification when a market crosses a price, using the price_threshold webhook.

This guide walks through letting a user set a price alert on a market in your mobile app and pushing them a notification the moment the market crosses that price. It is built on the [`price_threshold`](/webhooks/price-threshold) webhook, with [fire-and-delete](/webhooks/fire-and-delete) so each alert fires once and cleans itself up.

## How it fits together

1. A user taps "Notify me at 75%" on a market in your app.
2. Your backend creates a `price_threshold` webhook that points at your server, scoped to that market, with `one_shot: true`.
3. When the market's price crosses the target, Struct delivers the event to your server.
4. Your server looks up the device that registered the alert and sends a push notification.
5. Because the webhook is `one_shot`, it deletes itself after that single delivery. To repeat the alert, create a new one.

You will need a Struct API key, a server to receive the webhook, and a push provider for your app (this guide uses [Expo](https://docs.expo.dev/push-notifications/overview/), but FCM and APNs work the same way).

## 1. Create the alert when the user opts in

When the user sets an alert, create a `price_threshold` webhook from your backend and store the returned `id` against that user and device. Use `min_price` for an "above" alert (fire when the price crosses up to the target) or `max_price` for a "below" alert.

<CodeGroup>
  ```typescript SDK theme={null}
  import { StructClient } from "@structbuild/sdk";

  const struct = new StructClient({ apiKey: process.env.STRUCT_API_KEY! });

  async function createPriceAlert(opts: {
    conditionId: string;
    target: number; // 0.0 - 1.0
    direction: "above" | "below";
    userId: string;
    deviceToken: string;
  }) {
    const { data: webhook } = await struct.webhooks.create({
      url: "https://api.yourapp.com/webhooks/struct",
      event: "price_threshold",
      secret: process.env.STRUCT_WEBHOOK_SECRET!,
      filters: {
        condition_ids: [opts.conditionId],
        ...(opts.direction === "above"
          ? { min_price: opts.target }
          : { max_price: opts.target }),
        one_shot: true,
      },
    });

    // Map the webhook back to the device so the handler knows who to notify.
    await db.priceAlerts.insert({
      webhookId: webhook.id,
      userId: opts.userId,
      deviceToken: opts.deviceToken,
      conditionId: opts.conditionId,
      target: opts.target,
      direction: opts.direction,
    });

    return webhook.id;
  }
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.struct.to/v1/webhooks \
    -H "X-API-Key: YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "url": "https://api.yourapp.com/webhooks/struct",
      "event": "price_threshold",
      "secret": "your_signing_secret",
      "filters": {
        "condition_ids": ["0x4fec624c0ff2bfae89956cebd6fbc9c58f995f824382dc587dc5a32a4b15940b"],
        "min_price": 0.75,
        "one_shot": true
      }
    }'
  ```
</CodeGroup>

<Note>`price_threshold` requires `condition_ids` or `position_ids` when `one_shot` is set, which the per-market scope here already provides.</Note>

## 2. Receive the event and send the push

Struct delivers the `price_threshold` event to your endpoint as an HTTP POST. Verify the signature, look up the device that registered the alert, and forward a push notification.

```typescript Node (Express) theme={null}
import express from "express";
import type { WebhookEvent } from "@structbuild/sdk";

const app = express();

app.post(
  "/webhooks/struct",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    // Verify X-Webhook-Signature before trusting the body.
    // Full check: /webhooks/signature-verification
    if (!verifyStructSignature(req)) return res.status(401).end();

    const delivery: WebhookEvent = JSON.parse(req.body.toString("utf8"));
    if (delivery.event !== "price_threshold") return res.status(200).end();

    const { data } = delivery;
    const alert = await db.priceAlerts.findByWebhookId(delivery.webhook_id);
    if (!alert) return res.status(200).end();

    await sendExpoPush(alert.deviceToken, {
      title: data.direction === "up" ? "Price target hit" : "Price dropped",
      body: `${data.question ?? "A market"} crossed ${Math.round(
        data.threshold * 100,
      )}% (now ${Math.round(data.price * 100)}%)`,
      data: { conditionId: data.condition_id, marketSlug: data.market_slug },
    });

    // one_shot already removed the subscription on Struct's side; drop our row too.
    await db.priceAlerts.deleteByWebhookId(delivery.webhook_id);

    res.status(200).end();
  },
);

async function sendExpoPush(
  token: string,
  payload: { title: string; body: string; data?: Record<string, unknown> },
) {
  await fetch("https://exp.host/--/api/v2/push/send", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ to: token, sound: "default", ...payload }),
  });
}
```

<Warning>Always verify the `X-Webhook-Signature` header before acting on a delivery. The full HMAC check is on the [Signature Verification](/webhooks/signature-verification) page. Respond with a `2xx` within 10 seconds so the delivery is not retried.</Warning>

## 3. Let users cancel an alert

If a user removes a pending alert before it fires, delete the webhook so it never delivers:

```typescript theme={null}
await struct.webhooks.deleteWebhook({ webhookId: alert.webhookId });
await db.priceAlerts.deleteByWebhookId(alert.webhookId);
```

A `one_shot` alert that has already fired is gone on Struct's side, so there is nothing to cancel once the push has been sent.

## Recurring alerts

`one_shot` makes each alert fire once. For an alert that should keep notifying every time a market crosses a level (a "watchlist" toggle rather than a one-time target), omit `one_shot`: the subscription stays active and re-arms after each crossing. Delete it when the user turns the watch off.

## Reading the payload

The fields you will use most in the notification:

* `direction`: `"up"` (crossed the `min_price` target) or `"down"` (crossed the `max_price` target).
* `threshold`: the target from your filter that was crossed.
* `price` / `probability`: the value that triggered it, on a `0.0`–`1.0` scale.
* `question` / `market_slug` / `event_slug`: for the notification copy and for deep-linking back into your app.

See the full field list on the [`price_threshold` webhook page](/webhooks/price-threshold).
