Webhooks e IPN / APN
A SOAMTAR notifica o seu servidor em tempo real através de webhooks, também chamados IPN (Instant Payment Notification) ou APN (Automated Payment Notification).
- IPN: notificação imediata assim que o pagamento é confirmado (por ex.
payment.success). - APN: notificações automáticas para as alterações de estado secundárias e os eventos do ciclo de vida (por ex.
payment.expired).
Os envios são assinados (HMAC-SHA256), idempotentes por evento, repetidos com espera exponencial, e o segredo pode ser rodado sem interrupção.
Eventos
| Evento | Quando |
|---|---|
payment.success | O Orange Money Serra Leoa confirmou o pagamento (o cliente introduziu o seu PIN) |
payment.failed | O Orange Money recusou o pagamento, devolveu um erro ou esgotou o tempo de espera |
payment.cancelled | O pagador cancelou |
payment.expired | O prazo de pagamento terminou sem conclusão |
payment.refunded | Os reembolsos cobrem agora a totalidade do pagamento (data.refund = o reembolso) |
payment.partially_refunded | Um reembolso parcial foi concluído (data.refund = o reembolso) |
payout.success / payout.failed / payout.cancelled | Resultado de um pagamento de saída para uma carteira Orange Money |
refund.success / refund.failed | Resultado de um reembolso (data = o objeto refund + transaction_reference, merchant_reference, is_test) |
payout_request.submitted / payout_request.approved / payout_request.rejected / payout_request.completed / payout_request.failed | Ciclo de vida de um pedido de levantamento do seu saldo |
Escolhe os eventos a que cada endpoint está subscrito (Painel → Webhooks).
Cada reembolso gera o seu próprio conjunto de eventos: refund.success + payment.refunded / payment.partially_refunded, ou refund.failed. Ver Reembolsos (em inglês).
O evento webhook.test (enviado pelo botão de teste do painel e por POST /v1/webhooks/test) é entregue a todos os seus endpoints ativos independentemente das subscrições; não é possível subscrevê-lo.
Configuração
Painel → Programadores → Webhooks → Add endpoint
- URL: deve ser HTTPS público.
- Eventos: subconjunto da lista acima.
- Segredo: gerado aleatoriamente. Guarde-o no seu servidor.
Um endpoint é suspenso automaticamente após várias falhas seguidas (limiar interno).
callback_url por transação
Ao criar um pagamento (POST /v1/payments) ou um pagamento de saída (POST /v1/payouts) pode passar um callback_url HTTPS opcional. A SOAMTAR envia para esse URL todos os eventos dessa transação, com o mesmo payload, a mesma assinatura X-Soamtar-Signature e as mesmas repetições que para os seus endpoints.
- A assinatura usa o segredo do seu endpoint de webhook ativo mais antigo do mesmo ambiente (sandbox ou live). Precisa portanto de pelo menos um endpoint registado; sem nenhum, o
callback_urlnão é chamado. - Se o
callback_urlfor igual ao URL de um endpoint registado, o evento não é enviado duas vezes.
Conteúdo do webhook
Corpo JSON enviado para o seu URL (formato por defeito):
{
"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 é o identificador da transação SOAMTAR; data.merchant_reference é o merchant_ref que enviou.
Assinatura
Cada pedido inclui o cabeçalho:
X-Soamtar-Signature: t=<unix>,v1=<hex_hmac_sha256>Durante uma rotação do segredo, podem coexistir duas assinaturas:
X-Soamtar-Signature: t=<unix>,v1=<hmac_current_secret>,v2=<hmac_next_secret>Verificação
- Extraia o carimbo temporal
te a assinaturav1(ev2, se existir). - Verifique que
té recente (dentro da janela de tolerância). - Recalcule
expected = HMAC-SHA256(t + "." + raw_body, secret). - Compare em tempo constante (
hash_equalsem PHP,crypto.timingSafeEqualem Node). - Se existir uma
v2, aceite quando v1 OU v2 corresponder.
$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');Tolerância
O exemplo usa 300 segundos, um limite razoável que corresponde à tolerância aplicada pela SOAMTAR.
Cabeçalhos adicionais
| Cabeçalho | Significado |
|---|---|
X-Soamtar-Timestamp | Carimbo temporal da assinatura (mesmo valor que t) |
X-Soamtar-Event | Tipo de evento (payment.success, ...) |
X-Soamtar-Delivery | Identificador da tentativa de entrega, útil nos registos |
User-Agent | Soamtar-Webhooks/1.0 (+https://app.so-amtar.com/docs/webhooks) |
Idempotência no seu servidor
O mesmo evento pode chegar várias vezes (repetição após um tempo de espera esgotado, reenvio após um incidente). Elimine duplicados segundo o par (event, data.id) e ignore os repetidos.
CREATE UNIQUE INDEX uq_inbound_event ON your_webhook_log(event, transaction_id);Repetições e entrega
- 5 tentativas no máximo: o envio inicial e depois repetições após 60 s, 5 min, 30 min e 2 h.
- 2xx = sucesso, a SOAMTAR para.
- 3xx = considerado falha (o seu endpoint deve responder 2xx diretamente).
- 4xx, 5xx ou tempo esgotado = falha, é agendada uma repetição.
- Após várias falhas seguidas, o endpoint é suspenso e recebe um email.
Pode reenviar uma entrega manualmente em Painel → Webhook logs → Resend.
Rotação do segredo sem interrupção
- Painel → Webhooks → Rotate secret. A SOAMTAR mostra o novo segredo (copie-o já: não volta a ser mostrado).
- Durante a janela de rotação, cada payload pode ser assinado com os dois segredos.
- Atualize o seu verificador para aceitar
v1OUv2. - Clique em Promote: o novo segredo passa a ser o atual e o anterior é invalidado.
Testar um webhook
Use o painel para enviar um evento de teste assinado com o seu segredo real (Painel → Webhooks → Test), ou POST /v1/webhooks/test com a sua chave secreta. Ambos enviam o evento webhook.test, entregue a todos os seus endpoints ativos independentemente das subscrições.
Se perder um webhook
- Consulta ativa:
POST /v1/payments/{id}/verifyfaz a SOAMTAR consultar o Orange Money e voltar a emitir o evento se a transição tiver ocorrido. - Uma reconciliação automática interna recupera as transações bloqueadas: normalmente não tem de fazer nada.