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

# Alerts

> Stream on-screen alerts for purchases and tips by pushing webhook events into an OBS browser source

A streaming alert overlay turns shop activity into on-stream engagement. A card animates in when an order or tip lands, the chat sees it, the creator reacts to it live. This guide walks through the architecture for building one against Fourthwall webhooks — modeled on Fourthwall's first-party Alerts app.

The overlay itself is a plain webpage your creators drop into OBS, Streamlabs, or any streaming tool that supports HTML browser sources. Webhooks land on your server, and you need to push them out to the OBS browser page in real time.

This guide will also show you how to set up an [embedded settings](/apps/embedded-settings) page for your app. This way, the creator can manage their overlay settings without leaving Fourthwall.

<Note>
  A complete, runnable implementation of this guide — install, embedded settings, webhook receiver, SSE transport, and overlay — lives in the [streaming-alerts example](https://github.com/FourthwallHQ/fourthwall-examples/tree/main/examples/streaming-alerts) on GitHub.
</Note>

## Architecture

```mermaid theme={null}
sequenceDiagram
    participant Shop
    participant Fourthwall
    participant Server as Your server
    participant Overlay as OBS browser source

    Shop->>Fourthwall: ① Order placed / Tip sent
    Fourthwall->>Server: ② Webhook (ORDER_PLACED / DONATION)
    Note over Server: ③ Resolve shopId,<br/>fan out to overlays
    Server-->>Overlay: ④ SSE / WebSocket push
    Note over Overlay: ⑤ Queue + animate card,<br/>play sound
```

Five moving parts:

1. **Install + webhook registration.** On install, your app runs the OAuth flow once, then subscribes to `ORDER_PLACED` and `DONATION` for that shop, scoped to your webhook receiver URL.
2. **Embedded settings page.** An iframe Fourthwall renders inside the dashboard, identified by a signed handoff. This is the creator's control surface: overlay URL, test alert, and privacy/enable toggles.
3. **Webhook receiver.** A signed HTTP endpoint that validates the payload, looks up the owning shop, and hands the event to the fan-out layer.
4. **Push transport.** A long-lived connection from the browser source to your server. The path-of-least-resistance is Server-Sent Events (SSE) — one direction, automatic reconnect, no protocol upgrade.
5. **Overlay page.** A static HTML/JS page identified by a per-shop slug. It subscribes to the stream, queues incoming events, and animates them one at a time.

## 1. Install the App

Use the standard [App OAuth flow](/guides/oauth) so the creator can authorize your app against their shop with one click at install. On callback, exchange the code for an access token and resolve the shop with [`GET /shops/current`](/api-reference/platform/shop/get-current-shop). You need the token just long enough to register the webhooks (next step) — you don't have to store it. Afterward, the [embedded settings](/apps/embedded-settings) handoff re-identifies the shop on every request by signature, so persist only what you key off per shop (the webhook secret, the toggles).

```ts theme={null}
// after OAuth callback
const shop = await fetch(
  "https://api.fourthwall.com/open-api/v1.0/shops/current",
  { headers: { Authorization: `Bearer ${accessToken}` } }
).then(r => r.json());
```

## 2. Register the Webhooks

Immediately after connecting a shop, register a single webhook subscribed to the alert-worthy events. Subscribing once per shop — instead of per overlay session — keeps the configuration stable across reconnects.

```bash theme={null}
POST https://api.fourthwall.com/open-api/v1.0/webhooks
Authorization: Bearer {accessToken}
Content-Type: application/json

{
  "url": "https://your-app.com/webhooks/fourthwall",
  "allowedTypes": ["ORDER_PLACED", "DONATION"]
}
```

The response includes a `secret` you'll use to [verify signatures](/webhooks/signature-verification) on every incoming event.

<Tip>
  **Also subscribe to `PLATFORM_APP_DISCONNECTED`.** When a creator disconnects your app from their dashboard, this event lets you clean up their stored tokens and stop trying to push to a stale overlay.
</Tip>

## 3. Receive and Fan Out

Your webhook receiver:

1. Verifies the `X-Fourthwall-Hmac-Apps-SHA256` header against the body using the stored `secret`.
2. Resolves the shop from the webhook payload (`shopId` is included).
3. Pushes the event onto the in-memory channel (or pub/sub topic) for that `shopId`.
4. Returns `200 OK` immediately — alert overlays are fire-and-forget; if no overlay is listening, the event is silently dropped.

```ts theme={null}
// /api/webhooks/fourthwall
export async function POST(req: Request) {
  const body = await req.text();
  const sig = req.headers.get("x-fourthwall-hmac-apps-sha256");
  if (!verifySignature(body, sig, secret)) return new Response("bad sig", { status: 401 });

  const event = JSON.parse(body);
  await channel(event.shopId).publish(event);   // e.g. Redis pub/sub, in-memory EventEmitter
  return new Response("ok");
}
```

## 4. Push to the Overlay

The simplest transport that works in every browser source is **Server-Sent Events**. One endpoint per shop, the browser opens an `EventSource`, your server holds the connection open and writes a line of JSON for each event.

```ts theme={null}
// /events/:shopId  (SSE endpoint)
export async function GET(req: Request, { params }: { params: { shopId: string } }) {
  const stream = new ReadableStream({
    start(controller) {
      const sub = channel(params.shopId).subscribe(event => {
        controller.enqueue(`data: ${JSON.stringify(event)}\n\n`);
      });
      req.signal.addEventListener("abort", () => sub.unsubscribe());
    },
  });
  return new Response(stream, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      "Connection": "keep-alive",
    },
  });
}
```

WebSockets work too, but bring upgrade-handshake complexity for no real gain — alerts are one-way push.

## 5. Build the Overlay Page

The creator copies a URL like `https://your-app.com/overlay/{shopId}` and pastes it into OBS as a browser source. The page opens an `EventSource`, queues events, and animates them serially so two cards never overlap.

```html theme={null}
<!-- /overlay/[shopId] -->
<div id="card" hidden></div>
<script>
  const queue = [];
  let playing = false;

  const es = new EventSource(`/events/${SHOP_ID}`);
  es.onmessage = e => {
    queue.push(JSON.parse(e.data));
    drain();
  };

  async function drain() {
    if (playing || !queue.length) return;
    playing = true;
    const event = queue.shift();
    await renderCard(event);   // animate in, hold, animate out, play sound
    playing = false;
    drain();
  }
</script>
```

A few non-obvious details worth getting right:

* **Reconnection.** `EventSource` retries automatically, but back off exponentially on the server side (e.g. `1s × 2^n`) so a flapping creator doesn't hammer you.
* **Identifying the shop.** Put the `shopId` in the URL path, not in a query parameter — browser sources sometimes strip query strings on reload.
* **No replay.** Don't try to backfill missed events when the overlay reconnects. The creator wasn't on screen to react to them anyway; replaying stale alerts feels worse than dropping them.
* **Idempotency.** Webhooks can be retried. Dedupe on the event ID before fanning out so the overlay doesn't show the same alert twice.
* **Sound.** Browsers gate audio behind a user gesture, but OBS auto-grants it for browser sources. Test in OBS, not in a regular browser tab.

## 6. The Settings Page

The creator manages everything from an [embedded settings](/apps/embedded-settings) page — the iframe Fourthwall renders inside the dashboard. Set its URL under **Settings URL** on your Platform App. Fourthwall loads it with a signed handoff:

```
https://your-app.com/?shop_id={id}&hmac={sig}&timestamp={ts}
```

Verify the `hmac` before trusting the request — it's what authenticates the shop, on the page load and on every API call the page makes back to you. It's **HMAC-SHA512, Base64-encoded** (distinct from the SHA256 hex used for webhook delivery) over `timestamp={ts}&shop_id={id}&app_id={app_id}`, signed with the HMAC secret from your Platform App's basic information.

```ts theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

function verifyEmbeddedSettings({ shopId, appId, timestamp, hmac, secret }) {
  const data = `timestamp=${timestamp}&shop_id=${shopId}&app_id=${appId}`;
  const expected = createHmac("sha512", secret).update(data).digest("base64");
  const a = Buffer.from(expected), b = Buffer.from(hmac);
  return a.length === b.length && timingSafeEqual(a, b);
}
```

Once verified, render the controls: the copyable overlay URL, a **test alert** button that publishes a synthetic event onto the shop's channel, and the privacy/enable toggles below. See the [Embedded Settings](/apps/embedded-settings) guide for the full contract.

## Privacy

Supporters can be sensitive about having their real name on a livestream. Give the creator a toggle — on the settings page above — to replace the supporter's name with a generic label ("Anonymous", "A supporter") before the alert is pushed to the overlay. Apply the transformation server-side so the overlay never sees the real name.

## Reference

<CardGroup cols={2}>
  <Card title="Runnable Example" icon="github" href="https://github.com/FourthwallHQ/fourthwall-examples/tree/main/examples/streaming-alerts">
    The full implementation of this guide
  </Card>

  <Card title="App OAuth" icon="key" href="/guides/oauth">
    Connect a shop with one click
  </Card>

  <Card title="Embedded Settings" icon="window" href="/apps/embedded-settings">
    Render your settings page in the dashboard
  </Card>

  <Card title="Manage Webhooks via API" icon="webhook" href="/webhooks/api-management">
    Register subscriptions programmatically
  </Card>

  <Card title="Verify Signatures" icon="shield-check" href="/webhooks/signature-verification">
    Validate the HMAC header
  </Card>

  <Card title="ORDER_PLACED event" icon="cart-shopping" href="/api-reference/platform/webhook ORDER_PLACED">
    Purchase webhook payload
  </Card>

  <Card title="DONATION event" icon="heart" href="/api-reference/platform/webhook DONATION">
    Tip webhook payload
  </Card>

  <Card title="Retry Policies" icon="rotate-right" href="/webhooks/retry-policies">
    What happens when your receiver is down
  </Card>
</CardGroup>
