> ## 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.

# Add Webhook

> Configure a new webhook endpoint for a store

Add a webhook endpoint to a store. Each store can have up to **20 webhooks** across all channels.

```
POST /v1/actions/store/add-webhook
```

**Authentication:** API Key (owner or admin role required)

## Request Body

| Field      | Type           | Required | Description                                                                                                                |
| ---------- | -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `storeId`  | string         | Yes      | Store ID (Short ID format `STO_xxx`)                                                                                       |
| `channel`  | string         | Yes      | One of `http`, `feishu`, `discord`, `telegram`, `slack`                                                                    |
| `url`      | string         | Yes      | Target webhook URL (HTTPS; merchant ensures URL matches the chosen channel)                                                |
| `events`   | string\[]      | Yes      | Subscribed event types — e.g. `["order.completed", "refund.succeeded"]`. Empty array means no events fire to this webhook. |
| `testMode` | boolean        | Yes      | `true` = fires for test transactions; `false` = fires for production                                                       |
| `secret`   | string \| null | No       | Channel-specific credential (e.g. Telegram `chat_id`). Stored as opaque text.                                              |

## Example Request

<CodeGroup>
  ```typescript SDK theme={"system"}
  import { WaffoPancake } from "@waffo/pancake-ts";

  const client = new WaffoPancake({
    merchantId: process.env.WAFFO_MERCHANT_ID!,
    privateKey: process.env.WAFFO_PRIVATE_KEY!,
  });

  // Standard HTTPS webhook (RSA-signed envelope)
  const { webhook } = await client.webhooks.add({
    storeId: "STO_2aUyqjCzEIiEcYMKj7TZtw",
    channel: "http",
    url: "https://example.com/webhooks/pancake",
    events: ["order.completed", "refund.succeeded"],
    testMode: false,
  });
  ```

  ```typescript SDK (Discord) theme={"system"}
  // Discord webhook — uses Discord's native embed format
  await client.webhooks.add({
    storeId: "STO_2aUyqjCzEIiEcYMKj7TZtw",
    channel: "discord",
    url: "https://discord.com/api/webhooks/123456789/abc-def-ghi",
    events: ["order.completed"],
    testMode: false,
  });
  ```

  ```typescript SDK (Telegram) theme={"system"}
  // Telegram bot — chat_id goes in secret
  await client.webhooks.add({
    storeId: "STO_2aUyqjCzEIiEcYMKj7TZtw",
    channel: "telegram",
    url: "https://api.telegram.org/bot123456:ABC-DEF/sendMessage",
    events: ["order.completed", "refund.failed"],
    testMode: false,
    secret: "8737101383",
  });
  ```

  ```bash cURL theme={"system"}
  curl -X POST https://api.waffo.com/v1/actions/store/add-webhook \
    -H "Content-Type: application/json" \
    -H "X-Merchant-Id: $WAFFO_MERCHANT_ID" \
    -H "X-Signature: ..." \
    -d '{
      "storeId": "STO_2aUyqjCzEIiEcYMKj7TZtw",
      "channel": "http",
      "url": "https://example.com/webhooks/pancake",
      "events": ["order.completed"],
      "testMode": false
    }'
  ```
</CodeGroup>

## Success Response (200)

```json theme={"system"}
{
  "data": {
    "webhook": {
      "id": "11111111-2222-3333-4444-555555555555",
      "storeId": "uuid-of-store",
      "channel": "http",
      "url": "https://example.com/webhooks/pancake",
      "events": ["order.completed", "refund.succeeded"],
      "testMode": false,
      "secret": null,
      "createdAt": "2026-05-07T00:00:00.000Z",
      "updatedAt": "2026-05-07T00:00:00.000Z"
    }
  }
}
```

<Note>
  The returned `webhook.id` is a UUID, not a Short ID — webhook IDs are not in the `IdPrefix` scope. Pass it as-is to `update-webhook` and `remove-webhook`.
</Note>

## Response Fields

| Field       | Type           | Description                                                           |
| ----------- | -------------- | --------------------------------------------------------------------- |
| `id`        | string         | Webhook UUID                                                          |
| `storeId`   | string         | Owning store UUID                                                     |
| `channel`   | string         | Webhook channel (`http`, `feishu`, `discord`, `telegram`, or `slack`) |
| `url`       | string         | Target webhook URL                                                    |
| `events`    | string\[]      | Subscribed event types                                                |
| `testMode`  | boolean        | `true` for test transactions, `false` for production                  |
| `secret`    | string \| null | Channel-specific credential (opaque text), `null` if unset            |
| `createdAt` | string         | Creation timestamp (ISO 8601)                                         |
| `updatedAt` | string         | Last update timestamp (ISO 8601)                                      |

## Errors

> **Retry policy:** Never retry 4xx — fix the request and resubmit. Retry 5xx with exponential backoff (start 5s, max 3 attempts).

| Status | `errors[0].message`                                                      | What it means                                     | Recommended handling                                      |
| ------ | ------------------------------------------------------------------------ | ------------------------------------------------- | --------------------------------------------------------- |
| 400    | `Missing required field: storeId`                                        | `storeId` was not provided                        | Fix the request body, resubmit                            |
| 400    | `Expected format: STO_xxx, got "..."`                                    | `storeId` Short ID could not be decoded           | Fix the `storeId` format, resubmit                        |
| 400    | `testMode must be a boolean`                                             | `testMode` is not a boolean                       | Pass a boolean value, resubmit                            |
| 400    | `Invalid channel: must be one of http, feishu, discord, telegram, slack` | `channel` is not in the allowed list              | Use one of the allowed channels                           |
| 400    | `Invalid URL format`                                                     | `url` is not a valid URL                          | Fix the URL, resubmit                                     |
| 400    | `events must be a string array`                                          | `events` is not an array of strings               | Pass a string array, resubmit                             |
| 400    | `secret must be a string or null`                                        | `secret` is not string or null                    | Pass string or null, resubmit                             |
| 400    | `Webhook limit reached (max 20 per store)`                               | Store already has 20 webhooks                     | Remove an existing webhook first                          |
| 401    | `Missing merchantId in request context`                                  | API Key authentication did not resolve a merchant | Verify API Key headers and signature                      |
| 403    | `Not authorized to manage webhooks for this store`                       | Merchant is not `owner` or `admin` of the store   | Verify the merchant's role on this store                  |
| 500    | `Internal server error`                                                  | Unexpected server-side failure                    | Retry with exponential backoff (start 5s, max 3 attempts) |
