Webhook

每当订单或退款发生变化时,在您的服务器上接收带签名的 POST。这样您就不必每隔几秒查询一次 API。

本页内容

工作原理

  1. 1
    注册 Webhook

    在Webhook 面板中或通过 POST /webhooks 注册,填写您的服务器 URL 和希望接收的事件。您的账户需要有有效的 API 密钥。URL 必须以 https:// 开头,并使用可通过 DNS 解析的公共域名。IP 地址和包含用户名或密码的 URL 会被拒绝,返回 INVALID_INPUT。

  2. 2
    保存签名密钥

    它随创建响应返回,格式为 whsec_…,之后可用 GET /webhooks/{id}?include_signing_secret=true 再次查看。无法重新生成:如果泄露,请删除该 Webhook 并创建一个新的。不要与 API 密钥混淆。

  3. 3
    接收 POST

    每个事件都带有 svix-id、svix-timestamp 和 svix-signature 请求头,正文为 JSON 格式的事件。

  4. 4
    验证、保存并响应 2xx

    先核对签名并保存事件,然后再响应。任何非 2xx 的响应或没有响应都会触发新的重试。

事件

事件何时发送
order.created订单已创建。查询订单可获取最终金额和支付信息。
order.completed订单已支付并完成。在向您的客户交付商品之前,请先查询确认 status 为 completed。
order.expired订单未付款,已过期。
order.canceledByteMax 取消了订单,例如付款无法被接受并已退还给付款人。
order.updated订单数据变化但状态未变:已退款金额、已冻结金额或支付信息。这不表示已支付:订单支付后会发送 order.completed。
refund.created您的账户(通过 API 或控制台)发起的退款已登记。客服发起的退款只会体现在 order.updated 中。
refund.succeeded退款已完成:款项已退还给付款人。
refund.failed退款未能完成。
webhook.test测试事件,只发送到您测试的那个 Webhook。

每个事件包含 type(说明发生了什么)和 related_object:对象的 type(order、refund 或 webhook)、id 和 url(https://bytemax.exchange 上的查询路径)。处理前请查询该 url:即使事件延迟、重复或乱序到达,查询始终返回当前状态。

示例:order.completed
{
  "id": "evt_example",
  "type": "order.completed",
  "created_at": "2026-09-10T13:05:00.000Z",
  "related_object": {
    "type": "order",
    "id": "ord_example",
    "url": "/api/v1/orders/ord_example"
  }
}

发布状态

GET /events 和 GET /events/{id} 会返回 publication_status,表示事件是否已从 ByteMax 发出。事件在您服务器上的结果见投递记录。

pending
已记录,正在等待发送或重试。ByteMax 会尝试发布约 2 小时;之后事件变为 not_published。
published
已提交投递。每次尝试的结果见投递记录。
not_published
未发送:当时您的账户没有有效的 API 密钥、没有启用的 Webhook 订阅该事件类型,或发布失败。此事件无法重新发送;请查询订单或退款的当前状态。

接收并验证

在解析 JSON 之前,用密钥和原始请求正文进行验证。使用您所用语言的 Svix 官方库:它会核对签名以及 svix-timestamp 的时间容差。不要自行实现验证。

如何使用官方库验证签名(docs.svix.com)

到达您服务器的内容
POST /webhooks/bytemax HTTP/1.1
Content-Type: application/json
svix-id: msg_2gkQ7Wc9cD6yYq1xkKz7GJb3wQm
svix-timestamp: 1789045500
svix-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhUjM8G3xRk2w1vNq4T0=

{ "id": "evt_example", "type": "order.completed", ... }

避免重复处理

事件可能多次到达,也可能乱序。五条规则可避免重复效果:

  • 把事件的 id 作为唯一键:响应前先保存,并忽略重复的事件。
  • 不要依赖到达顺序:每收到一个事件,都查询订单或退款,并按查询返回的状态处理。
  • 在同一个事务中保存事件的效果和已处理标记。
  • 保存后才响应 2xx。如果保存失败,响应 5xx 以便再次接收。
  • 耗时的工作放在响应之外:先入队,快速响应。

测试与重发

POST /webhooks/{id}/test-events,带上 Content-Type: application/json 并以 {} 作为正文,会向该 Webhook 发送一个 webhook.test;该 Webhook 必须处于启用状态。用 GET /events/{id} 跟踪发布情况,并在您的服务器上确认事件已到达且签名已通过。测试只证明投递和签名有效;要测试订单和退款的处理,请跟踪一笔真实订单。

投递失败会自动重试,最多 7 次:分别在 5 秒、5 分钟、30 分钟、2 小时、5 小时、10 小时和 10 小时后。连续失败 5 天的 Webhook 会被自动停用。每次尝试的历史记录在控制台和 GET /webhooks/{id}/deliveries 中。要重发特定事件,使用 POST /webhooks/{id}/redeliveries,最多 100 个 ID。即使部分事件被拒绝,该调用也会返回 200:请查看每一项的 status。

queued
重发已被接受。
rejected
未重发任何内容。原因见 code:EVENT_NOT_FOUND(ID 不存在)、EVENT_NOT_ACCEPTED(该 Webhook 未订阅此事件类型,或该测试事件属于另一个 Webhook)、EVENT_NOT_PUBLISHED(事件仍为 pending 或已是 not_published)或 REDELIVERY_REJECTED(投递服务拒绝)。pending 事件在 GET /events/{id} 显示 published 后即可重发。
retry
重发未执行或无法确认:调用已超时,或投递服务繁忙或出错。请重新发送;如果事件到达两次,事件的 id 可以避免重复处理。

限制

  • 每个账户最多 200 个 Webhook,每个 URL 一个。
  • 事件在发布后 30 天内可查询。
  • 列表使用 items 和 next_cursor:每页 30 条,最多 100 条。游标只能用于同一个列表和相同的筛选条件。

错误

身份验证、通用错误和调用限制见 API 页面。以下代码为 Webhook 专有:

WEBHOOK_URL_EXISTSHTTP 409
您已有使用此 URL 的 Webhook。
WEBHOOK_LIMIT_REACHEDHTTP 409
您已有 200 个 Webhook。请先删除一个,再创建新的。
WEBHOOK_NOT_ENABLED_FOR_V1HTTP 409
测试和重发需要已启用的 v1 Webhook。请启用它;如果它来自旧版 API,请用 PATCH 迁移至 v1。
WEBHOOK_VERSION_REQUIREDHTTP 400
来自旧版 API 的 Webhook 只能在迁移至 v1 的同时修改:请发送 api_version: "v1" 和新的 events。
WEBHOOK_NOT_FOUNDHTTP 404
您的账户中没有该 ID 的 Webhook。
EVENT_NOT_FOUNDHTTP 404
没有该 ID 的事件。事件在发布后保留 30 天。
INVALID_CURSORHTTP 400
游标不属于当前列表和筛选条件。请去掉 cursor 重新开始。

返回 503 SERVICE_UNAVAILABLE 表示投递服务暂时不可用。请几秒后重试。

参考

共十个操作,全部需要 Authorization: Bearer <API_KEY>。最常用的三个完整列出;其余格式相同。

POST/webhooks

创建 Webhook

创建 Webhook 并返回签名密钥。

JSON 正文
urlstring必填: 是
您的服务器接收事件的 https:// 地址,须使用公共域名。
eventsarray必填: 是
Webhook 接收的事件类型,不可重复:从事件中的 8 种订单和退款事件里选择一个或多个。webhook.test 不能订阅:它只会通过测试发送。
enabledboolean必填: 否
Webhook 是否接收事件。默认为 true。
请求
curl --request POST "https://bytemax.exchange/api/v1/webhooks" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{"url":"https://merchant.example/webhooks/bytemax","events":["order.created","order.completed","order.expired","order.canceled","order.updated","refund.created","refund.succeeded","refund.failed"],"enabled":true}'
响应 201
{
  "id": "ep_example",
  "url": "https://merchant.example/webhooks/bytemax",
  "events": [
    "order.created",
    "order.completed",
    "order.expired",
    "order.canceled",
    "order.updated",
    "refund.created",
    "refund.succeeded",
    "refund.failed"
  ],
  "enabled": true,
  "api_version": "v1",
  "signing_secret": "<WEBHOOK_SIGNING_SECRET>"
}
POST/webhooks/{id}/test-events

发送测试

向此 Webhook 发送一个 webhook.test。正文为空的 JSON 对象。

请求参数
参数位置必填
id路径是
请求
curl --request POST "https://bytemax.exchange/api/v1/webhooks/ep_example/test-events" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{}'
响应 202
{
  "id": "evt_test_example",
  "type": "webhook.test",
  "created_at": "2026-09-10T13:05:00.000Z",
  "related_object": {
    "type": "webhook",
    "id": "ep_example",
    "url": "/api/v1/webhooks/ep_example"
  },
  "publication_status": "pending"
}
POST/webhooks/{id}/redeliveries

重发事件

向此 Webhook 重发最多 100 个事件。响应包含每个事件的结果。

请求参数
参数位置必填
id路径是
JSON 正文
event_idsarray必填: 是
要重发的事件 ID,1 到 100 个,不可重复。
请求
curl --request POST "https://bytemax.exchange/api/v1/webhooks/ep_example/redeliveries" \
  --header "Authorization: Bearer <API_KEY>" \
  --header "Content-Type: application/json" \
  --data '{"event_ids":["evt_example"]}'
响应 200
{
  "items": [
    {
      "event_id": "evt_example",
      "status": "queued"
    }
  ]
}

其他操作

操作名称作用
GET/webhooks列出 Webhook列出您账户中的所有 Webhook,包括通过旧版 API 创建的 Webhook(api_version: "legacy")。
GET/webhooks/{id}查询 Webhook单个 Webhook。带 include_signing_secret=true 时包含签名密钥。
PATCH/webhooks/{id}修改 WebhookURL、事件或启用状态。要把在旧版 API 中创建的 Webhook 迁移至 v1,请同时发送 api_version: "v1" 和新的 events。
DELETE/webhooks/{id}删除 Webhook删除该 Webhook。
GET/events列出事件最近 30 天的事件及其发布状态。
GET/events/{id}查询事件单个事件及其发布状态。
GET/webhooks/{id}/deliveries列出投递某个 Webhook 的投递尝试:您的服务器返回 2xx 时为 succeeded,没有响应或返回其他状态码时为 failed。可按状态和类型筛选。