Sign in
简介/Webhook 通知

Webhook 通知

通过 HMAC 签名的 webhook 实时接收支付与提现状态更新。

每当支付状态变更时,2328.io 会向您配置的 url_callback 发送 webhook。这是获取支付状态通知的推荐方式。

请求格式

  • 方法: POST
  • Content-Type: application/json
  • 签名: 请求体中的 sign 字段

载荷

webhook 请求体遵循 /v1/payment/info 响应格式,并额外包含 tx_explorer_url 和用于签名校验的 sign 字段。

支付成功

JSON
{
  "uuid": "db17d490-15b6-47b9-9015-91d1d8b119f2",
  "order_id": "ORDER-12345",
  "amount": "180.00000000",
  "currency": "RUB",
  "url": "https://go.2328.io/db17d490-15b6-47b9-9015-91d1d8b119f2",
  "expires_at": "2026-05-09T16:56:58+03:00",
  "created_at": "2026-05-09T15:56:58+03:00",
  "payer_currency": "TON",
  "payer_amount": "0.95256917",
  "network": "TON",
  "address": "UQA0RevhkCQx-EltyNgPPeG8dqtnCz7ZslOzMdNQlLxVaNBb",
  "payment_status": "paid",
  "txid": "41c2a327323480af8e705d05deb09c238a41779928832abef4bb77c862357b11",
  "tx_explorer_url": "https://tonviewer.com/transaction/41c2a327323480af8e705d05deb09c238a41779928832abef4bb77c862357b11",
  "payment_amount": "0.95256917",
  "merchant_amount": "0.949711462490000000",
  "amount_usd": "2.41324380",
  "exchange_rate": "0.01340691",
  "sign": "6f8c15b6e53b506d5bfa38ed3fb3b50697af73434262153c02e412541372f04d"
}

已取消 / 失败的支付

未到达终态 paid 时,txidpayment_amountmerchant_amountnull

JSON
{
  "uuid": "48edaf2d-2c49-4638-8f86-88636f661c1f",
  "order_id": "ORDER-12345",
  "amount": "2800.00000000",
  "currency": "RUB",
  "url": "https://go.2328.io/48edaf2d-2c49-4638-8f86-88636f661c1f",
  "expires_at": "2026-05-09T06:19:04+03:00",
  "created_at": "2026-05-09T05:19:04+03:00",
  "payer_currency": "ETH",
  "payer_amount": "0.01620968",
  "network": "ETH-ERC20",
  "address": "0x37c20d6d96d130Bc5B33D832e43b8e16aACe0c59",
  "payment_status": "cancel",
  "txid": null,
  "tx_explorer_url": null,
  "payment_amount": null,
  "merchant_amount": null,
  "amount_usd": "37.53934800",
  "exchange_rate": "0.01340691",
  "sign": "40ce68ad9691ad54e684329d75ab5adaf5b01409a2d18d3e0110b8c1be605342"
}

字段说明

字段类型说明
uuidstring支付 UUID
order_idstring您的订单 ID
amountdecimal (8 位小数)currency 对应的法币金额
currencystring商户请求的法币币种
urlstring托管支付页面 URL
expires_atstring (ISO 8601)支付会话的过期时间
created_atstring (ISO 8601)支付会话的创建时间
payer_currencystring付款人使用的加密货币
payer_amountdecimal (8 位小数)预期支付的加密货币数量
networkstring区块链网络
addressstring收款地址
payment_statusstring取值之一:pendingcheckpaidunderpaid_checkunderpaidoverpaidcancelaml_lock(详见 References
txidstring | null链上交易哈希,仅在支付确认后才有值
tx_explorer_urlstring | null区块链浏览器中的交易链接。若没有 txid 或该转账为内部 P2P,则为 null
payment_amountdecimal | null实际支付金额,仅在支付到账后才有值
merchant_amountdecimal (18 位小数) | null扣除手续费后入账给商户的金额
amount_usddecimal (8 位小数)创建时的美元等值
exchange_ratedecimal创建时使用的加密货币 / 法币汇率
signstring (hex)载荷的 HMAC-SHA256 签名

验证签名

验证 webhook 签名的步骤:

  1. 从载荷中提取 sign 字段
  2. 从对象中移除 sign 字段
  3. 将剩余字段编码为 JSON
  4. 将 JSON 进行 Base64 编码
  5. 使用您的 API_KEY 对 Base64 字符串计算 HMAC-SHA256
  6. 使用恒定时间比较函数(如 hash_equals)将计算结果与收到的 sign 比较
PHP
<?php
function verifyWebhookSign(array $data, string $apiKey): bool {
    $receivedSign = $data['sign'] ?? '';
    unset($data['sign']);

    $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $base64 = base64_encode($json);
    $calculated = hash_hmac('sha256', $base64, $apiKey);

    return hash_equals($calculated, $receivedSign);
}

$apiKey = 'YOUR_API_KEY';
$payload = json_decode(file_get_contents('php://input'), true);

if (!verifyWebhookSign($payload, $apiKey)) {
    http_response_code(401);
    exit;
}

switch ($payload['payment_status']) {
    case 'paid':
    case 'overpaid':
        // Credit the order — check idempotency by order_id first
        break;
    case 'underpaid_check':
    case 'underpaid':
    case 'cancel':
        break;
}

http_response_code(200);

为用户入账资金前务必校验签名。 未签名或签名错误的 webhook 可能是伪造请求。

提现 webhook

当提现的 status 发生变化时,系统会向创建提现时传入的 url_callback 发送 POST webhook。如果未提供 url_callback,则不会为该提现发送任何 webhook。

提现 webhook 必须使用 Payout API key 校验签名,不是普通 API key。签名算法与支付 webhook 完全相同(移除 sign → JSON → Base64 → HMAC-SHA256),仅签名密钥不同。

载荷

JSON
{
  "uuid": "019dff1f-0dbd-7277-8d45-271e7775388f",
  "order_id": "4dfdcc84402b1185b71cbe399321533e",
  "status": "completed",
  "currency": "TRX",
  "network": "TRX-TRC20",
  "amount": "3.00",
  "merchant_amount": "3.00",
  "network_amount": "3.00",
  "amount_usd": "1.04",
  "to_address": "THauRv5tcucQRohXg8NiyGTk16DX1XQG5x",
  "memo": null,
  "txid": "9242e533703704ef3eaba840f70b4a26333e72c943377ee375fea17badb53def",
  "tx_explorer_url": "https://tronscan.org/#/transaction/9242e533703704ef3eaba840f70b4a26333e72c943377ee375fea17badb53def",
  "block_number": null,
  "error_type": null,
  "created_at": "2026-05-07T00:08:38+03:00",
  "updated_at": "2026-05-07T00:08:54+03:00",
  "from_currency": "USDT",
  "debited_amount": "1.050735",
  "debited_currency": "USDT",
  "sign": "925ad7bf3d6841864101f7cc2c7e30652e70a06cdb04dbe07a0129480000ce4a"
}

字段说明

字段类型说明
uuidstring提现 UUID
order_idstring您提供的幂等性 / 业务 ID(如有)
statusstringpendingcompletedfailedcancelled(详见 References
currencystring提现币种
networkstring区块链网络
amountdecimal提现金额(以 currency 为单位)
merchant_amountdecimal从商户余额扣除的金额
network_amountdecimal实际链上发送的金额
amount_usddecimal提现时的美元等值
to_addressstring收款方区块链地址
memostring | null备注 / destination tag(如使用)
txidstring | null链上交易哈希,completed 时设置
tx_explorer_urlstring | null区块链浏览器中的交易链接。若没有 txid 或该转账为内部 P2P,则为 null
block_numberinteger | null链上交易所在区块高度
error_typestring | nullstatus = failed 时的失败原因(例如 aml_risk,详见 References
created_atstring (ISO 8601)提现创建时间
updated_atstring (ISO 8601)状态最近一次变更时间
from_currencystring使用自动兑换时,提现实际扣款的源余额(例如 BTC 提现的 USDT
debited_amountdecimalfrom_currency 余额扣除的金额
debited_currencystring扣款币种
signstring (hex)载荷的 HMAC-SHA256 签名,使用 Payout API key 计算

最佳实践

  • 幂等性 — 务必检查支付是否已被处理(按 order_iduuid)。webhook 可能被多次发送。
  • 快速响应 — 尽快返回 HTTP 200。耗时操作转交后台队列。
  • 重试 — 若系统未收到 HTTP 200,2 分钟后会重新发送,最多重试 5 次。
  • 异步处理 — 异步处理 webhook 事件,避免阻塞响应。
  • 安全 — 在信任载荷之前务必验证 sign 签名。

webhook 可能乱序到达。不要假设第一条 webhook 即是最终状态 — 如需确认,可通过 /v1/payment/info(或 /v1/payout/status/{uuid})重新拉取。

交货加工合同

在 webhook 端点内使用以下顺序:

  1. 读取请求正文而不记录机密或完整签名。
  2. 确定它是支付/静态钱包事件还是支付事件,以便您选择正确的 API 密钥。
  3. 删除 sign,重现记录的 JSON 字节,计算 HMAC-SHA256,并在恒定时间内进行比较。
  4. 验证所需的标识符、十进制字符串和状态值。
  5. 原子插入 inbox/idempotency 记录。如果已经存在,则返回 HTTP 200,而不会产生重复的副作用。
  6. 提交订单/账本变更并将非关键电子邮件、分析或通知排队。
  7. 快速返回 HTTP 200。

持有 idempotency 交易时请勿调用慢速第三方服务。提交之后但返回之前的 timeout 可能会导致 retry;副本必须遵守提交的 inbox 密钥并成为空操作。

活动主要身份注释
付款环节uuid + 状态/版本证据同一个 invoice 可以发出多个合法的状态更改。
部分付款充值invoice uuid + txid多笔转账可属于同一欠缴 invoice。
静态钱包存款网络+txidorder_id 被该钱包的每笔存款重复使用。
支出支付 uuid + 状态切勿从 webhook retry 逻辑创建第二个支出。

如果您的架构没有事件 ID,请将经过验证的 payload 哈希存储为附加审核证据,但不要用时间戳替换上面的业务标识。

订购和 reconciliation

至少传递一次,并且状态消息可能会出现争用。实施单调的业务规则而不是“最后一个请求获胜”:

  • 切勿因为较旧的事件迟到而将已履行的订单移回 check
  • 允许 underpaid_check 接收额外的 txid,而无需重复之前的积分;
  • paidoverpaid 视为成功的 settlement 状态,同时保留其不同的金额;
  • 保留underpaid作为最终的部分支付结果,除非权威API稍后报告另一个状态;
  • 路由 aml_lock 进行审核,不要让通用的 retry 工作人员完成它;
  • 当过渡不可能、缺少上下文或财务上不明确时,查询付款/支出信息。

即使 webhook 交付看起来正常,也运行计划的 reconciliation。将您的本地终端状态和贷记金额与 /v1/payment/info/v1/static-wallet/transactions/v1/payout/status/{uuid} 进行比较,并对差异发出警报,而不是默默地覆盖账本历史记录。

Webhook 端点安全

  • 需要 HTTPS 并保持回调可公开访问;私有/环回回调目标在付款创建期间被拒绝。
  • 强制执行较小的请求正文限制和 JSON 内容类型。
  • 在昂贵的工作之前进行速率限制,但为合法的突发和 retries 留出足够的空间。
  • 切勿仅通过源 IP 授权 webhook。网络许可名单是纵深防御; HMAC 验证是强制性的。
  • 编辑 sign、API 密钥、策略需要时的地址以及应用程序日志中的个人元数据。
  • 在受控轮换窗口期间保持当前和明确计划的替换密钥可用;永远不要猜测哪个密钥签署了事件。
  • 返回无效签名的通用错误主体,以便端点不会成为密钥或帐户预言机。