# Webhook 概览

> Shoplazza Webhook 的完整机制：前置条件、注册订阅、推送格式与请求头、投递规则与签名校验。

Webhook 让你的应用**被动接收**店铺事件——当订单创建、商品更新、应用卸载等事件发生时，Shoplazza 主动把事件 payload POST 到你预先注册的 URL。

:::tip
想直接查事件列表？去 [Webhook 事件清单](/docs/app/building-blocks/webhooks/supported-webhook-events)。
:::

## 你需要准备

你的端点能收到 webhook 的前提是：

- 商家已在店铺安装你的应用。
- 你的应用已按某个 API 版本订阅了对应事件（topic）。

## 注册一个订阅

向 webhooks 端点发起带认证的 `POST` 请求——每个请求都带 `Access-Token: <access_token>` 请求头。

`POST https://<shop>/openapi/2022-01/webhooks`

请求体：

```json5
{
  "topic": "products/create", // 要订阅的事件，完整清单见下方 Webhook 事件清单
  "address": "https://your-app.example.com/webhook/products-create" // 你的 HTTPS 端点
}
```

响应——创建成功的订阅对象：

```json
{
  "id": "1234",
  "topic": "products/create",
  "address": "https://your-app.example.com/webhook/products-create",
  "format": "json",
  "created_at": "2024-01-15T08:30:00Z",
  "updated_at": "2024-01-15T08:30:00Z"
}
```

:::note
请求体结构因 API 版本而异。上面的示例针对 `2022-01`，用的是扁平结构。从 `2025-06` 起，请求体必须把订阅包在 `webhook` 对象里：

```json
{ "webhook": { "topic": "products/create", "address": "https://your-app.example.com/webhook/products-create" } }
```
:::

订阅的创建、查询、更新、删除见完整的 [Webhook API](/api/webhooks)。订阅对象的字段（`id`、`topic`、`address`、`format`、`created_at`、`updated_at`）也在那里说明。

## 工作原理

```mermaid
flowchart TD
    A[应用调 POST /webhooks 注册订阅] --> B[Shoplazza 持久化订阅: topic, address]
    B --> C[事件触发: Shoplazza POST payload 到你的 address]
    C --> D[你的端点: 验签 → 处理 → 5 秒内返回状态码]
    D --> E{返回?}
    E -- 2xx --> G[成功]
    E -- 5xx --> R[重试]
    E -- 4xx / 超时 / 连接失败 --> X[不重试, 丢弃该消息]
    R -- 递增延迟后重新投递 --> C
    R -. 约 24h 内约 11 次仍失败 .-> X
```

## 推送请求格式

事件触发时，Shoplazza 发起一个 HTTP `POST`，包含 JSON 请求体和一组请求头。

### 请求头

| 请求头 | 说明 | 示例 |
|---|---|---|
| `X-Shoplazza-Topic` | 触发本次推送的事件。 | `orders/create` |
| `X-Shoplazza-Hmac-Sha256` | 请求体的 HMAC-SHA256 签名（base64 编码），使用应用的 Client Secret 生成。 | |
| `X-Shoplazza-Shop-Domain` | 产生该事件的店铺域名。 | `example.myshoplaza.com` |
| `X-Shoplazza-Api-Version` | 序列化 payload 使用的 API 版本。 | `2025-06` |
| `X-Shoplazza-Deduplication-ID` | 本次推送的去重 ID，同一事件重发时保持不变。 | |

### 请求体

请求体是该 topic 对应的资源 payload。以 [`products/update`](/docs/app/building-blocks/webhooks/supported-webhook-events/products/update) 为例：

```json5
{
  "product": {
    "id": "325431fd-102b-4374-9769-2b3c171f28e8",
    "title": "ICOICE Ocean Green | 1 Year",
    "vendor": "ICOICE",
    "published": true,
    "inventory_quantity": 78386,
    "created_at": "2023-08-10T06:58:08Z",
    "updated_at": "2026-06-22T10:54:12Z"
    // ...更多字段：handle、tags、image、images、options、variants
  }
}
```

完整 payload 结构见 [products/update 事件页](/docs/app/building-blocks/webhooks/supported-webhook-events/products/update)。

## 推送规约

- **超时**：你的端点必须在 **5 秒**内返回 `2xx` 状态码。
- **成功**：任何 `2xx` 状态码均视为成功。
- **重试**：只有返回 `5xx` 才会触发重试。生产环境下，一次失败的推送会在约 24 小时内重试约 11 次，间隔递增：1m、1m、5m、10m、30m、1h、2h，之后每 4h 一次。
- **重试耗尽**：重试次数用完后，该推送被丢弃，**不会**取消订阅。
- **重复推送**：同一事件可能到达多次。请让处理逻辑具备幂等性——例如，跳过 `X-Shoplazza-Deduplication-ID` 已处理过的事件。

:::warning
超时和连接失败**不会**重试，只有明确返回 `5xx` 才会。请先快速返回 `2xx`、再异步处理事件，避免处理慢导致漏收事件。
:::

## 验证签名

收到 Shoplazza 投递的 POST 请求后，应用**必须**先验证签名才能信任 payload。

每个 webhook 请求都带一个 base64 编码的 `X-Shoplazza-Hmac-Sha256` 请求头，由应用的 **Client Secret** 对**请求原始 body** 做 HMAC-SHA256、再 base64 编码生成。验证时用相同算法计算并与该 header 做时间安全比较，一致即可信任 payload。**在应用响应 webhook 之前完成验证。**

:::note
与 OAuth 回调（场景 1）的差异：Webhook 对**原始 body** 签名、输出 **base64**；OAuth 对排序后的 query 串签名、输出 hex。
:::

算法步骤与多语言代码（Ruby、Node.js）见 [HMAC 签名校验 · 场景 2](/docs/app/building-blocks/authentication/signature-verification)。

## 相关

- [Webhook 事件清单](/docs/app/building-blocks/webhooks/supported-webhook-events) — 全量 topic 与 payload 示例
- [HMAC 签名校验](/docs/app/building-blocks/authentication/signature-verification) — 通用签名算法
- [Webhook API（OpenAPI）](/api/webhooks) — 订阅 CRUD 接口
