注册 Webhook
用于创建、列出和删除通知 webhook 的端点。
创建 Webhook
POST /api/external/webhooks请求头
| 头 | 类型 | 必填 | 描述 |
|---|---|---|---|
Authorization | String | 是 | ApiKey {client_id}:{client_secret} |
Content-Type | String | 是 | application/json |
hmac | String | 是 | body 的 HMAC-SHA512 签名(了解详情) |
请求体
| 字段 | 类型 | 必填 | 默认 | 描述 |
|---|---|---|---|---|
url | String | 是 | -- | 接收通知的 URL(默认 HTTPS) |
events | Array | 是 | -- | 要订阅的事件列表。必须是非空数组,至少包含下表中的一个有效事件。省略该字段返回 400 {"errors": {"events": ["can't be blank"]}}。 |
secret | String | 否 | 自动生成 | 用于投递的 HMAC-SHA256 签名密钥。如省略,自动生成随机值。 |
description | String | 否 | null | webhook 的自由描述,用于内部识别 |
allow_insecure | Boolean | 否 | false | 允许 HTTP(非 HTTPS)URL。数据安全由客户负责。 |
可用事件(仅 PIX — 今天其他产品不在 Owem 范围内):
| 事件 | 状态 body | 描述 | 触发 |
|---|---|---|---|
pix.charge.created | created | QR 码已生成或 cash-in 已启动 | 激活 |
pix.charge.paid | paid | PIX 已接收并结算 | 激活 |
pix.charge.expired | expired | QR 码在未付款情况下过期(worker 每 5 分钟) | 激活 |
pix.charge.cancelled | cancelled | QR 码在付款前取消 | 已注册,尚未触发 |
pix.payout.queued | queued | 已发送的 PIX 由于速率限制(每 merchant 的 ClientLimiter 或 DICT BACEN bucket)入队 | 激活 |
pix.payout.processing | processing | PIX 已发送,等待确认 | 激活 |
pix.payout.confirmed | settled | PIX 已发送并确认(终止) | 激活 |
pix.payout.failed | rejected | PIX 已发送,被拒绝(终止) | 激活 |
pix.payout.returned | returned | 已发送的 PIX 已退回 | 激活 |
pix.refund.requested | requested | 收到退款请求(BACEN 违规);在客户余额上创建预防性封锁 | 激活 |
pix.refund.completed | settled / completed | 抗辩分析完成,退款已执行(或已释放) | 激活 |
pix.return.received | settled | 收到 PIX 退款(贷记) | 激活 |
pix.infraction.created | ACKNOWLEDGED | 对方通过 BACEN DICT 报告 PIX 违规 | 激活 |
pix.infraction.resolved | CLOSED / CANCELLED | 违规已解决(admin 关闭、auto-deny 或对方取消) | 激活 |
pix.infraction.defense_submitted | defense_submitted | merchant 已提交抗辩 | 激活 |
webhook.test | test | 手动测试。仅通过 Admin/Merchant portal 可用 — External API 不提供触发端点 | 手动触发 |
pix.charge.cancelled 尚未触发
该事件存在于有效订阅枚举中,但今天生产中没有代码触发此类通知(QR 码取消流程未实现)。您可以将其包含在 events 数组中 — API 接受 — 但在流程实现之前,您的端点不会收到通知。
该表之外的任何事件都会被拒绝
创建 webhook 时,后端的 validate_events 对照枚举 @valid_events(schema Fluxiq.Schemas.Webhooks.Webhook)验证每一项。未知事件(boleto.paid、account.created、sta.file.* 等)返回 400,错误为 events: contains invalid events: ...。在 session 163 之前,这些名称作为愿望保留在枚举中 — 已移除,因为代码中没有 dispatcher。
每个事件的载荷
每个事件的完整载荷示例见 Webhook 载荷。
示例
BODY='{"url":"https://seusite.com.br/webhook","events":["pix.charge.paid","pix.payout.confirmed"]}'
HMAC=$(echo -n "$BODY" | openssl dgst -sha512 -hmac "$CLIENT_SECRET" | awk '{print $2}')
curl -X POST https://api.owem.com.br/api/external/webhooks \
-H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-H "hmac: $HMAC" \
-d "$BODY"成功响应 (201)
{
"worked": true,
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"url": "https://seusite.com.br/webhook",
"events": ["pix.charge.paid", "pix.payout.confirmed"],
"secret": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"description": null,
"is_active": true,
"created_at": "2026-03-07T15:30:00Z"
}id 的格式
webhook 的 id 是规范的 UUID v4(36 个字符,带连字符)。直接在 DELETE /api/external/webhooks/:id 中使用该值。
错误响应 (422)
{
"worked": false,
"detail": "URL deve utilizar HTTPS"
}默认仅 HTTPS
webhook URL 必须使用 HTTPS。HTTP URL 将被拒绝,除非在注册中发送 allow_insecure: true。
重要 — Webhook Secret
在注册响应中返回的 secret 字段是用于签名 webhook 投递的密钥(HMAC-SHA256)。请在收到后安全存储此值 — 它用于验证通知确实来自 Owem Pay。
不要与 client_secret 混淆:
client_secret= 您对 API 请求的身份验证(Authorization 头)- webhook
secret= 验证收到的投递的签名(X-Owem-Signature 头)
如果您不在注册中发送 secret 字段,将自动生成随机值并在响应中返回。
参见 Webhook 验证 了解如何验证签名的示例。
之后恢复 secret
secret 在 POST /api/external/webhooks(创建)以及 GET /api/external/webhooks 和 GET /api/external/webhooks/:id(查询)中都返回。如果您丢失了该值,只需通过 GET 再次查询 webhook。
在未来版本中,此行为可能受到限制(仅在创建时显示);我们建议在注册时将 secret 存储在托管的 secret 中(vault、SSM 等)。
HTTP URL
默认情况下,webhook 要求 HTTPS 以确保传输中数据的安全。要使用 HTTP,请在 webhook 注册中发送 allow_insecure: true。
注意
HTTP URL 以明文方式传输数据。传输信息的安全性和保密性完全由客户负责。Owem Pay 正常投递 webhook,但对未加密连接上的数据拦截或泄漏不承担责任。
私有 URL 始终被阻止
即使使用 allow_insecure: true,指向私有/内部地址的 URL 也会被拒绝:
localhost/127.x.x.x- RFC1918:
10.x.x.x、192.168.x.x、172.16-31.x.x - 内部 TLD:
.local、.internal
Webhook 必须指向互联网可访问的公共 URL。
列出 Webhook
GET /api/external/webhooks请求头
| 头 | 类型 | 必填 | 描述 |
|---|---|---|---|
Authorization | String | 是 | ApiKey {client_id}:{client_secret} |
示例
curl -X GET https://api.owem.com.br/api/external/webhooks \
-H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET"成功响应 (200)
返回对象数组(不包装在 {"worked": true} 中)。每项包含与注册相同的字段,包括 secret。
[
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"url": "https://seusite.com.br/webhook",
"events": ["pix.charge.paid", "pix.payout.confirmed"],
"description": null,
"account_id": 10014,
"is_active": true,
"allow_insecure": false,
"status": "active",
"secret": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"created_at": "2026-03-07T15:30:00",
"updated_at": "2026-03-07T15:30:00"
}
]| 字段 | 类型 | 描述 |
|---|---|---|
id | string (UUID) | webhook 标识符 |
url | string | 目标 URL |
events | array | 订阅的事件 |
description | string 或 null | 可选描述 |
account_id | integer 或 null | 关联的账户。null = API key 的全局 webhook(如果支持) |
is_active | boolean | false = webhook 暂停,无投递 |
allow_insecure | boolean | true = 接受 HTTP URL |
status | string | 从 is_active 派生 — "active" 或 "inactive" |
secret | string | 用于签名投递的 HMAC-SHA256 密钥。在 LIST 中返回以允许客户端在丢失原始值时恢复 |
created_at / updated_at | string ISO 8601 | UTC 时间戳,NaiveDateTime 格式(无 Z 后缀,例:"2026-03-07T15:30:00")。与 webhook 载荷中的其他日期字段(paid_at、returned_at、expired_at)不同,它们使用带 Z 后缀的 DateTime ISO 8601。始终假设 webhook 对象字段为 UTC |
删除 Webhook
DELETE /api/external/webhooks/:id请求头
| 头 | 类型 | 必填 | 描述 |
|---|---|---|---|
Authorization | String | 是 | ApiKey {client_id}:{client_secret} |
路径参数
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
id | String (UUID) | 是 | webhook ID(创建端点返回的 UUID v4) |
示例
curl -X DELETE https://api.owem.com.br/api/external/webhooks/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET"成功响应 (204)
HTTP 204 No Content — 空 body。webhook 已成功移除;不会触发待处理的投递。
第一次调用:204。后续:404
第一次成功调用返回 204 No Content。相同 id 的后续调用返回 404 { "errors": { "not_found": "webhook not found" } } — webhook 已被移除。这不是严格的 HTTP 幂等性(每次调用都返回 204)— 这是资源不再存在时 DELETE 的标准行为。请编写您的集成以同时接受 204 和 404 作为"webhook 不再激活"。
错误响应 (400)
无效的 id 格式(不是 UUID):
{
"errors": {
"bad_request": "id must be a valid UUID"
}
}错误响应 (404)
webhook 不存在或不属于您的账户:
{
"errors": {
"not_found": "webhook not found"
}
}