Skip to main content

Webhooks

Les webhooks vous permettent de recevoir des notifications en temps réel sur les événements de paiement.
Le webhook est la source de vérité. Ne marquez jamais une commande comme “payée” sans avoir reçu et vérifié un webhook payment.success.

Événements

ÉvénementDescription
payment.successPaiement confirmé
payment.failedPaiement échoué
payment.cancelledPaiement annulé par le client
payment.expiredDélai de paiement expiré

Format du payload

{
  "event": "payment.success",
  "version": "v1",
  "timestamp": "2025-12-18T16:37:00.000Z",
  "data": {
    "id": "txn_abc123",
    "amount": 5000,
    "currency": "XOF",
    "status": "SUCCESS",
    "provider": "ORANGE_MONEY",
    "provider_ref": "OM123456789",
    "customer_phone": "+22370123456",
    "metadata": {
      "order_id": "order_123"
    },
    "created_at": "2025-12-18T16:35:00.000Z",
    "updated_at": "2025-12-18T16:37:00.000Z"
  }
}

Headers

HeaderDescription
X-SahelPay-SignatureSignature HMAC pour vérification
X-SahelPay-TimestampTimestamp UNIX (secondes)
X-SahelPay-Event-IDID unique de l’événement

Vérification de signature

Le header X-SahelPay-Signature a le format : t=timestamp,v1=signature
import crypto from 'crypto';

function verifyWebhook(rawBody, signatureHeader, secret) {
  const parts = {};
  signatureHeader.split(',').forEach(p => {
    const [key, value] = p.split('=');
    parts[key] = value;
  });

  const timestamp = parts['t'];
  const signature = parts['v1'];

  // Protection replay (5 min)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) {
    return false;
  }

  // Vérifier signature
  const payload = `${timestamp}.${rawBody}`;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Retry automatique

SahelPay retente automatiquement les webhooks en cas d’échec :
TentativeDélai
1Immédiat
21 minute
32 minutes
44 minutes
58 minutes
Après 5 tentatives, le webhook est marqué comme FAILED.

Bonnes pratiques

Répondez avec un 200 OK en moins de 5 secondes. Si le traitement est long, faites-le en async.
Utilisez X-SahelPay-Event-ID pour éviter de traiter deux fois le même événement.
Conservez un log de tous les webhooks reçus pour le debug.