Skip to content

Webhooks -- 概述

Webhooks 允许您的应用实时接收 Minha Konta 平台上的事件通知。当事件发生时,Minha Konta 会向您注册的 URL 发送 HTTP POST 请求。

工作原理

  1. 在您的账户中注册 Webhook URL
  2. 当事件发生时(如:收到 PIX),Minha Konta 向您的 URL 发送 HTTP POST
  3. 您的应用处理通知并返回 2xx 状态码(200、201 或 204)

可用事件

Minha Konta 投递 PIXMinha Konta 账户之间 TEF 和 webhook 操作测试事件。其他产品(boleto、账户、STA)不在范围内。任何尝试订阅下表之外事件的请求都会被拒绝,返回 events: contains invalid events: ...

事件状态 body描述触发
pix.charge.createdcreatedQR 码已生成或 cash-in 已启动激活
pix.charge.paidpaidPIX 已接收并结算激活
pix.charge.expiredexpiredQR 码在未付款情况下过期激活
pix.charge.cancelledcancelledQR 码在付款前取消已注册,尚未触发
pix.payout.queuedqueued已发送的 PIX 因运营限制进入队列。自动重试,最大 TTL 2 小时激活
pix.payout.processingprocessingPIX 已发送,等待 BACEN 确认激活
pix.payout.heldprocessing已发送的 PIX 在清算代理处被暂扣审核(授权/反欺诈)。随后会清算或拒绝——请勿重新提交激活
pix.payout.confirmedsettledPIX 已发送并确认(终止激活
pix.payout.failedrejectedPIX 已发送,被 SPI 拒绝(终止激活
pix.payout.returnedreturned已发送的 PIX 已退回激活
pix.refund.requestedrequested收到退款请求(BACEN 违规);在客户余额上创建预防性封锁激活
pix.refund.completedsettled / completed抗辩分析完成,退款已执行(或已释放)激活
pix.return.receivedsettled收到 PIX 退款(贷记)激活
pix.infraction.createdACKNOWLEDGED对方通过 BACEN DICT 报告 PIX 违规;可能需要 MED 分析和抗辩激活
pix.infraction.resolvedCLOSED / CANCELLED违规已根据最终决定或对方取消而解决激活
pix.infraction.defense_submitteddefense_submittedmerchant(门户或 API)已提交抗辩;等待 BACEN 分析激活
tef.transfer.sentsettledMinha Konta 账户间 TEF 已为来源账户结算激活
tef.transfer.receivedsettledMinha Konta 账户间 TEF 已为目标账户结算激活
tef.transfer.failedfailedMinha Konta 账户间 TEF 被拒绝或未结算激活
webhook.testtest手动测试。仅通过 Admin/Merchant portal 可用 - External API 不暴露触发测试的端点手动触发(非 External API)

pix.charge.cancelled 尚未触发

事件在 enum 中且可订阅,但系统今天没有 QR 码取消流程。如果您订阅,POST /webhooks 正常响应 201 - 但不会收到通知。继续监控 pix.charge.expired 以了解 QR 的自然生命周期。

安全性

每个通知包含用于验证的安全和识别头:

描述
X-Minha Konta-Signature载荷的 HMAC-SHA256 签名(前缀 sha256=)。在罕见情况下(注册时没有 secret 的 webhook),文字值为 unsigned - 参见下方注释
X-Minha Konta-Timestamp发送的 Unix 时间戳(秒)
X-Minha Konta-Event-Id投递的唯一 UUID(用于去重)
X-Minha Konta-Event-Type事件类型(例:pix.charge.paid
Content-Type始终为 application/json
User-Agent始终为 Minha Konta-Webhook/1.0 - 用于防火墙/WAF 白名单。未来演变将遵循 Minha Konta-Webhook/{version} 模式;如果您想对新版本免疫,按前缀 Minha Konta-Webhook/ 过滤

当 webhook 没有 secret 时签名为 unsigned

如果 webhook 在旧记录中没有 secret 字段,X-Minha Konta-Signature 可能为 unsigned。这会在您那边禁用 HMAC 验证。如果收到 unsigned,请使用显式 secret 注册新 webhook 并移除旧的。

Webhook 中的 SHA256 vs API 中的 SHA512

API 使用 HMAC-SHA512 验证您发送的请求。Minha Konta 发送的 webhook 在 X-Minha Konta-Signature 签名中使用 HMAC-SHA256。它们是不同的算法 -- 各有其上下文。

验证签名

验证签名以确保通知由 Minha Konta 发送:

javascript
const crypto = require('crypto');

function validateWebhook(rawBody, timestamp, signature, secret) {
  // rawBody is the RAW request body string (before any JSON parse)
  // timestamp is unix seconds (e.g., 1712160000)
  const message = `${timestamp}.${rawBody}`;
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(message)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

使用 RAW body,不要重新序列化

必须使用 HTTP 请求的 body,即字节到达您应用时的那一份。如果您先执行 JSON.parse 然后再执行 JSON.stringify,产生的字节不会与 Minha Konta 用于签名的字节完全一致,验证将失败。

在 Express/Node 中:使用 express.raw({ type: 'application/json' }) 或在任何 parse 中间件之前保存 body。

在其他框架中:配置在 JSON 中间件之前捕获 raw body。

WEBHOOK 中的键排序:NÃO 需要

对于 webhook 验证(HMAC-SHA256),您不需要按字母顺序排列键 - 使用从 Minha Konta HTTP 请求中接收的 raw body。

⚠️ 注意 - 与请求发送的差异:在您发送的请求的 HMAC-SHA512 签名中,按字母顺序的键是必须的(Minha Konta 服务器在验证前重新排序)。不要混淆这两种场景:

  • 收到的 Webhook(HMAC-SHA256):验证 raw body,无需重新排序
  • 发送的 Request(HMAC-SHA512):签名前按字母顺序排列您的键

始终验证

切勿在未验证签名的情况下处理 webhook。这可以防止伪造请求。

此外,验证 X-Minha Konta-Timestamp 是否在当前时间的 ± 5 分钟之内(防重放保护 - 服务器默认拒绝"旧"webhook;此检查应由您的端点作为防御深度执行)并按 X-Minha Konta-Event-Id 去重事件(针对重试的保护)。

重试策略

如果您的 URL 返回非 2xx 状态(或 30 秒后超时),Minha Konta 将执行最多 8 次重试,采用指数退避策略。第一次和第八次尝试之间的总时间约为 7h45min

尝试与上次尝试的间隔累计时间
第 1 次-(立即)~50-200 ms
第 2 次30 秒~30 s
第 3 次2 分钟~2,5 min
第 4 次10 分钟~12,5 min
第 5 次30 分钟~42,5 min
第 6 次1 小时~1,75 h
第 7 次2 小时~3,75 h
第 8 次4 小时~7,75 h

8 次尝试均失败后,webhook_delivery 被标记为 failed,不再自动重发。您可以向 Minha Konta 支持请求手动重放,提供 X-Minha Konta-Event-Id(或让有 admin 访问权限的操作员通过门户重放)。

delivery 状态

每个 delivery 经过以下状态:pending(已创建,等待投递)→ delivered(收到 2xx)或 failed(8 次尝试耗尽)或 expired(重放保护)。

关于 expired 当投递尝试在发送前已经过旧时,它可能被丢弃并标记为 expired。这可以防止延迟重处理触发旧通知。向 Minha Konta 支持请求的手动重放会走受控流程,并可向客户重新发送事件。

持久性

在尝试第一次投递之前,事件会被持久化以便重试。如果投递期间发生故障,系统会在下一次重试时自动恢复 - 不会丢失任何事件。

幂等性

您的应用应具备幂等性:如果多次收到同一事件(通过 X-Minha Konta-Event-Id 识别),应在不产生重复效果的情况下处理。

通过 admin 手动重放

如果 delivery 失败且您需要重新发送,Minha Konta 团队可以通过 admin dashboard 执行手动重放。请联系支持并提供 delivery 的 event_id

重复投递(已知的 race condition)

系统可能使用多个投递路径来加速首次通知并保持持久重试。在高并发场景中,您可能通过 HTTP 收到相同载荷 2 次,但具有相同的 X-Minha Konta-Event-Id - 是同一个事件,不是重试。

为避免重复影响:

  • X-Minha Konta-Event-Id 去重(推荐 - 每个 delivery 唯一的 UUID,在重试和上述 race condition 中稳定)
  • 或者在事件合适时按 end_to_end_id + event_type 去重

这是预期行为,不是错误。合法的重试(5xx/超时后)也重用相同的 X-Minha Konta-Event-Id

Webhook 中的 External ID

当交易创建时包含 external_id,该字段会包含在 webhook 载荷的 data 对象中。使用它将事件与您系统中的订单关联,无需额外查询。

端点要求

  • URL 必须使用 HTTPS(除非注册时设置 allow_insecure: true
  • 必须在 30 秒内返回 2xx 状态
  • 响应 body 被忽略
  • 建议快速响应(立即 200 OK)并在您这边异步处理事件;长延迟会降低吞吐量并增加重试机会

下一步

Minha Konta Instituição de Pagamento - ISPB 39929224