Escrow (séquestre opérationnel)
Bloquer un encaissement Partner jusqu'à libération par la clé plateforme du Service Provider
Produit Service Provider
L'escrow n'est pas un encaissement instantané. Seule la clé plateforme (clé API marchand émise par le Service Provider) peut créer un pot, lancer son encaissement, et plus tard le libérer ou le rembourser. Le marchand ne peut pas se débloquer lui-même.
Tout le cycle de vie passe par : vous créez le pot, vous lancez son encaissement avec , puis vous le libérez ou le remboursez. Le pot montre lui-même où en est l'encaissement, les montants et les frais. Ce n'est pas un séquestre réglementé : les fonds retenus n'apparaissent ni dans le solde du marchand ni dans jusqu'à la libération.
escrowId n'est plus accepté sur les paiements
et avec escrowId répondent 400 escrow_id_not_supported, et rien n'est créé. Les encaissements d'un pot ne sont plus lisibles sur ni (404). Le pot ne contient plus paymentRequestId ni paymentLinkId. Lisez le bloc collect du pot à la place.
Clés
| Clé | Encaissement instantané | /escrows |
|---|---|---|
| Clé plateforme (émise par le SP pour ce marchand) | Oui | Oui |
| Clé Owner / Dashboard du marchand | Oui | 403 |
Clé entreprise du SP (/service_providers/*) | Non (pas d'argent) | 403 |
La clé plateforme est scoped au marchand. Elle ne peut pas toucher les escrows d'un autre marchand (404). Vous l'obtenez via Clés et magasin dédié.
Il n'y a pas d'activation par marchand. Dès que la clé plateforme existe, est disponible pour ce marchand.
De la création à la libération
POST /partner_api/escrows { storeId, holdUntil? } → pending
POST /partner_api/escrows/{id}/collect { type, data } → pending, collect.status: pending
→ le client paie (redirect / in-app / soundbox / lien)
→ held (ESCROW_HELD) ; GET /transactions reste vide pour ce job
ou l'encaissement échoue → failed (ESCROW_COLLECT_FAILED) → relancez /collect
POST /partner_api/escrows/{id}/release → released (ESCROW_RELEASED)
ou /refund → refunded (ESCROW_REFUNDED)
ou holdUntil passe sans paiement → expired (ESCROW_EXPIRED)| Statut | Signification | Webhook |
|---|---|---|
pending | Pot créé, ou encaissement en cours | aucun |
failed | L'encaissement (demande de paiement) a échoué ou expiré. Le pot peut être relancé | ESCROW_COLLECT_FAILED |
held | Le client a payé, les fonds sont retenus | ESCROW_HELD |
released | Fonds crédités au marchand (manuel ou à holdUntil) | ESCROW_RELEASED |
refunded | Remboursement au payeur lancé | ESCROW_REFUNDED |
expired | holdUntil est passé sans paiement | ESCROW_EXPIRED |
1. Créer le pot
Create escrow (Partner API)curl -X POST "https://api.jeko.africa/partner_api/escrows" \
-H "X-API-KEY: platform_key" \
-H "X-API-KEY-ID: platform_key_id" \
-H "Content-Type: application/json" \
-d '{
"storeId": "59ae202a-f583-4a15-970f-9e99bd1e0baa"
}'Corps : uniquement storeId et, optionnellement, holdUntil (ISO 8601 UTC).
holdUntilomis → 7 jours. Doit être dans le futur, au plus 30 jours.holdUntilcourt dès la création du pot, pas dès le paiement. Il ne peut être ni prolongé ni désactivé.- Pot
heldàholdUntil→ Jèko le libère (pas un remboursement au payeur). - Pot encore
pendingoufailedàholdUntil→ il passeexpired, et son encaissement en cours est arrêté (demande expirée, lien désactivé).
Réponse (pending) :
{
"id": "7c2d1a90-0b1e-4c3a-9f11-2e8c4d6a1b20",
"storeId": "59ae202a-f583-4a15-970f-9e99bd1e0baa",
"status": "pending",
"holdUntil": "2026-09-14T20:00:00.000Z",
"amount": null,
"createdAt": "2026-09-07T20:00:00.000Z",
"collect": null,
"release": null,
"refund": null
}2. Lancer l'encaissement
Collect escrow (Partner API)type vaut payment_request ou payment_link. data reprend le corps habituel de Paiement en ligne ou de Liens de paiement, sans storeId (c'est celui du pot) :
payment_request:amountCents,currency,reference,paymentDetails.payment_link:title,amountCents,currency. Le lien est toujours à paiement unique.
curl -X POST "https://api.jeko.africa/partner_api/escrows/7c2d1a90-0b1e-4c3a-9f11-2e8c4d6a1b20/collect" \
-H "X-API-KEY: platform_key" \
-H "X-API-KEY-ID: platform_key_id" \
-H "Content-Type: application/json" \
-d '{
"type": "payment_request",
"data": {
"amountCents": 50000,
"currency": "XOF",
"reference": "ORDER-1042",
"paymentDetails": {
"type": "redirect",
"data": {
"paymentMethod": "wave",
"successUrl": "https://example.com/success",
"errorUrl": "https://example.com/error"
}
}
}
}'Réponse : le pot, avec le bloc collect.
{
"id": "7c2d1a90-0b1e-4c3a-9f11-2e8c4d6a1b20",
"status": "pending",
"collect": {
"type": "payment_request",
"status": "pending",
"errorReason": null,
"reference": "ORDER-1042",
"redirectUrl": "https://pay.wave.com/c/…",
"link": null,
"amount": { "amount": 50000, "currency": "XOF" },
"fees": null,
"paymentMethod": "wave",
"counterpartIdentifier": null
},
"release": null,
"refund": null
}redirectUrln'est renvoyé que dans cette réponse. Conservez-le : le renvoie ànull.- Pour un lien,
collect.linkest l'URL à partager,referenceetpaymentMethodrestentnulljusqu'au paiement. - Tous les moyens de paiement mobile money sont acceptés : Wave, Orange, MTN, Moov, Djamo.
- Un pot = un encaissement à la fois.
/collectsur un potpendingqui a déjà un encaissement → 409escrow_already_bound. /collectsur un potheld,released,refunded,expired, ou aprèsholdUntil→ 409escrow_not_collectable.- Si l'appel à l'opérateur échoue (400
third_party_payment_provider_error), le pot n'est pas consommé : vous pouvez rappeler/collect.
Suivre l'encaissement
Pollez , ou écoutez les webhooks. collect.status :
collect.status | Quand |
|---|---|
pending | En attente du paiement |
success | Payé : le pot est held |
failed | L'opérateur a refusé le paiement. errorReason donne sa raison |
expired | La demande a expiré sans paiement (lien : holdUntil passé) |
Échec et nouvelle tentative
Une demande de paiement qui échoue ou expire fait passer le pot à failed, et ESCROW_COLLECT_FAILED part. Rappelez /collect sur le même pot : il repasse pending et le nouveau collect remplace l'ancien. Les tentatives précédentes n'apparaissent plus.
Tant que la tentative précédente peut encore être payée chez l'opérateur, /collect répond 409 escrow_not_collectable. Réessayez quand elle est terminée.
Un lien ne fait jamais passer le pot à failed : le payeur peut réessayer sur le même lien. Le pot reste pending jusqu'au paiement ou jusqu'à holdUntil.
3. Après le paiement (held)
- :
status: "held",collect.status: "success", avec ce que le payeur a payé, les frais, son moyen de paiement et son numéro. - : vide pour ce job. Le solde magasin () ne change pas.
- Webhook
ESCROW_HELD, pasTRANSACTION_COMPLETED. Ne créditez pas le marchand comme si le paiement était dépensable.TRANSACTION_COMPLETEDarrive plus tard, à la libération.
{
"id": "7c2d1a90-0b1e-4c3a-9f11-2e8c4d6a1b20",
"storeId": "59ae202a-f583-4a15-970f-9e99bd1e0baa",
"status": "held",
"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": null,
"refund": null
}collect.amount est ce que le payeur a payé : le montant demandé si le marchand paie les frais, montant + frais si le payeur les paie (50800 ici). amount en haut du pot reste le montant demandé.
Un abonnement events: ["TRANSACTION_COMPLETED"] ne reçoit pas les webhooks escrow. events: null (défaut) les reçoit. Lisez d'abord l'en-tête Jeko-Event. Qui reçoit l'appel est dans Événements. L'abonnement se crée avec la clé Service Provider : Webhooks marchand.
Libération, remboursement, expiration
Un pot held se termine par une libération (manuelle ou automatique) ou un remboursement. Chacun porte sur tout le pot : pas de montant partiel.
Libération
crédite le marchand. Résultat : status: "released", le bloc release, et une ligne type=escrow sur . Deux webhooks partent : ESCROW_RELEASED et TRANSACTION_COMPLETED, dont le corps est cette ligne.
"release": { "amount": { "amount": 49200, "currency": "XOF" }, "fees": { "amount": 800, "currency": "XOF" }, "autoRelease": false }release.amount est le montant crédité au marchand, égal à celui de la ligne type=escrow.
Libération automatique à holdUntil
Si le pot est encore held à holdUntil, Jèko le libère exactement comme un release : mêmes montants, même ligne type=escrow, mêmes webhooks, avec release.autoRelease: true.
Remboursement
rembourse le payeur. Le marchand ne reçoit rien et ne paie rien. Résultat : status: "refunded", le bloc refund, et ESCROW_REFUNDED, sans TRANSACTION_COMPLETED ni ligne sur .
"refund": {
"status": "success",
"amount": { "amount": 49000, "currency": "XOF" },
"fees": { "amount": 200, "currency": "XOF" },
"paymentMethod": "wave",
"counterpartIdentifier": "+2250700000000"
}ESCROW_REFUNDED part quand le remboursement est lancé. Avec Wave, refund.status vaut tout de suite success. Avec les autres opérateurs, il vaut pending jusqu'à la réponse de l'opérateur, puis success ou failed. Le pot reste refunded dans les deux cas : lisez refund.status sur . counterpartIdentifier peut être null quand le numéro du payeur n'est pas connu.
Expiration
Un pot pending ou failed dont holdUntil passe devient expired, et ESCROW_EXPIRED part. Son encaissement en cours est arrêté. Si un paiement de cet encaissement est malgré tout confirmé par l'opérateur après coup, le pot passe held (ESCROW_HELD), puis il est libéré automatiquement, car holdUntil est déjà passé.
Qui reçoit combien
Exemple : un paiement Wave de 500 XOF, avec 8 XOF de frais et 2 XOF de frais de remboursement.
| Frais payés par le marchand | Frais payés par le payeur | |
|---|---|---|
Le payeur paie (collect.amount) | 500 XOF | 508 XOF |
À la libération, le marchand reçoit (release.amount) | 492 XOF (frais 8) | 500 XOF (frais 8) |
Au remboursement, le payeur reçoit (refund.amount) | 490 XOF (500 − 8 − 2) | 498 XOF (508 − 8 − 2) |
Votre commission de Service Provider vous est versée à la libération ou au remboursement.
/escrows vs /transactions
| Surface | Rôle |
|---|---|
Cycle de vie du pot et de son encaissement, avec montants et frais (collect, release, refund) | |
Portefeuille opérable seulement. Rien pendant le hold. Après libération : une ligne type=escrow avec le net crédité, les frais et le moyen de paiement du payeur | |
| Disponible opérable seulement |
Erreurs fréquentes
| Code | Quand |
|---|---|
| 400 | holdUntil invalide ; escrowId envoyé à /payment_requests ou /payment_links (escrow_id_not_supported) |
| 422 | data qui ne correspond pas à type |
| 403 | Clé Owner / Dashboard ou clé entreprise du SP |
| 404 | Magasin ou escrow introuvable, ou autre marchand |
| 409 | escrow_already_bound : un encaissement est déjà en cours. escrow_not_collectable : statut qui ne permet pas d'encaisser, holdUntil passé, ou tentative précédente encore payable. payment_request_exists_with_reference : reference déjà utilisée |
Avant de détacher un marchand
Libérez ou remboursez ses pots held. Le détachement révoque les clés plateforme, et plus aucune clé ne peut agir sur ces pots.