Webhook

Webhook 是履约的唯一可信来源。每当发生重要事件——支付、退款、订阅、结算——FastStar 会向你的回调地址 POST 带签名的 JSON 事件,即使买家从未回到你的网站,你的系统也能保持同步。

配置

在商户后台配置 Webhook:设置回调地址(endpoint)与签名密钥(secret),并可选择只订阅部分事件类型(不选则接收全部事件)。你的端点必须返回 2xx 状态码才算投递成功。

投递格式

每次投递为一个 HTTP POST,携带以下请求头:

请求头说明
X-Webhook-Signature签名:t=<unix 时间戳>,v1=<hex HMAC-SHA256>
X-Webhook-ID事件唯一 id——用于幂等去重
X-Webhook-Timestamp事件创建时间(unix 秒)

消息体为 JSON 事件包:

{
  "id": "evt_xxx",
  "type": "payment.succeeded",
  "created": 1769900000,
  "data": { "payment_id": "pi_xxx", "amount": 1999, "currency": "USD", "status": "succeeded" },
  "livemode": true,
  "api_version": "v1"
}

事件类型

事件触发时机
payment.succeeded支付成功完成——据此履约
payment.failed支付尝试失败
payment.canceled支付被取消
payment.refunded扣款被退款
refund.succeeded退款完成
subscription.created / subscription.updated / subscription.canceled订阅生命周期变化
invoice.paid / invoice.payment_failed订阅账单结果
dispute.created / dispute.updated / dispute.closed拒付 / 争议生命周期
settlement.succeeded结算(余额出款)完成

验证签名

签名头的格式为 t=<时间戳>,v1=<签名>。签名是用你的 Webhook 密钥对字符串 <时间戳>.<原始消息体> 计算的 HMAC-SHA256(hex 编码)。验签必须基于原始请求体,在任何 JSON 解析之前进行:

const crypto = require('crypto');

function verifyWebhook(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries(
    signatureHeader.split(',').map((p) => p.split('='))
  );
  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');
  return (
    parts.v1 &&
    parts.v1.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected))
  );
}

// Express:保留原始消息体用于验签
app.post('/webhooks/faststar', express.raw({ type: 'application/json' }), (req, res) => {
  if (!verifyWebhook(req.body.toString(), req.get('X-Webhook-Signature'), process.env.WEBHOOK_SECRET)) {
    return res.status(400).send('bad signature');
  }
  const event = JSON.parse(req.body);
  if (event.type === 'payment.succeeded') {
    // 履约(按 event.id 做幂等处理)
  }
  res.sendStatus(200); // 尽快确认,重活异步处理
});

建议拒绝时间戳过旧的事件以防重放。也可以通过 API 验证签名: POST /public/verify-signature{ provider_name, payload, signature }{ valid: true }),但推荐优先在本地做 HMAC 验签。

重试与幂等

  • 只有你的端点返回 2xx 才算投递成功。其他状态码或网络错误会进入重试队列。
  • 重试按递增退避执行(大致按 1 分钟、5 分钟、30 分钟、2 小时、24 小时的节奏),最多重试到你配置的次数(默认 3 次)。
  • 重试可能导致同一事件被投递多次,请幂等处理——以事件 idX-Webhook-ID)为处理键。
  • 尽快响应(先确认、再异步处理),避免超时被判定为失败。

前端事件不等于履约。checkout.success 等 SDK 回调只用于确认买家的界面体验。履约一律以 payment.succeeded Webhook 为准。