Webhooks
Les webhooks Kadryza permettent de recevoir les changements importants de statut transaction.
Tous les endpoints de configuration webhook utilisent :
X-API-Key: <cle_api_kadryza>Endpoints de configuration
Créer un endpoint
POST /v1/webhooks{
"url": "https://merchant.example.com/webhooks/kadryza"
}Réponse :
{
"id": "d0fb9df9-0a9b-4c2f-84f8-e28d1a2b1e7c",
"url": "https://merchant.example.com/webhooks/kadryza",
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"is_active": true,
"created_at": "2026-06-05T12:00:00Z"
}Le secret webhook est retourné uniquement à la création. Stockez-le immédiatement.
Lister les endpoints
GET /v1/webhooksRéponse :
{
"endpoints": [
{
"id": "d0fb9df9-0a9b-4c2f-84f8-e28d1a2b1e7c",
"url": "https://merchant.example.com/webhooks/kadryza",
"is_active": true,
"created_at": "2026-06-05T12:00:00Z"
}
]
}Supprimer un endpoint
DELETE /v1/webhooks/:idTester un endpoint
POST /v1/webhooks/:id/testUn webhook de test contient aussi :
X-Kadryza-Test: trueHeaders envoyés aux webhooks
Kadryza envoie actuellement :
Content-Type: application/json
X-Kadryza-Signature: sha256=<hmac_hex>
User-Agent: Kadryza-Webhook/1.0Pour les livraisons de test uniquement :
X-Kadryza-Test: trueNe vous basez pas sur des headers d’événement ou d’identifiant de livraison qui ne sont pas listés ci-dessus : ils ne font pas partie du contrat actuellement confirmé.
Signature
La signature est :
- HMAC-SHA256 ;
- calculée sur le body JSON brut ;
- encodée en hex ;
- préfixée par
sha256=.
import { verifyWebhookSignature } from '@kadryza/sdk'
const valid = verifyWebhookSignature({
payload: rawBody,
signature: req.headers['x-kadryza-signature'],
secret: process.env.KADRYZA_WEBHOOK_SECRET!
})Événements confirmés
| Événement | Description |
|---|---|
transaction.success | Transaction confirmée avec succès |
transaction.failed | Transaction échouée |
transaction.timeout | Transaction expirée sans confirmation |
transaction.test | Webhook envoyé par /v1/webhooks/:id/test |
payment_session.succeeded | Payment session passée en SUCCESS (après confirmation sûre) |
payment_session.under_review | Paiement reçu à vérifier — jamais transformé en succès automatiquement |
payment_session.expired | Session AWAITING_PAYMENT expirée (dépasse la fenêtre d’expiration + grâce backend) sans paiement confirmé |
N’ajoutez pas d’autres événements à vos traitements tant que le backend ne les envoie pas explicitement.
Les événements payment_session.* ne sont émis qu’après la décision sûre côté
backend (commit confirmé). Un événement émis en environnement test porte
l’en-tête X-Kadryza-Test: true et data.environment = "test" — y compris pour
payment_session.expired (les sessions test expirent aussi).
payment_session.expired est émis lorsqu’une session AWAITING_PAYMENT dépasse
sa fenêtre d’expiration plus une période de grâce côté backend (un paiement
tardif pendant la grâce passe en under_review, jamais en succès). Comme les
autres événements payment_session.*, il ne crée aucune écriture comptable
(ledger).
Payload
{
"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"
}Champs confirmés dans data :
| Champ | Type |
|---|---|
id | UUID |
reference | string |
amount | integer |
currency | XAF |
operator | AIRTEL ou MOOV |
phone_number | string |
status | statut transaction |
confirmed_at | string, optionnel |
Payload payment_session.*
{
"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",
"created_at": "2026-06-05T12:30:00Z"
},
"timestamp": "2026-06-05T12:33:10Z"
}Le payload payment_session.* n’expose aucune donnée interne (pas de
raw_sms, ni numéro du payeur, ni gateway_id). Le numéro de collecte est
exposé sous forme d’identifiant opaque collection_number_id (le MSISDN à
afficher au client est dans la réponse de l’API assigned_collection_number).
Réponse attendue
Votre endpoint doit répondre avec un statut HTTP 2xx. Toute réponse non-2xx est considérée comme un échec de livraison.
Retries
Kadryza utilise un worker Asynq avec retries. La règle actuelle côté backend prévoit jusqu’à 4 retries avec backoff progressif.
Votre endpoint doit être idempotent. Comme aucun identifiant de livraison dédié n’est confirmé, utilisez une clé dérivée du payload, par exemple :
event + data.id + data.statusSécurité
- vérifiez toujours
X-Kadryza-Signatureavant de traiter l’événement ; - utilisez le body brut, pas un JSON reparsé puis re-sérialisé ;
- répondez vite et traitez les tâches longues en arrière-plan ;
- ne loggez pas le secret webhook.