Kadryza Pay est en preview privée au Tchad. Demander un accès anticipé →

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/webhooks

Ré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/:id

Tester un endpoint

POST /v1/webhooks/:id/test

Un webhook de test contient aussi :

X-Kadryza-Test: true

Headers envoyés aux webhooks

Kadryza envoie actuellement :

Content-Type: application/json
X-Kadryza-Signature: sha256=<hmac_hex>
User-Agent: Kadryza-Webhook/1.0

Pour les livraisons de test uniquement :

X-Kadryza-Test: true

Ne 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énementDescription
transaction.successTransaction confirmée avec succès
transaction.failedTransaction échouée
transaction.timeoutTransaction expirée sans confirmation
transaction.testWebhook envoyé par /v1/webhooks/:id/test
payment_session.succeededPayment session passée en SUCCESS (après confirmation sûre)
payment_session.under_reviewPaiement reçu à vérifier — jamais transformé en succès automatiquement
payment_session.expiredSession 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 :

ChampType
idUUID
referencestring
amountinteger
currencyXAF
operatorAIRTEL ou MOOV
phone_numberstring
statusstatut transaction
confirmed_atstring, 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.status

Sécurité

  • vérifiez toujours X-Kadryza-Signature avant 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.