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

# 新增 Webhook

> 为门店配置新的 Webhook 端点

为门店新增 Webhook 端点。每个门店在所有通道下最多可配置 **20 个 Webhook**。

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

**认证方式：** API Key（需要 owner 或 admin 角色）

## 请求体

| 字段         | 类型             | 必需 | 说明                                                                               |
| ---------- | -------------- | -- | -------------------------------------------------------------------------------- |
| `storeId`  | string         | 是  | Store ID（Short ID 格式 `STO_xxx`）                                                  |
| `channel`  | string         | 是  | 取值为 `http`、`feishu`、`discord`、`telegram`、`slack` 之一                              |
| `url`      | string         | 是  | 目标 Webhook URL（HTTPS；商户需确保 URL 与所选通道匹配）                                          |
| `events`   | string\[]      | 是  | 订阅的事件类型 —— 例如 `["order.completed", "refund.succeeded"]`。空数组表示不向该 Webhook 触发任何事件。 |
| `testMode` | boolean        | 是  | `true` = 仅对测试交易触发；`false` = 仅对生产交易触发                                             |
| `secret`   | string \| null | 否  | 通道特定凭据（如 Telegram 的 `chat_id`）。以不透明字符串形式存储。                                      |

## 请求示例

<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!,
  });

  // 标准 HTTPS Webhook（RSA 签名信封）
  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 —— 使用 Discord 原生 embed 格式
  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 放入 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>

## 成功响应 (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>
  返回的 `webhook.id` 是 UUID，而非 Short ID —— Webhook ID 不属于 `IdPrefix` 范围。请将其原样传入 `update-webhook` 与 `remove-webhook`。
</Note>

## 响应字段

| 字段          | 类型             | 说明                                                         |
| ----------- | -------------- | ---------------------------------------------------------- |
| `id`        | string         | Webhook UUID                                               |
| `storeId`   | string         | 所属门店的 UUID                                                 |
| `channel`   | string         | Webhook 通道（`http`、`feishu`、`discord`、`telegram` 或 `slack`） |
| `url`       | string         | 目标 Webhook URL                                             |
| `events`    | string\[]      | 订阅的事件类型                                                    |
| `testMode`  | boolean        | `true` 对测试交易触发，`false` 对生产交易触发                             |
| `secret`    | string \| null | 通道特定凭据（不透明字符串），未设置时为 `null`                                |
| `createdAt` | string         | 创建时间戳（ISO 8601）                                            |
| `updatedAt` | string         | 最后更新时间戳（ISO 8601）                                          |

## 错误响应

> **重试策略**：4xx 一律不要重试 — 修正请求后重发。5xx 指数退避重试（起步 5s，最多 3 次）。

| 状态码 | `errors[0].message`                                                      | 含义                         | 推荐处理                 |
| --- | ------------------------------------------------------------------------ | -------------------------- | -------------------- |
| 400 | `Missing required field: storeId`                                        | 未提供 `storeId`              | 修正请求体后重发             |
| 400 | `Expected format: STO_xxx, got "..."`                                    | `storeId` Short ID 解码失败    | 修正 `storeId` 格式后重发   |
| 400 | `testMode must be a boolean`                                             | `testMode` 不是布尔值           | 传布尔值后重发              |
| 400 | `Invalid channel: must be one of http, feishu, discord, telegram, slack` | `channel` 不在允许列表           | 使用允许的 channel        |
| 400 | `Invalid URL format`                                                     | `url` 不是合法 URL             | 修正 URL 后重发           |
| 400 | `events must be a string array`                                          | `events` 不是字符串数组           | 传字符串数组后重发            |
| 400 | `secret must be a string or null`                                        | `secret` 不是 string 或 null  | 传 string 或 null 后重发  |
| 400 | `Webhook limit reached (max 20 per store)`                               | 门店已达 20 个 Webhook 上限       | 先删除已有 Webhook        |
| 401 | `Missing merchantId in request context`                                  | API Key 认证未解析出 merchant    | 检查 API Key 请求头与签名    |
| 403 | `Not authorized to manage webhooks for this store`                       | 商户不是该门店的 `owner` 或 `admin` | 验证商户在该门店的角色          |
| 500 | `Internal server error`                                                  | 服务端意外失败                    | 指数退避重试（起步 5s，最多 3 次） |
