回调通知(Webhooks)
Webhooks 是我们的 API 在相关事件发生时(例如收到 PIX 或支付完成)向您注册的 URL 发送的通知。
Webhook 配置
Webhooks 必须通过 Integrator Portal 进行配置。
查询可用事件
curl -X GET "https://api.corpxapi.com/v1/webhooks/events" \
-H "Authorization: Bearer YOUR_TOKEN"
响应:
{
"eventTypes": [
"pix.in.completed",
"pix.out.completed",
"pix.out.failed",
"qrcode.paid",
"pix.refund.completed",
"pix.refund.failed",
"pix.med.opened",
"pix.med.updated",
"transfer.internal.in",
"transfer.internal.out",
"account.balance_updated",
"account.lock_created",
"account.lock_released",
"accreditation.pf.created",
"accreditation.pj.created",
"edi.batch",
"ted.payment"
]
}
接收流程
- 我们平台上发生了一个事件。
- 我们向已注册的 URL 发送
POST请求。 - 您的应用需处理该通知并返回
2xx状态码。
配置灵活性
我们的 webhook 基础设施支持多种投递方式:
- 合并:您可以在同一个 URL 接收多种事件类型(例如
pix.in.completed和pix.out.completed)。 - 分离:您可以为每种事件类型配置不同的 URL。
- 冗余:我们可以同时将同一事件发送到多个独立的 URL。
安全性(目标端认证)
创建或更新 webhook 订阅时,您可以选择我们的投递基础设施如何对发往您端点的请求进行认证。认证方式通过 API 或控制面板按订阅进行配置。
可用认证方式
| 方式 | authType 值 | 描述 |
|---|---|---|
| HMAC 签名 | HMAC | 使用 HMAC-SHA256 和您的密钥对每个请求体进行签名。签名通过 X-Signature 请求头发送。推荐使用。 |
| API Key | API_KEY | 在可配置的 HTTP 请求头中发送静态 API key(默认:X-API-Key)。 |
| Basic Auth | BASIC | 在标准 Authorization: Basic ... 请求头中发送 username:password。 |
| Bearer Token | BEARER | 在 Authorization: Bearer ... 请求头中发送 token。 |
| 无认证 | NONE | 不进行认证。不建议在生产环境使用。 |
您可以在 Integrator Portal 中创建或更新 webhook 订阅时配置认证方式。前往 Webhooks > Edit Subscription,从下拉菜单中选择您偏好的认证方式。系统将根据所选方式要求您提供所需凭据(例如 HMAC 密钥、API key 或用户名/密码)。
HMAC 签名验证
当 authType 设置为 HMAC 时,每个请求都会包含一个 X-Signature 请求头,其中包含使用您的 secret 作为密钥对原始请求体计算的 Base64 编码 HMAC-SHA256 哈希值。
公式:
expected = base64(HMAC_SHA256(your_secret, raw_request_body))
将计算出的值与 X-Signature 请求头进行比较。如果匹配,则请求是可信的。
请始终使用原始请求体字节进行验证,而非重新解析/重新序列化的版本。重新序列化 JSON 可能会改变字段顺序或空白字符,导致签名无效。
验证示例
Node.js:
const crypto = require("crypto");
function verifySignature(secret, rawBody, signatureHeader) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("base64");
return expected === signatureHeader;
}
// In your Express handler:
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const signature = req.headers["x-signature"];
if (!verifySignature(WEBHOOK_SECRET, req.body, signature)) {
return res.status(403).send("Invalid signature");
}
const event = JSON.parse(req.body);
// Process event...
res.sendStatus(200);
});
Python:
import hmac, hashlib, base64
def verify_signature(secret: str, raw_body: bytes, signature_header: str) -> bool:
expected = base64.b64encode(
hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
).decode()
return hmac.compare_digest(expected, signature_header)
Go:
func verifySignature(secret string, rawBody []byte, signatureHeader string) bool {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(rawBody)
expected := base64.StdEncoding.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signatureHeader))
}
API Key 认证
当 authType 设置为 API_KEY 时,我们会在每个请求的 HTTP 请求头中发送您配置的 API key。默认请求头名称为 X-API-Key,但您可以通过 authConfig.header 进行自定义。
# Creating a subscription with API Key auth:
curl -X POST "https://api.corpxapi.com/v1/webhooks" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-Id: your-tenant-id" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhook",
"authType": "API_KEY",
"authConfig": {
"key": "your-api-key-value",
"header": "X-API-Key"
},
"eventTypes": ["pix.in.completed"]
}'
您的服务器应验证请求头中的值是否与预期的 key 匹配。
Basic Auth
当 authType 设置为 BASIC 时,我们会在每个请求中发送 Authorization: Basic <base64(username:password)> 请求头。
{
"authType": "BASIC",
"authConfig": {
"username": "your-username",
"password": "your-password"
}
}
Bearer Token
当 authType 设置为 BEARER 时,我们会在每个请求中发送 Authorization: Bearer <token> 请求头。
{
"authType": "BEARER",
"authConfig": {
"token": "your-bearer-token"
}
}
信息性请求头
除上述认证请求头外,我们的投递基础设施还会在每个请求中添加以下信息性请求头:
| 请求头 | 描述 |
|---|---|
x-hookdeck-event-id | 投递事件 ID(用于调试和支持请求)。 |
x-hookdeck-request-id | 原始请求 ID。 |
x-hookdeck-attempt-count | 投递尝试次数(首次尝试为 1)。 |
这些请求头仅供参考,无需验证。
IP 白名单
为提高集成安全性,我们建议您的目标服务器验证传入请求的来源 IP 地址。仅接受来自我们基础设施官方 IP 的通知:
34.138.140.22334.138.161.10035.231.250.19335.196.71.2934.138.56.192
建议将这些地址添加到您防火墙或 Web 服务器的白名单中。
重试
如果您的应用返回错误(非 2xx 状态码)或发生超时,我们的系统将按照指数退避策略尝试重新发送通知:
- 尝试次数:最多 6 次。
- 间隔:逐步递增。
在所有尝试用尽后,该事件将被标记为失败。您可以使用我们 API 中的 Replay 端点请求手动重发。
重发事件(Replay)
如果您需要重发某个事件(由于处理失败或通知丢失),请使用 /v1/webhooks/replay 端点。
请求示例:
curl -X POST "https://api.corpxapi.com/v1/webhooks/replay" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-Tenant-Id: your-tenant-id" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"eventId": "evt_abc_123",
"tenantId": "your-tenant-id"
}'
API 将返回 202 Accepted 状态码,该事件将被加入队列等待新的投递尝试。
通知格式
所有通知遵循标准信封格式。每个事件的具体内容位于 data 对象中。
标准信封
{
"id": "evt_123456789",
"type": "pix.in.completed",
"occurredAt": "2025-12-29T21:14:33.912Z",
"schemaVersion": "1.0",
"environment": "production",
"accountId": "acc_123456",
"data": { }
}
data 中的通用字段
以下字段出现在表示账本流水的所有事件类型中(PIX IN/OUT、退款、QR 支付、内部转账、手续费)。它们是将 webhook 与其他 API 端点关联起来的推荐方式:
| 字段 | 说明 |
|---|---|
transactionId | 我们 API 使用格式的交易标识符。与 GET /v1/accounts/{accountId}/statement 返回的内容以及发起该交易的端点的同步响应一致。用于在我们的 API 中查询交易。 |
coreId | 银行合作方核心账本行的 UUID。与账单的 coreId 字段暴露的值相同;在 webhook 与异步账单同步之间保持稳定。用于与合作方导出数据对账。 |
endToEnd / endToEndId | 央行 PIX E2E ID(仅 PIX 事件)。 |
status | 交易状态。使用标准化词汇表(见下表)。 |
前三个字段可以在同一个载荷中共存。如果只有一个出现,则意味着另一个字段对该事件类型不相关(例如:内部转账带有 transactionId + coreId,但没有 endToEnd)。
data.status 可能的值
status 字段在所有事件中使用标准化词汇表:
| Status | 含义 | 事件 |
|---|---|---|
SUCCESS | 操作成功完成;余额已借记/贷记。 | pix.in.completed、pix.out.completed、pix.refund.completed、qrcode.paid、transfer.internal.in、transfer.internal.out、fee.charged、fee.refunded |
FAILED | 操作失败。余额未变动(或已返还)。查看 data.error 了解详情。 | pix.out.failed、pix.refund.failed |
REVERSED | 之前已完成的 PIX IN 被撤销(例如完整退款)。当延迟对账覆盖已交付的 PIX IN 时出现。 | pix.in.completed(罕见,对账后) |
MED(争议)事件的 status 使用自己的词汇表:
| Status | 含义 |
|---|---|
OPEN | 由索赔方开启的争议,等待响应。 |
PENDING_DECISION | 已提交响应,等待央行/监管方决定。 |
ACCEPTED | 争议被接受 — 资金已返还(全部或部分)。 |
REJECTED | 争议被拒绝 — 资金保留在收款方。 |
CANCELED | 由索赔方或监管方取消的争议。 |
事件:pix.med.opened、pix.med.updated。
status 保证对于表示账本流水的事件,status 始终存在。如果您收到的 webhook 中缺少此字段(或为空值),则它是纯信息性事件(例如 account.balance_updated)——此时请使用 type 字段本身来确定语义。
日期与时间格式
API 和 webhook 返回的所有日期/时间字段均遵循 ISO 8601 / RFC 3339 标准,使用 UTC 时区并带 Z 后缀。 这适用于信封字段(occurredAt)以及 data 内部的所有字段(receivedAt、completedAt、initiatedAt、chargedAt、openedAt 等)——同时也适用于 REST 端点(/statement、/balance、/payments 等)返回的 createdAt/updatedAt。
示例:
2026-04-29T23:53:55.001Z ← 毫秒精度
2026-04-29T23:53:49.328720Z ← 微秒精度
2026-04-30T18:04:36Z ← 不带小数秒
我们绝不输出巴西本地时间(BRT)或不带时区标识的"裸"时间戳。如果您收到不带 Z 后缀的日期字段,请视为缺陷并通知我们 —— 一经定位即会规范化。
事件类型与内容(data)
1. pix.in.completed
当 PIX 成功入账到账户时发送。
data 结构:
endToEnd:中央银行的唯一交易 ID。amount:交易金额。currency:币种(例如 "BRL")。payer:付款方详情(姓名、证件、银行、支行、账户)。payee:收款方详情(姓名、证件、银行、支行、账户)。reconciliationId:银行合作方的对账标识符(如可用)。receivedAt:收款时间戳。
完整示例:
{
"id": "evt_123456789",
"type": "pix.in.completed",
"occurredAt": "2025-12-29T21:14:33.912Z",
"schemaVersion": "1.0",
"environment": "production",
"accountId": "acc_123456",
"data": {
"endToEnd": "E0000000020251229211433912",
"amount": 150.50,
"currency": "BRL",
"reconciliationId": "FAlGf89fN0ol41Wyc5zVQ0k5KD",
"payer": {
"name": "John Smith",
"document": "12345678900",
"bankCode": "001",
"branch": "0001",
"accountNumber": "12345-6"
},
"payee": {
"name": "Test Company",
"document": "12345678000199",
"bankCode": "999",
"branch": "0001",
"accountNumber": "98765-4"
},
"receivedAt": "2025-12-29T21:14:33.900Z"
}
}
2. qrcode.paid
当您生成的 QR Code 被支付时发送。
data 结构:
endToEnd:中央银行的唯一交易 ID。type:QR Code 类型(static或dynamic)。identifier:QR Code 的 txid(标识符)。qrcodeId:内部 QR Code ID。amount:支付金额。currency:币种。payer:付款方详情。payee:收款方详情。reconciliationId:银行合作方的对账标识符(如可用)。receivedAt:收款时间戳。
完整示例:
{
"id": "evt_987654321",
"type": "qrcode.paid",
"occurredAt": "2025-12-29T21:15:00.000Z",
"schemaVersion": "1.0",
"environment": "production",
"accountId": "acc_123456",
"data": {
"endToEnd": "E0000000020251229211500000",
"type": "dynamic",
"identifier": "txid-qr-123",
"qrcodeId": "qr_abc123",
"amount": 250.00,
"currency": "BRL",
"reconciliationId": "FAlGf89fN0ol41Wyc5zVQ0k5KD",
"payer": {
"name": "Maria Oliveira",
"document": "98765432100",
"bankCode": "033",
"branch": "0001",
"accountNumber": "54321-0"
},
"payee": {
"name": "Your Company",
"document": "12345678000199",
"bankCode": "999",
"branch": "0001",
"accountNumber": "98765-4"
},
"receivedAt": "2025-12-29T21:14:33.900Z"
}
}
3. pix.out.completed
当出账 PIX 转账成功完成时发送。
data 结构:
endToEnd:端到端 ID。key:使用的目标密钥。identifier:交易标识符。amount:转账金额。payer:您的账户详情。payee:目标账户详情。reconciliationId:银行合作方的对账标识符(如可用)。initiatedAt:转账发起时间。completedAt:确认完成时间。
完整示例:
{
"id": "evt_out_123",
"type": "pix.out.completed",
"occurredAt": "2025-12-29T21:14:53.900Z",
"schemaVersion": "1.0",
"environment": "production",
"accountId": "acc_123456",
"data": {
"endToEnd": "E9999999920251229211433900",
"key": {
"type": "CPF",
"key": "12345678900"
},
"identifier": "transfer-001",
"amount": 50.00,
"currency": "BRL",
"reconciliationId": "FAlGf89fN0ol41Wyc5zVQ0k5KD",
"payer": {
"name": "Your Company",
"document": "12345678000199",
"bankCode": "999",
"branch": "0001",
"accountNumber": "98765-4"
},
"payee": {
"name": "John Smith",
"document": "12345678900",
"bankCode": "001",
"branch": "0001",
"accountNumber": "12345-6"
},
"initiatedAt": "2025-12-29T21:14:33.900Z",
"completedAt": "2025-12-29T21:14:53.900Z"
}
}
4. pix.out.failed
当出账 PIX 转账失败时发送。
data 结构:
endToEnd:端到端 ID(如可用)。error:错误描述/失败原因。key:目标密钥。identifier:交易标识符。amount:金额。payer:您的账户详情。payee:目标账户详情。reconciliationId:银行合作方的对账标识符(如可用)。initiatedAt:发起时间戳。
完整示例:
{
"id": "evt_out_err_123",
"type": "pix.out.failed",
"occurredAt": "2025-12-29T21:14:35.000Z",
"schemaVersion": "1.0",
"environment": "production",
"accountId": "acc_123456",
"data": {
"endToEnd": "E9999999920251229211433900",
"error": "Insufficient balance in destination account or invalid key",
"key": {
"type": "EMAIL",
"key": "teste@exemplo.com"
},
"identifier": "transfer-002",
"amount": 1000.00,
"currency": "BRL",
"payer": {
"name": "Your Company",
"document": "12345678000199",
"bankCode": "999",
"branch": "0001",
"accountNumber": "98765-4"
},
"payee": {
"name": "Failed Recipient",
"document": "00000000000",
"bankCode": "001",
"branch": "0001",
"accountNumber": "00000-0"
},
"initiatedAt": "2025-12-29T21:14:33.900Z"
}
}
5. payment.sent (已弃用)
此事件将在未来版本中移除。请改用带有 method(PAYMENT)字段和 payload 中 payment 对象的 pix.out.completed。
当支付指令(PIX Out)被银行合作方确认并与对账单中的交易对账后发送。
data 结构:
paymentId:支付指令标识符。endToEnd:中央银行的端到端交易 ID。identifier:集成方创建支付时提供的标识符。amount:支付金额。currency:币种(例如 "BRL")。status:支付状态(COMPLETED)。key:目标 PIX 密钥(类型和值)。payer:付款方详情(姓名、证件、银行、支行、账户)。payee:收款方详情(姓名、证件、银行、支行、账户、PIX key)。initiatedAt:支付指令创建时间戳。completedAt:确认时间戳。
完整示例:
{
"id": "evt_pay_123",
"type": "payment.sent",
"occurredAt": "2026-02-13T15:50:00.000Z",
"schemaVersion": "1.0",
"environment": "production",
"accountId": "acc_123456",
"data": {
"paymentId": "pay_abc123",
"endToEnd": "E3674167520260213154900123456789",
"identifier": "order-12345",
"amount": 150.50,
"currency": "BRL",
"status": "COMPLETED",
"key": {
"type": "CPF",
"key": "12345678900"
},
"payer": {
"name": "Your Company",
"document": "12345678000199",
"bankCode": "999",
"branch": "0001",
"account": "98765-4"
},
"payee": {
"name": "John Smith",
"document": "12345678900",
"bankCode": "001",
"branch": "0001",
"account": "12345-6",
"pixKey": "12345678900"
},
"initiatedAt": "2026-02-13T15:49:30.000Z",
"completedAt": "2026-02-13T15:50:00.000Z"
}
}
6. payment.refunded (已弃用)
此事件将在未来版本中移除。请改用带有 method: REFUND_RECEIVED 的 pix.in.completed。
当您发送的支付(PIX Out)被收款方退回时发送。
data 结构:
paymentId:原始支付指令标识符。originalEndToEnd:原始支付的端到端 ID。refundEndToEnd:退款的端到端 ID(D 码)。identifier:集成方提供的标识符。originalAmount:原始支付金额。refundAmount:退款金额。currency:币种。status:状态(REFUNDED)。initiatedAt:原始支付时间戳。refundedAt:退款时间戳。
完整示例:
{
"id": "evt_pay_ref_123",
"type": "payment.refunded",
"occurredAt": "2026-02-13T16:00:00.000Z",
"schemaVersion": "1.0",
"environment": "production",
"accountId": "acc_123456",
"data": {
"paymentId": "pay_abc123",
"originalEndToEnd": "E3674167520260213154900123456789",
"refundEndToEnd": "D3674167520260213160000987654321",
"identifier": "order-12345",
"originalAmount": 150.50,
"refundAmount": 150.50,
"currency": "BRL",
"status": "REFUNDED",
"initiatedAt": "2026-02-13T15:49:30.000Z",
"refundedAt": "2026-02-13T16:00:00.000Z"
}
}
7. pix.refund.completed
当 PIX 退款完成时发送。
data 结构:
originalEndToEnd:被退款的原始交易 ID。refundEndToEnd:新退款 PIX 的 ID。identifier:退款标识符。amount:退款金额。payer:您的账户详情(发起退款方)。payee:目标账户详情(接收退款方)。reconciliationId:银行合作方的对账标识符(如可用)。initiatedAt:发起时间戳。completedAt:完成时间戳。
完整示例:
{
"id": "evt_ref_123",
"type": "pix.refund.completed",
"occurredAt": "2025-12-29T21:14:53.900Z",
"schemaVersion": "1.0",
"environment": "production",
"accountId": "acc_123456",
"data": {
"originalEndToEnd": "E0000000020251229211433912",
"refundEndToEnd": "D0000000020251229211453900",
"identifier": "refund-999",
"amount": 150.50,
"currency": "BRL",
"payer": {
"name": "Your Company",
"document": "12345678000199",
"bankCode": "999",
"branch": "0001",
"accountNumber": "98765-4"
},
"payee": {
"name": "John Smith",
"document": "12345678900",
"bankCode": "001",
"branch": "0001",
"accountNumber": "12345-6"
},
"initiatedAt": "2025-12-29T21:14:33.900Z",
"completedAt": "2025-12-29T21:14:53.900Z"
}
}
8. pix.refund.failed
当退款请求失败时发送。
data 结构:
originalEndToEnd:原始交易 ID。error:失败原因。identifier:标识符。amount:金额。payer:您的账户详情。payee:目标账户详情。initiatedAt:发起时间戳。
完整示例:
{
"id": "evt_ref_err_123",
"type": "pix.refund.failed",
"occurredAt": "2025-12-29T21:14:35.000Z",
"schemaVersion": "1.0",
"environment": "production",
"accountId": "acc_123456",
"data": {
"originalEndToEnd": "E0000000020251229211433912",
"error": "Original transaction has already been refunded",
"identifier": "refund-998",
"amount": 150.50,
"currency": "BRL",
"payer": {
"name": "Your Company",
"document": "12345678000199",
"bankCode": "999",
"branch": "0001",
"accountNumber": "98765-4"
},
"payee": {
"name": "John Smith",
"document": "12345678900",
"bankCode": "001",
"branch": "0001",
"accountNumber": "12345-6"
},
"initiatedAt": "2025-12-29T21:14:33.900Z"
}
}
9. fee.charged
当账户被收取银行费用时发送(例如按笔收取的 PIX 手续费)。
data 结构:
amount:收取的费用金额。currency:币种。feeServiceType:触发收费的操作类型(例如PIX_OUT、PIX_IN)。description:费用描述。chargedAt:收费时间戳。
完整示例:
{
"id": "evt_fee_123",
"type": "fee.charged",
"occurredAt": "2026-02-14T20:30:43.000Z",
"schemaVersion": "1.0",
"environment": "production",
"accountId": "acc_123456",
"data": {
"amount": 2.99,
"currency": "BRL",
"feeServiceType": "PIX_OUT",
"description": "Banking Fee",
"chargedAt": "2026-02-14T20:30:43.000Z"
}
}
10. pix.med.opened
当新的 MED(特别退回机制)争议针对某笔交易发起时发送。
data 结构:
endToEndId:争议中的交易 ID。identifier:关联标识符。qrcodeId:QR Code ID(如适用)。amount:争议金额。currency:币种。claimant:提出索赔的人员详情(姓名、证件、银行、支行、账户)。openedAt:MED 发起日期。deadlineAt:响应截止日期。
完整示例:
{
"id": "evt_med_123",
"type": "pix.med.opened",
"occurredAt": "2025-12-29T21:14:33.912Z",
"schemaVersion": "1.0",
"environment": "production",
"accountId": "acc_123456",
"data": {
"endToEndId": "E0000000020251229211433912",
"identifier": "tx-med-001",
"qrcodeId": "qr_abc123",
"amount": 150.50,
"currency": "BRL",
"claimant": {
"name": "John Smith",
"document": "12345678900",
"bankCode": "001",
"branch": "0001",
"accountNumber": "12345-6"
},
"openedAt": "2025-12-29T21:14:33.900Z",
"deadlineAt": "2026-01-05T21:14:33.900Z"
}
}
10. pix.med.updated
当 MED(特别退回机制)争议更新(状态变更、新信息、解决方案)时发送。
data 结构:
endToEndId:争议中的交易 ID。identifier:关联标识符。qrcodeId:QR Code ID(如适用)。amount:争议金额。currency:币种。status:当前 MED 状态(OPEN、CLOSED、AGREED、DISAGREED)。result:争议结果(如已解决)。claimant:提出索赔的人员详情。openedAt:MED 发起日期。updatedAt:最后更新日期。
完整示例:
{
"id": "evt_med_456",
"type": "pix.med.updated",
"occurredAt": "2025-12-30T15:20:00.000Z",
"schemaVersion": "1.0",
"environment": "production",
"accountId": "acc_123456",
"data": {
"endToEndId": "E0000000020251229211433912",
"identifier": "tx-med-001",
"qrcodeId": "qr_abc123",
"amount": 150.50,
"currency": "BRL",
"status": "CLOSED",
"result": "AGREED",
"claimant": {
"name": "John Smith",
"document": "12345678900",
"bankCode": "001",
"branch": "0001",
"accountNumber": "12345-6"
},
"openedAt": "2025-12-29T21:14:33.900Z",
"updatedAt": "2025-12-30T15:20:00.000Z"
}
}
最佳实践
- 幂等性:您的应用应做好多次接收同一 webhook 的准备。使用信封中的
id避免重复处理。 - 快速响应:收到 webhook 后立即返回
200 OK状态码,并异步处理业务逻辑以避免超时。 - 时间戳验证:检查
occurredAt是否过旧(建议 5 分钟容差),以防止重放攻击。