Version de travail · contenus et exemples à affiner Voir les 95 pages ↗
Parlons de votre projet ↗
Webhooks / P040

Événements

Adaptation éditoriale
Référence de travail. Les routes, schémas, limites et procédures hérités du référentiel sont à aligner avec l’API d’Éric. Ils ne prouvent pas une fonctionnalité livrée. Les hôtes .invalid sont des exemples inertes.

Tous ces événements partent par le même POST. L'en-tête Kelyko-Event dit lequel. Le corps change. Seul l'escrow répète event dans le JSON. Pour les autres, ce champ n'existe pas.

events: null sur un abonnement (le défaut) reçoit chaque événement routé vers cet abonnement. Une liste qui ne contient que TRANSACTION_COMPLETED laisse tomber les autres.

Qui reçoit

Deux abonnements peuvent être appelés pour le même événement :

  • Entreprise (storeId absent), plus l'URL legacy du Dashboard. Elle est candidate pour chaque événement.
  • Magasin, seulement si l'expéditeur vise ce storeId.
Événement Magasin visé
TRANSACTION_COMPLETED Le magasin de la transaction. Magasin du marchand, ou magasin dédié si la clé plateforme a encaissé là
SERVICE_PROVIDER_LINK_REQUEST Le magasin dédié du plateforme. Les autres magasins du marchand ne le reçoivent pas
ESCROW_HELD, ESCROW_RELEASED, ESCROW_REFUNDED, ESCROW_COLLECT_FAILED, ESCROW_EXPIRED Le magasin du pot. Avec une clé plateforme, c'est le magasin dédié
COMPLIANCE_VERIFICATION_COMPLETED Le magasin dédié du plateforme qui a ouvert la vérification

TRANSACTION_COMPLETED

Paiement complété, ou transfert complété ou échoué. *Un paiement qui échoue n'envoie pas cet événement.* L'absence de notification ne distingue pas un échec d'un paiement encore en cours.

Point d’attention.

Un encaissement retenu en escrow envoie ESCROW_HELD au paiement, pas TRANSACTION_COMPLETED. TRANSACTION_COMPLETED part à la libération (manuelle ou à holdUntil), avec la ligne type=escrow du solde opérable. Un remboursement n'envoie jamais TRANSACTION_COMPLETED. Une intégration qui n'attend que TRANSACTION_COMPLETED ne voit donc un paiement sous escrow qu'une fois libéré. Voir Escrow.

Le corps est la transaction, sans champ event. Les champs sont détaillés dans Intégration.

{
  "id": "txn_1234567890",
  "amount": { "amount": 10000, "currency": "XOF" },
  "fees": { "amount": 100, "currency": "XOF" },
  "status": "success",
  "counterpartLabel": "John Doe",
  "counterpartIdentifier": "+2250701234567",
  "paymentMethod": "wave",
  "transactionType": "payment",
  "businessName": "Ma Boutique",
  "storeId": "01a0b1c2-d3e4-7f89-a0b1-c2d3e4f5a6b7",
  "storeReference": "STORE-001",
  "storeName": "Magasin Principal",
  "description": "Payment for order #12345",
  "executedAt": "2024-01-15 14:30:25",
  "transactionDetails": {
    "id": "d22c81f3-ee04-4ec5-8bd2-cd8af5dabcfc",
    "reference": "PAY-2024-001",
    "paymentLinkId": "abc123def456"
  }
}

transactionType vaut payment ou transfer. status vaut pending, success ou error.

Pour un reversement, transactionDetails.id est l'ID du transfert, y compris quand reference n'a pas été envoyée. transactionDetails.reference n'est présent que si vous avez envoyé reference.

SERVICE_PROVIDER_LINK_REQUEST

Demande de rattachement, puis ses changements de statut. Ce n'est pas une transaction : pas de amount, pas de fees. Le détail du parcours est dans Rattacher un marchand.

{
  "id": "c1e15642-cdb5-404a-b028-26a51c94059b",
  "status": "pending",
  "merchantBusinessId": "59ae202a-f583-4a15-970f-9e99bd1e0baa",
  "merchantBusinessName": "Magasin de Jean",
  "serviceProviderBusinessId": "a0b1c2d3-e4f5-6789-abcd-ef0123456789",
  "serviceProviderBusinessName": "Ma Marketplace",
  "ownerPhone": "+22507012345",
  "expiresAt": "2026-08-30T12:00:00.000Z",
  "createdAt": "2026-08-23T12:00:00.000Z",
  "serviceProviderMemberId": null
}

serviceProviderMemberId est renseigné quand le marchand a approuvé. L'idempotence se fait sur id.

Événements escrow

ESCROW_HELD, ESCROW_RELEASED, ESCROW_REFUNDED, ESCROW_COLLECT_FAILED et ESCROW_EXPIRED envoient tous le même corps : le pot tel que le renvoie GET /partner_api/escrows/{id} (avec ses blocs collect, release et refund), précédé de event et escrowId, suivi de businessName, storeName et walletAvailableBalance. ESCROW_RELEASED ajoute transactionId. Le détail des blocs est dans Escrow.

Kelyko-Event dit lequel. Le corps répète event. Lisez l'en-tête d'abord. walletAvailableBalance est le solde opérable : il ne bouge pas pendant le hold.

Événement Quand Statut du pot
ESCROW_HELD Le client a payé, les fonds sont retenus. Pas une transaction dépensable : GET /transactions reste vide pour ce job held
ESCROW_COLLECT_FAILED La demande de paiement du pot a échoué ou expiré. Vous pouvez relancer /collect failed
ESCROW_RELEASED Libération manuelle ou à holdUntil. Le solde opérable est crédité. transactionId est la ligne type=escrow de GET /transactions released
ESCROW_REFUNDED Remboursement au payeur, envoyé quand il est lancé. refund.status peut encore être pending refunded
ESCROW_EXPIRED holdUntil est passé sans paiement expired

Exemple ESCROW_RELEASED (frais payés par le marchand) :

{
  "event": "ESCROW_RELEASED",
  "escrowId": "7c2d1a90-0b1e-4c3a-9f11-2e8c4d6a1b20",
  "transactionId": "a1b2c3d4-e5f6-7890-abcd-ef0123456789",
  "id": "7c2d1a90-0b1e-4c3a-9f11-2e8c4d6a1b20",
  "storeId": "59ae202a-f583-4a15-970f-9e99bd1e0baa",
  "status": "released",
  "holdUntil": "2026-09-14T20:00:00.000Z",
  "amount": { "amount": 50000, "currency": "XOF" },
  "createdAt": "2026-09-07T20:00:00.000Z",
  "collect": {
    "type": "payment_request",
    "status": "success",
    "errorReason": null,
    "reference": "ORDER-1042",
    "redirectUrl": null,
    "link": null,
    "amount": { "amount": 50000, "currency": "XOF" },
    "fees": { "amount": 800, "currency": "XOF" },
    "paymentMethod": "wave",
    "counterpartIdentifier": "+2250700000000"
  },
  "release": {
    "amount": { "amount": 49200, "currency": "XOF" },
    "fees": { "amount": 800, "currency": "XOF" },
    "autoRelease": false
  },
  "refund": null,
  "businessName": "Boutique Example",
  "storeName": "Cocody",
  "walletAvailableBalance": { "amount": 49200, "currency": "XOF" }
}

Exemple ESCROW_COLLECT_FAILED :

{
  "event": "ESCROW_COLLECT_FAILED",
  "escrowId": "7c2d1a90-0b1e-4c3a-9f11-2e8c4d6a1b20",
  "id": "7c2d1a90-0b1e-4c3a-9f11-2e8c4d6a1b20",
  "storeId": "59ae202a-f583-4a15-970f-9e99bd1e0baa",
  "status": "failed",
  "holdUntil": "2026-09-14T20:00:00.000Z",
  "amount": null,
  "createdAt": "2026-09-07T20:00:00.000Z",
  "collect": {
    "type": "payment_request",
    "status": "failed",
    "errorReason": "Insufficient balance",
    "reference": "ORDER-1042",
    "redirectUrl": null,
    "link": null,
    "amount": { "amount": 50000, "currency": "XOF" },
    "fees": null,
    "paymentMethod": "wave",
    "counterpartIdentifier": null
  },
  "release": null,
  "refund": null,
  "businessName": "Boutique Example",
  "storeName": "Cocody",
  "walletAvailableBalance": { "amount": 0, "currency": "XOF" }
}

paymentRequestId et paymentLinkId ne font plus partie du corps : lisez collect.

COMPLIANCE_VERIFICATION_COMPLETED

Vérification KYC ou KYB d'un marchand rattaché à un plateforme, quand le statut devient succeeded ou failed. Le sondage GET reste valable. Voir Conformité.

{
  "id": "fc86b752-c92e-44cb-a1c5-73d5ec256982",
  "type": "kyc",
  "status": "succeeded",
  "merchantBusinessId": "59ae202a-f583-4a15-970f-9e99bd1e0baa"
}

type vaut kyc ou kyb. status vaut succeeded ou failed.