Webhooks
Les événements Payment Session peuvent inclure un objet data.pricing figé
avec la session : montant original, total payeur, répartition des frais et net
commercial. data.amount reste le total attendu du paiement. Une intégration
ayant déclaré pricing_version: 1 doit distinguer ce total du prix de commande
dans data.pricing.original_amount. Les nouvelles tentatives de livraison ne
recalculent jamais le tarif courant. Ni une cotation ni une livraison webhook
ne constituent une disponibilité financière. Les coûts provider/réseau et la
marge interne Kadryza ne sont pas exposés dans ces payloads publics.
Kadryza envoie un POST signé à vos endpoints quand un paiement atteint un
état qui compte. C’est le canal de référence pour mettre à jour vos commandes.
Les endpoints de configuration utilisent X-API-Key avec la portée
manage_webhooks, et sont limités à 20 modifications par 5 minutes. Vous
pouvez aussi les gérer depuis le dashboard.
Configurer un endpoint
Créer un endpoint
/v1/webhooksX-API-Key{ "url": "https://merchant.example.com/webhooks/kadryza" }{
"id": "d0fb9df9-0a9b-4c2f-84f8-e28d1a2b1e7c",
"url": "https://merchant.example.com/webhooks/kadryza",
"secret": "5f0c8e3a…(64 caractères hexadécimaux)",
"is_active": true,
"created_at": "2026-06-05T12:00:00Z"
}secret n’est renvoyé qu’à la création. Stockez-le immédiatement dans
un secret serveur. Si vous le perdez, créez un nouvel endpoint et retirez
l’ancien.
Règles sur l’URL (400 WEBHOOK_URL_UNSAFE sinon) :
- elle doit résoudre vers une adresse publique :
localhost, adresses privées et réseaux internes sont refusés, y compris en test ; - HTTPS est obligatoire pour les livraisons live ; HTTP n’est toléré que pour les événements de test ;
- les redirections ne sont suivies que si elles conservent la méthode
POST.
Lister les endpoints
/v1/webhooksX-API-Key{
"endpoints": [
{
"id": "d0fb9df9-0a9b-4c2f-84f8-e28d1a2b1e7c",
"url": "https://merchant.example.com/webhooks/kadryza",
"is_active": true,
"created_at": "2026-06-05T12:00:00Z"
}
]
}Activer ou désactiver un endpoint
/v1/webhooks/:idX-API-Key{ "is_active": false }Un endpoint désactivé ne reçoit plus rien ; les livraisons prévues pour lui
sont marquées skipped. Renvoie l’endpoint mis à jour.
Retirer un endpoint
/v1/webhooks/:idX-API-KeyRenvoie 204. L’endpoint disparaît de la liste et ne reçoit plus rien ;
l’historique des livraisons est conservé.
Tester un endpoint
/v1/webhooks/:id/testX-API-KeyEnvoie immédiatement un événement transaction.test signé, avec
X-Kadryza-Test: true. Réponse 200 {"status":"ok"} si votre endpoint a
répondu 2xx, sinon 502 WEBHOOK_TEST_FAILED.
Événements
| Événement | Envoyé quand |
|---|---|
payment_session.succeeded | Une Payment Session passe en SUCCESS |
payment_session.under_review | Une Payment Session passe en UNDER_REVIEW : paiement à vérifier, ou issue inconnue après l’échéance. Jamais transformé automatiquement en succès. |
payment_session.expired | Une Payment Session test passe en EXPIRED |
transaction.success, transaction.failed, transaction.timeout | Une transaction (API historique) atteint un état final |
transaction.test | Envoi manuel par POST /v1/webhooks/:id/test |
N’implémentez pas d’autres événements tant qu’ils ne figurent pas ici.
payment_session.failed est réservé et n’est pas émis.
Un événement n’est émis qu’après que la décision est enregistrée côté Kadryza. Si la session change d’état avant l’envoi, l’ancien événement est abandonné plutôt qu’envoyé avec des données périmées.
En-têtes
Content-Type: application/json
User-Agent: Kadryza-Webhook/1.0
X-Kadryza-Signature: sha256=<hmac_hex>
X-Kadryza-Event-ID: 3f1c2b9a-7d4e-5a61-9b2f-0c8d7e6f5a41
X-Kadryza-Test: true| En-tête | Présent |
|---|---|
X-Kadryza-Signature | Toujours |
X-Kadryza-Event-ID | Événements payment_session.* |
X-Kadryza-Test | Événements payment_session.* de l’environnement test, et transaction.test (pas les autres événements transaction.* de sandbox) |
Payloads
payment_session.*
{
"event_id": "3f1c2b9a-7d4e-5a61-9b2f-0c8d7e6f5a41",
"event": "payment_session.succeeded",
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"merchant_id": "1f2e3d4c-5b6a-7980-cdef-001122334455",
"reference": "order_2026_001",
"ticket": "KDRZ-8F3K2",
"amount": 5000,
"currency": "XAF",
"operator": "AIRTEL",
"status": "SUCCESS",
"environment": "live",
"collection_number_id": "9a8b7c6d-5e4f-3210-fedc-ba9876543210",
"expires_at": "2026-06-05T12:40:00Z",
"completed_at": "2026-06-05T12:33:10Z",
"payment_proof": {
"operator_reference": "PP260605.1233.A00001",
"payer_name": "IDRISS DEDE",
"payer_phone_masked": "+235 66 •• •• 01",
"received_at": "2026-06-05T12:33:08Z"
},
"created_at": "2026-06-05T12:30:00Z"
},
"timestamp": "2026-06-05T12:33:10Z"
}event_idest stable entre les retries : c’est votre clé de déduplication.- Le payload n’expose ni le téléphone complet du payeur, ni
metadata, ni le numéro de collecte en clair (seulement son identifiant opaquecollection_number_id). payment_proofest présent dès que Kadryza détient le SMS de l’opérateur (succès ou vérification) : référence de transaction, nom du payeur et numéro masqué. Voir Preuve de paiement.
transaction.*
{
"event": "transaction.success",
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"reference": "order_2026_001",
"amount": 5000,
"currency": "XAF",
"operator": "AIRTEL",
"phone_number": "+23566000000",
"status": "SUCCESS",
"confirmed_at": "2026-06-05T12:01:15Z"
},
"timestamp": "2026-06-05T12:01:15Z"
}Pas d’event_id : dédupliquez sur event + data.id + data.status.
Signature
X-Kadryza-Signature vaut sha256= suivi du HMAC-SHA256 du corps brut,
calculé avec le secret de l’endpoint et encodé en hexadécimal.
- Calculez sur les octets reçus, avant tout parsing JSON.
- Comparez en temps constant.
- Rejetez toute requête non signée ou mal signée.
Exemples complets (SDK, Node.js, Express, Next.js) : Vérifier les webhooks.
Réponse attendue et retries
Répondez 2xx en moins de 10 secondes. Toute autre réponse, un timeout
ou une erreur réseau est un échec.
| Tentative | Délai après l’échec précédent |
|---|---|
| 1 | immédiate |
| 2 | 1 minute |
| 3 | 5 minutes |
| 4 | 30 minutes |
| 5 | 2 heures |
Après la cinquième tentative, la livraison est abandonnée (failed). Vous
pouvez consulter l’historique d’une session avec
GET /v1/payment-sessions/:id/webhook-deliveries.
Idempotence et ordre
- Une même livraison peut arriver plusieurs fois (retry après un timeout de
votre côté) : stockez
event_idavant toute action irréversible et ignorez les doublons. - L’ordre d’arrivée entre événements différents n’est pas garanti :
appuyez-vous sur
data.status, ou relisez la session. - Répondez vite, traitez en arrière-plan.