Webhooks 与 IPN / APN
SOAMTAR 通过 webhooks 实时通知您的服务器,也称为 IPN(即时付款通知) 或 APN(自动付款通知)。
- IPN:付款一经确认即发送的即时通知(例如
payment.success)。 - APN:针对次要状态变化和生命周期事件的自动通知(例如
payment.expired)。
投递经过签名(HMAC-SHA256),按事件保证幂等,按退避策略重试,且密钥可在不中断服务的情况下轮换。
事件
| 事件 | 触发时机 |
|---|---|
payment.success | 塞拉利昂 Orange Money 确认了付款(客户已输入 PIN) |
payment.failed | Orange Money 拒绝付款、返回错误或超时 |
payment.cancelled | 付款人取消 |
payment.expired | 付款时限已过且未完成 |
payment.refunded | 退款累计已覆盖该付款全额(data.refund = 该退款) |
payment.partially_refunded | 一笔部分退款已完成(data.refund = 该退款) |
payout.success / payout.failed / payout.cancelled | 向 Orange Money 钱包出款的结果 |
refund.success / refund.failed | 退款结果(data = refund 对象 + transaction_reference、merchant_reference、is_test) |
payout_request.submitted / payout_request.approved / payout_request.rejected / payout_request.completed / payout_request.failed | 余额提现申请的生命周期 |
您可以选择每个端点订阅的事件(控制台 → Webhooks)。
每笔退款都会产生各自的一组事件:refund.success + payment.refunded / payment.partially_refunded,或 refund.failed。参见退款(英文)。
webhook.test 事件(由控制台的测试按钮和 POST /v1/webhooks/test 发送)无论订阅设置如何都会投递到您所有有效的端点;该事件不可订阅。
配置
控制台 → 开发者 → Webhooks → Add endpoint
- URL:必须为公开的 HTTPS。
- 事件:上表的子集。
- 密钥:随机生成。请保存在您的服务器上。
连续多次失败后(内部阈值),端点会被自动停用。
按交易设置 callback_url
创建付款(POST /v1/payments)或出款(POST /v1/payouts)时,可传入可选的 HTTPS callback_url。SOAMTAR 会将该交易的所有事件发送到此 URL,使用与您的端点相同的 payload、相同的 X-Soamtar-Signature 签名和相同的重试策略。
- 签名使用同一环境(sandbox 或 live)中您最早创建的有效 webhook 端点的密钥。因此您必须至少注册一个端点;若没有任何端点,则不会调用
callback_url。 - 如果
callback_url与某个已注册端点的 URL 相同,事件不会重复发送。
Webhook 内容
发送到您 URL 的 JSON 请求体(默认格式):
{
"event": "payment.success",
"created": "2026-10-21T09:41:12+00:00",
"data": {
"id": "0e85b3c2-7c4f-4f1d-9b3a-1c8a2d4e5f6a",
"reference": "TXN-1A2B3C4D-ABCDEF123456",
"merchant_reference": "ORDER-001",
"provider_reference": "CI190304.1139.A01071",
"amount": "150.00",
"fee_amount": "2.25",
"net_amount": "147.75",
"currency": "SLE",
"status": "success",
"is_test": false,
"paid_at": "2026-10-21T09:41:10+00:00",
"metadata": { "cart_id": "abc-123" }
}
}data.id 是 SOAMTAR 交易 id;data.merchant_reference 是您发送的 merchant_ref。
签名
每个请求都带有以下请求头:
X-Soamtar-Signature: t=<unix>,v1=<hex_hmac_sha256>在密钥轮换期间,两个签名可能同时存在:
X-Soamtar-Signature: t=<unix>,v1=<hmac_current_secret>,v2=<hmac_next_secret>验证
- 解析时间戳
t和签名v1(如有,还有v2)。 - 检查
t是否足够新(在容差窗口内)。 - 重新计算
expected = HMAC-SHA256(t + "." + raw_body, secret)。 - 以恒定时间比较(PHP 中用
hash_equals,Node 中用crypto.timingSafeEqual)。 - 如存在
v2,则 v1 或 v2 任一匹配即接受。
$header = $_SERVER['HTTP_X_SOAMTAR_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
$secrets = [getenv('SOAMTAR_WEBHOOK_SECRET'), getenv('SOAMTAR_WEBHOOK_SECRET_NEXT')];
if (! preg_match('/t=(\d+)/', $header, $tm)) throw new RuntimeException('bad sig');
$ts = (int) $tm[1];
if (abs(time() - $ts) > 300) throw new RuntimeException('expired');
$ok = false;
foreach (['v1', 'v2'] as $i => $version) {
if (! preg_match("/{$version}=([a-f0-9]{64})/", $header, $vm)) continue;
$secret = $secrets[$i] ?? null;
if ($secret === null || $secret === '') continue;
if (hash_equals(hash_hmac('sha256', "{$ts}.{$body}", $secret), $vm[1])) {
$ok = true; break;
}
}
if (! $ok) throw new RuntimeException('invalid');容差
示例使用 300 秒,这是一个合理的上限,与 SOAMTAR 采用的容差一致。
其他请求头
| 请求头 | 含义 |
|---|---|
X-Soamtar-Timestamp | 签名时间戳(与 t 的值相同) |
X-Soamtar-Event | 事件类型(payment.success, ...) |
X-Soamtar-Delivery | 投递尝试标识,便于记录日志 |
User-Agent | Soamtar-Webhooks/1.0 (+https://app.so-amtar.com/docs/webhooks) |
服务器端的幂等处理
同一事件可能多次到达(超时后重试、事故后重放)。请按 (event, data.id) 组合去重,并忽略重复事件。
CREATE UNIQUE INDEX uq_inbound_event ON your_webhook_log(event, transaction_id);重试与投递
- 最多 5 次尝试:首次投递,之后分别在 60 秒、5 分钟、30 分钟和 2 小时后重试。
- 2xx = 成功,SOAMTAR 停止发送。
- 3xx = 视为失败(您的端点必须直接返回 2xx)。
- 4xx、5xx 或超时 = 失败,安排重试。
- 连续多次失败后,端点会被停用,并会收到邮件通知。
您可以在 控制台 → Webhook 日志 → Resend 手动重发某次投递。
无中断轮换密钥
- 控制台 → Webhooks → Rotate secret。SOAMTAR 会显示新密钥(请立即复制:不会再次显示)。
- 在轮换窗口内,每个 payload 都可能用两个密钥签名。
- 更新您的验证逻辑,接受
v1或v2。 - 点击 Promote:新密钥成为当前密钥,旧密钥失效。
测试 Webhook
在控制台中发送一个使用真实密钥签名的测试事件(控制台 → Webhooks → Test),或使用您的私钥调用 POST /v1/webhooks/test。两者都会发送 webhook.test 事件,无论订阅设置如何,都会投递到您所有有效的端点。
如果错过了 Webhook
- 主动查询:
POST /v1/payments/{id}/verify会让 SOAMTAR 向 Orange Money 查询,并在状态已变化时重新发送事件。 - 内部自动对账会处理卡住的交易:通常您无需任何操作。