Skip to content

Webhooks 与 IPN / APN ​

SOAMTAR 通过 webhooks 实时通知您的服务器,也称为 IPN(即时付款通知) 或 APN(自动付款通知)。

  • IPN:付款一经确认即发送的即时通知(例如 payment.success)。
  • APN:针对次要状态变化和生命周期事件的自动通知(例如 payment.expired)。

投递经过签名(HMAC-SHA256),按事件保证幂等,按退避策略重试,且密钥可在不中断服务的情况下轮换。

事件 ​

事件触发时机
payment.success塞拉利昂 Orange Money 确认了付款(客户已输入 PIN)
payment.failedOrange 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 请求体(默认格式):

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>

验证 ​

  1. 解析时间戳 t 和签名 v1(如有,还有 v2)。
  2. 检查 t 是否足够新(在容差窗口内)。
  3. 重新计算 expected = HMAC-SHA256(t + "." + raw_body, secret)。
  4. 以恒定时间比较(PHP 中用 hash_equals,Node 中用 crypto.timingSafeEqual)。
  5. 如存在 v2,则 v1 或 v2 任一匹配即接受。
php
$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-AgentSoamtar-Webhooks/1.0 (+https://app.so-amtar.com/docs/webhooks)

服务器端的幂等处理 ​

同一事件可能多次到达(超时后重试、事故后重放)。请按 (event, data.id) 组合去重,并忽略重复事件。

sql
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 手动重发某次投递。

无中断轮换密钥 ​

  1. 控制台 → Webhooks → Rotate secret。SOAMTAR 会显示新密钥(请立即复制:不会再次显示)。
  2. 在轮换窗口内,每个 payload 都可能用两个密钥签名。
  3. 更新您的验证逻辑,接受 v1 或 v2。
  4. 点击 Promote:新密钥成为当前密钥,旧密钥失效。

测试 Webhook ​

在控制台中发送一个使用真实密钥签名的测试事件(控制台 → Webhooks → Test),或使用您的私钥调用 POST /v1/webhooks/test。两者都会发送 webhook.test 事件,无论订阅设置如何,都会投递到您所有有效的端点。

如果错过了 Webhook ​

  1. 主动查询:POST /v1/payments/{id}/verify 会让 SOAMTAR 向 Orange Money 查询,并在状态已变化时重新发送事件。
  2. 内部自动对账会处理卡住的交易:通常您无需任何操作。

SOAMTAR Payment API Documentation.