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

# Set Up Webhooks

> Get notified when payments succeed, subscriptions renew, or refunds are processed

## What Are Webhooks?

Webhooks are automatic notifications that Waffo Pancake sends to your server when something happens — a payment succeeds, a subscription renews, a refund is processed, etc.

**Why you need them:**

* **Automate delivery**: Send download links or grant access immediately after payment
* **Keep your system in sync**: Update your database when subscription status changes
* **React to events**: Handle failed payments, cancellations, and refunds in real time

***

## Step 1: Add a Webhook in the Dashboard

<Steps>
  <Step title="Go to Settings">
    In your Dashboard, navigate to **Settings → Webhooks**.

    <Frame>
      <img src="https://mintcdn.com/waffo/MyxWRBeTICeW5ClC/images/guides/webhook-settings.png?fit=max&auto=format&n=MyxWRBeTICeW5ClC&q=85&s=ebf0fc610d7a43a1578dca3607e5f324" alt="Webhooks settings page with Test Mode and Live Mode sections" width="2000" height="1140" data-path="images/guides/webhook-settings.png" />
    </Frame>
  </Step>

  <Step title="Choose a Format">
    Select how the payload should be shaped for the destination — Raw JSON for your own backend, a chat-platform format (Slack / Discord / Feishu / Telegram) so messages render natively in that tool, or **OpenClaw / Hermes** (the self-hosted Pancake plugin) for custom routing and downstream logic. See [Choose a payload format](#choose-a-payload-format) below.

    <Frame>
      <img src="https://mintcdn.com/waffo/MyxWRBeTICeW5ClC/images/guides/webhook-add-format.png?fit=max&auto=format&n=MyxWRBeTICeW5ClC&q=85&s=69333c1c37cae6513c9574f502c69000" alt="Add Webhook modal with the Format dropdown open showing Raw, Feishu, Slack, Discord, Telegram, OpenClaw/Hermes" width="2000" height="1140" data-path="images/guides/webhook-add-format.png" />
    </Frame>
  </Step>

  <Step title="Set Your Webhook URL">
    Enter the URL where Waffo Pancake should send event notifications.

    * **Test mode URL**: Your development/staging server (e.g., `https://staging.yoursite.com/webhooks/waffo`)
    * **Production URL**: Your production server (e.g., `https://yoursite.com/webhooks/waffo`)

    <Tip>
      You can set different URLs for test and production environments. This keeps test events separate from real ones.
    </Tip>

    <Note>
      If you picked **Telegram** in the previous step, you'll fill in a **Bot Token** and a **Chat ID** instead of a URL. See [Telegram](#telegram) below for how to obtain them.
    </Note>
  </Step>

  <Step title="Pick the events">
    All events are subscribed by default. Untick the ones you don't want.
  </Step>

  <Step title="Save">
    Click **Save Webhook**.
  </Step>
</Steps>

You can configure **up to 10 webhooks per environment**, so you can fan out the same events to multiple destinations (your backend, a Slack channel, an internal dashboard) without writing your own relay. The same URL cannot be added twice in the same environment.

<Note>
  **Production note**: Live Mode webhooks only deliver after your store passes review. While the store is still under review, use Test Mode webhooks against staging code paths.
</Note>

***

## Choose a payload format

Waffo can deliver each event in the format your destination expects, so you don't have to write a translator. Pick the format when you add the webhook; you can re-add a webhook with a different format if you change your mind.

| Format                | Use when…                                                                                     | Endpoint                                                 |
| --------------------- | --------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| **Raw** (default)     | You're calling your own backend and want the canonical, signed JSON payload.                  | Any HTTPS URL                                            |
| **Feishu / Lark**     | You want events posted into a Feishu group via a custom bot.                                  | `https://open.feishu.cn/open-apis/bot/v2/hook/<hook-id>` |
| **Slack**             | You want events posted into a Slack channel via an Incoming Webhook.                          | `https://hooks.slack.com/services/T.../B.../<token>`     |
| **Discord**           | You want events posted into a Discord channel via a channel webhook.                          | `https://discord.com/api/webhooks/<id>/<token>`          |
| **Telegram**          | You want events posted into a Telegram chat or group via a bot.                               | Bot Token + Chat ID (URL is built for you)               |
| **OpenClaw / Hermes** | You're routing events through the self-hosted **Pancake plugin** for custom downstream logic. | `https://relay.waffo.ai/webhook/<webhook-id>`            |

Only **Raw** carries an RSA-SHA256 signature. The chat-platform formats authenticate via the URL-embedded token issued by the destination platform, so signature verification is not used for them.

### Raw

1. In your backend, expose an HTTPS endpoint that accepts `POST application/json`.
2. Paste the URL into the **Endpoint URL** field.
3. On the same **Settings → Webhooks** page, copy the **Webhook Public Key** for the environment you're integrating (Test or Production) — you'll use it to verify deliveries (see [API Reference — Webhooks](/api-reference/webhooks)). The key is platform-level, shared across all stores; it doesn't depend on the webhook you just added, and it stays the same whenever you add, edit, or delete webhook URLs.

### Feishu / Lark

1. In your Feishu group, add a **Custom Bot** and copy its webhook URL.
2. The URL looks like `https://open.feishu.cn/open-apis/bot/v2/hook/<hook-id>` (or `open.larksuite.com/...` outside China — both work).
3. Paste it into the **Endpoint URL** field.

### Slack

1. In your Slack workspace, install an **Incoming Webhook** for the destination channel.
2. Copy the webhook URL — it looks like `https://hooks.slack.com/services/T.../B.../<token>`.
3. Paste it into the **Endpoint URL** field.

### Discord

1. In the destination channel, open **Edit Channel → Integrations → Webhooks → New Webhook** and copy the URL.
2. Format: `https://discord.com/api/webhooks/<id>/<token>`.
3. Paste it into the **Endpoint URL** field.

### Telegram

Telegram needs both a **Bot Token** and a **Chat ID** — Waffo builds the Telegram `sendMessage` URL for you.

1. Talk to [@BotFather](https://t.me/botfather) and create (or pick) a bot. Copy its token, e.g. `123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11`.
2. Add the bot to the destination chat or channel and grant it permission to send messages.
3. Get the chat ID. The easiest way: send a message in the chat, then visit `https://api.telegram.org/bot<TOKEN>/getUpdates` and copy the `chat.id` from the response.
4. In the **Add Webhook** modal, choose **Telegram**, paste the **Bot Token** and **Chat ID**, and save.

Behind the scenes Waffo posts to `https://api.telegram.org/bot<TOKEN>/sendMessage` with `chat_id` set to the value you provided.

### OpenClaw / Hermes (Pancake plugin)

The Pancake plugin is Waffo's self-hosted relay. It receives events from Waffo, runs your custom logic (templating, rate-limiting, multi-fan-out), and forwards messages to wherever you want. **OpenClaw** and **Hermes** are two ready-to-run flavors of the plugin; pick whichever matches your stack.

1. Install the plugin with the matching setup command:

   ```bash theme={"system"}
   # OpenClaw
   npx -p @waffo/pancake-plugin openclaw-setup

   # Hermes
   npx -p @waffo/pancake-plugin hermes-setup
   ```

   Full setup guide: [github.com/waffo-com/waffo-pancake-plugin](https://github.com/waffo-com/waffo-pancake-plugin).

2. After install, the plugin gives you a relay URL of the form `https://relay.waffo.ai/webhook/<webhook-id>`.

3. In the **Add Webhook** modal, choose **OpenClaw / Hermes**, paste the relay URL, and save.

***

## Step 2: Understand Event Types

Waffo Pancake sends these webhook events:

### Order Events

| Event             | When It Fires                | Common Action                 |
| ----------------- | ---------------------------- | ----------------------------- |
| `order.completed` | Order completed successfully | Deliver product, grant access |

### Subscription Events

| Event                            | When It Fires                  | Common Action                     |
| -------------------------------- | ------------------------------ | --------------------------------- |
| `subscription.activated`         | Subscription activated         | Create account, set limits        |
| `subscription.payment_succeeded` | Subscription payment succeeded | Extend access period              |
| `subscription.updated`           | Subscription details changed   | Update access level               |
| `subscription.canceling`         | Cancellation requested         | Notify team, send retention offer |
| `subscription.uncanceled`        | Subscription reactivated       | Restore full access               |
| `subscription.canceled`          | Subscription fully ended       | Revoke access                     |
| `subscription.past_due`          | Payment failed                 | Notify customer, retry            |

### Refund Events

| Event              | When It Fires            | Common Action                 |
| ------------------ | ------------------------ | ----------------------------- |
| `refund.succeeded` | Refund processed         | Revoke access, update records |
| `refund.failed`    | Refund processing failed | Notify merchant, investigate  |

***

## Step 3: Set Up Email Notifications

In addition to webhook events, you can configure email notifications for both you and your customers.

### Merchant Notifications

Go to **Settings → Notifications** to choose which events trigger email alerts to you:

* New orders
* New subscriptions
* Failed payments
* Refund requests

<Frame>
  <img src="https://mintcdn.com/waffo/5dRlk76Jm-9Zm6Tj/images/guides/notification-settings.png?fit=max&auto=format&n=5dRlk76Jm-9Zm6Tj&q=85&s=c1e650a73ceae7f9ee34289cc7efb9c4" alt="Notification settings" width="1440" height="900" data-path="images/guides/notification-settings.png" />
</Frame>

### Customer Notifications

Customers automatically receive emails for:

* Order confirmation
* Subscription confirmation
* Subscription renewal
* Subscription cancellation
* Payment failure

You can toggle these in the notification settings.

***

## How Webhooks Work

```
Event occurs (e.g., payment succeeds)
    ↓
Waffo Pancake sends POST request to your webhook URL
    ↓
Your server receives the event
    ↓
Your server processes it (deliver product, update database, etc.)
    ↓
Your server responds with 200 OK
```

### Retry Policy

If your server doesn't respond with a 2xx status code, Waffo Pancake retries:

| Attempt   | Delay      |
| --------- | ---------- |
| 1st retry | 5 minutes  |
| 2nd retry | 30 minutes |
| 3rd retry | 2 hours    |
| 4th retry | 8 hours    |
| 5th retry | 24 hours   |

After 5 failed retries, the event is marked as failed.

***

## For Developers: Code Integration

If you're integrating webhooks with your server, here's how the flow works:

### 1. Create a Webhook Endpoint

Your server needs a POST endpoint to receive events. The specific implementation depends on your tech stack.

### 2. Verify the Signature

Every webhook request includes a signature header for security. Verify it to ensure the request came from Waffo Pancake, not a malicious third party.

### 3. Process Events

Parse the event type and data, then take the appropriate action (deliver product, update subscription status, etc.).

### 4. Respond Quickly

Return a 200 status code as fast as possible. If you need to do heavy processing, do it asynchronously after responding.

<Note>
  For detailed code examples and signature verification, see the [API Reference — Webhooks](/api-reference/webhooks).
</Note>

***

## Testing Webhooks

<Steps>
  <Step title="Set Test Webhook URL">
    In Dashboard → Settings → Webhooks, enter your test server URL.
  </Step>

  <Step title="Make a Test Purchase">
    Use test mode to create a purchase. This triggers webhook events to your test URL.
  </Step>

  <Step title="Verify Receipt">
    Check your server logs to confirm the webhook was received and processed correctly.
  </Step>
</Steps>

***

## Local development with ngrok

When you're building your webhook handler locally, Pancake's servers can't reach `http://localhost:3000` directly. You need a **public HTTPS tunnel** that forwards inbound requests to your local port. We recommend [ngrok](https://ngrok.com) — it's free for development and preserves all custom headers (including `X-Waffo-Signature`).

### Why a tunnel is required

Webhook delivery is **inbound to your server**: Pancake POSTs to whatever URL you configure. A localhost URL is only reachable from your own machine, so without a tunnel the requests never arrive. The tunnel gives your local server a public HTTPS hostname for the duration of your dev session.

### Install and start ngrok

<Steps>
  <Step title="Install">
    * **macOS**: `brew install ngrok`
    * **Windows / Linux**: download from [ngrok.com/download](https://ngrok.com/download)
  </Step>

  <Step title="Authenticate (free tier is fine)">
    Sign up at [ngrok.com](https://ngrok.com), grab your authtoken from the dashboard, then run:

    ```bash theme={"system"}
    ngrok config add-authtoken <your-authtoken>
    ```
  </Step>

  <Step title="Start the tunnel">
    Point ngrok at your local server's port. If your webhook handler runs on `localhost:3000`:

    ```bash theme={"system"}
    ngrok http 3000
    ```

    ngrok prints a forwarding URL like:

    ```
    Forwarding  https://abc-123-456-789.ngrok-free.app -> http://localhost:3000
    ```

    Copy the `https://...ngrok-free.app` URL — that's your public webhook endpoint.
  </Step>
</Steps>

### Wire it into Pancake

<Steps>
  <Step title="Configure the Test Webhook URL">
    In the Merchant Dashboard, open your store and go to **Settings → Webhooks**. Paste the ngrok URL (with your handler path appended, e.g. `https://abc-123.ngrok-free.app/api/webhooks/waffo`) into **Test Webhook URL** and select the events you want to receive. Save.

    <Warning>
      Only configure Test environment with the ngrok URL. Never put a temporary tunnel URL into the Production webhook — production traffic must hit a stable, deployed endpoint.
    </Warning>
  </Step>

  <Step title="Trigger an event">
    Either click **Send test event** in the Dashboard, or perform a real action in test mode (e.g., create a checkout and complete it with a [test card](/api-reference/webhooks#testing)).
  </Step>

  <Step title="Inspect the request">
    Open ngrok's local web inspector at [http://127.0.0.1:4040](http://127.0.0.1:4040) — it shows every request that came through, including headers and body. Confirm the `X-Waffo-Signature` header is present.
  </Step>

  <Step title="Verify in your handler">
    Check your local server logs. You should see the webhook POST landing and `verifyWebhook()` succeeding. If verification fails, see the pitfalls below.
  </Step>
</Steps>

### Common pitfalls

| Issue                                                          | Why it happens                                                                                 | Fix                                                                                                                    |
| -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **`localtunnel` always returns 401 on signature verification** | localtunnel strips custom HTTP headers, so `X-Waffo-Signature` never reaches your handler      | Use ngrok or cloudflared instead — both preserve all headers                                                           |
| **The ngrok URL changed after restart**                        | The free tier issues a new random subdomain on every `ngrok http` run                          | Update the Test Webhook URL in the Dashboard each time. For frequent dev, ngrok's paid tier offers reserved subdomains |
| **Events stop arriving after a while**                         | Free ngrok sessions expire after a few hours                                                   | Restart `ngrok http 3000` and re-paste the new URL                                                                     |
| **Pancake retried the event multiple times**                   | Your handler returned a non-2xx response and the delivery was retried with exponential backoff | Fix the handler, then either wait for the next retry or use **Resend** in the Dashboard's webhook delivery log         |

### Cloudflared as an alternative

If you prefer a no-account option, [cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/) works similarly:

```bash theme={"system"}
cloudflared tunnel --url http://localhost:3000
```

It preserves headers, requires no signup for ad-hoc tunnels, and prints a `*.trycloudflare.com` URL you can paste into the Dashboard. The trade-off is no built-in request inspector UI — you'll rely on your own server logs.

### Quick verification checklist

* [ ] Local server is running and listening on the expected port
* [ ] `ngrok http <port>` is running, forwarding URL captured
* [ ] Test Webhook URL in Dashboard matches the **current** ngrok URL (with handler path)
* [ ] Inbound request visible at [http://127.0.0.1:4040](http://127.0.0.1:4040) with `X-Waffo-Signature` header
* [ ] Local logs show `verifyWebhook` returning success and your event handler running

***

## Best Practices

1. **Respond quickly**: Return 200 immediately, process asynchronously
2. **Handle duplicates**: Events may be sent more than once — make your processing idempotent
3. **Verify signatures**: Always verify the webhook signature before processing
4. **Log everything**: Keep logs of received webhooks for debugging
5. **Monitor failures**: Check your Dashboard for failed webhook deliveries

***

## Checklist

* [ ] Webhook URLs configured for test and production
* [ ] Email notifications configured for your team
* [ ] Customer notification preferences set
* [ ] Test webhook received and processed successfully
* [ ] Signature verification implemented (if using code integration)

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Manage Refunds" icon="rotate-left" href="/guides/refunds">
    Handle refund requests and track refund status
  </Card>

  <Card title="API Reference — Webhooks" icon="code" href="/api-reference/webhooks">
    Detailed webhook payload format and code examples
  </Card>
</CardGroup>
