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 (
storeIdabsent), 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.