即将上线

Webhook

资金一有动静,马上知道。

Webhook 会在事件发生时主动推送到您的服务器,无需轮询。每个事件都经过签名,在您确认收到之前会持续重试,并且可以安全地重复处理。

◷ 沙盒将先向候补名单中的开发者开放。TujuPay 获得牌照后,才会提供正式密钥。

设置接收端点

  1. 1

    添加 URL

    在商家后台打开“开发者”,再进入“Webhook”,添加您服务器上的公开 HTTPS URL。

  2. 2

    选择事件

    选择您需要的事件,或在开发阶段订阅所有事件。

  3. 3

    复制签名密钥

    每个端点都有独立的密钥,以 whsec_ 开头。请像保管密码一样妥善保存。

验证签名

每个请求都带有 TujuPay-Signature 请求头,其中包含时间戳,以及对时间戳和原始请求体计算的 HMAC-SHA256 签名。请用您的密钥计算同样的签名并进行比对。拒绝超过五分钟的事件,以防重放攻击。

请求头格式
TujuPay-Signature: t=1791536400,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
Node.js
import crypto from "node:crypto";

export function verifyWebhook(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const age = Date.now() / 1000 - Number(parts.t);
  if (age > 300) throw new Error("Event too old");

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${parts.t}.${rawBody}`)
    .digest("hex");

  const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
  if (!ok) throw new Error("Bad signature");
  return JSON.parse(rawBody);
}
PHP
<?php
function verify_webhook(string $rawBody, string $header, string $secret): array {
    parse_str(str_replace(',', '&', $header), $parts);
    if (time() - (int) $parts['t'] > 300) {
        throw new Exception('Event too old');
    }
    $expected = hash_hmac('sha256', $parts['t'] . '.' . $rawBody, $secret);
    if (!hash_equals($expected, $parts['v1'])) {
        throw new Exception('Bad signature');
    }
    return json_decode($rawBody, true);
}

重试机制

如果您的服务器未在 10 秒内返回 2xx,我们会以逐渐拉长的间隔重试,最长持续三天。您也可以在商家后台重新发送任何事件。

  1. 11 分钟
  2. 25 分钟
  3. 330 分钟
  4. 42 小时
  5. 56 小时
  6. 6之后每 12 小时一次,最长 3 天

事件

事件发送时机
payment.succeeded顾客已付款,且款项已确认
payment.failed银行拒绝付款,或顾客放弃结账
payment.expired付款未在 30 分钟内完成
refund.succeeded退款已到达顾客账户
refund.failed退款无法完成
payout.scheduled今日结算金额已算出,即将发出
payout.paid结算款项已转入您的银行
payout.held结算被暂扣,事件中会说明原因

最佳实践

  • 立即返回 200,耗时的工作(例如发送邮件)放到后台任务中处理。
  • 保存每个事件 ID,跳过已处理过的事件。重试可能导致同一事件送达两次。
  • 务必验证签名。切勿信任重定向 URL 中的金额或状态。
  • 如需最新状态,请通过 API 查询付款;事件到达的顺序可能不一致。