Idempotence
Rejouer une création sans risque grâce à l'en-tête Idempotency-Key.
Un délai dépassé côté réseau ne vous dit rien sur ce que le serveur a fait. Rejouer un
POST /transfers à l'aveugle peut payer deux fois un bénéficiaire. Une clé d'idempotence lève
l'ambiguïté : le second appel renvoie la réponse du premier au lieu de refaire le travail.
Deux pages, deux questions
Cette page décrit la mécanique : l'en-tête, son format, où il fonctionne, combien de temps il dure. Pour choisir vos clés, structurer vos nouvelles tentatives et dédupliquer les webhooks dans votre propre code, consultez Bonnes pratiques → Idempotence.
L'en-tête
Envoyez Idempotency-Key sur la requête. Sans préfixe X-.
Authorization: sk.xxxxxxxxxxxxxxxx...
Idempotency-Key: 8f14e45f-ceea-467a-9bd1-2c2bd7a1e0a4
Content-Type: application/jsonIdempotency-Keystringfacultatif[A-Za-z0-9._:-]+. Un UUID v4 est le choix habituel. Tout caractère hors de cet alphabet renvoie un 422 sur idempotency_key.Les clés sont rattachées au compte, à l'environnement et à l'opération, et celles d'une clé publique
à part de celles d'une clé secrète ou restreinte : sur POST /payments, une clé que l'une a
utilisée est refusée à l'autre avec un 422. La même chaîne utilisée en sandbox et en live
correspond à deux clés indépendantes, la même chaîne sur POST /customers et sur POST /links
aussi, et aucun autre compte ne peut entrer en collision avec les vôtres.
Où ça fonctionne
| Endpoint | Protection |
|---|---|
POST /payments | Permanente |
POST /transfers | Permanente |
POST /transfers/batch | Obligatoire, chaque ligne a pour clé celle du lot et sa reference, permanent |
POST /refunds | Permanente |
POST /payments/{id} | Réponse enregistrée, 24 heures |
POST /payments/{id}/splits, /confirm, /close | Réponse enregistrée, 24 heures |
POST /customers | Réponse enregistrée, 24 heures |
POST /beneficiaries | Réponse enregistrée, 24 heures |
POST /invoices | Réponse enregistrée, 24 heures |
POST /links | Réponse enregistrée, 24 heures |
POST /webhooks | Réponse enregistrée, 24 heures |
POST /accounts | Réponse enregistrée, 24 heures |
POST /customers/{id}/tax_ids | Réponse enregistrée, 24 heures |
POST /tax/registrations | Réponse enregistrée, 24 heures |
POST /disputes/{id}/messages | Réponse enregistrée, 24 heures |
Les deux protections se comportent de la même façon vues de l'extérieur, et diffèrent par la durée pendant laquelle elles se souviennent.
Les paiements, les transferts et les remboursements gardent la clé pour toujours. La protection n'expire jamais : rejouer cette clé dans un an renvoie toujours le paiement, le transfert ou le remboursement d'origine au lieu d'en créer un second. Deux requêtes simultanées avec la même clé ne créent jamais deux ressources.
La première réponse de ces trois opérations est aussi enregistrée pendant 24 heures. Dans ce
délai, un rejeu renvoie cette réponse, inchangée, avec Idempotent-Replayed: true. Passé ce délai, le
paiement, le transfert ou le remboursement d'origine est renvoyé dans un nouveau 201, sans
l'en-tête, et pour un paiement avec un nouvel authorization_url et un nouvel
authorization_token, car une session de paiement ne sert qu'une fois.
Une clé sur un paiement, un transfert ou un remboursement est consommée pour toujours
Elle ne redevient jamais disponible. salaire-amina renvoyée le mois suivant ne paie pas Amina
une seconde fois : elle répond 201 avec le transfert du mois dernier et n'envoie rien. Mettez
dans la clé ce qui distingue deux intentions : la période de paie, la ligne de commande, la
tentative de remboursement.
Tout le reste se souvient de la première réponse pendant 24 heures, et rien d'autre. Passé ce délai, la clé est oubliée et le même appel crée une nouvelle ressource. C'est le bon compromis pour un client ou un lien de paiement, où un doublon est une gêne plutôt qu'une perte.
Ce qui est enregistré, c'est ce que l'opération a répondu en dessous de 500, refus compris. Une
charge refusée par un 402 sur POST /payments/{id} est rejouée comme refusée pendant 24 heures :
réessayer une fois le compte du client rechargé demande une nouvelle clé. Un 5xx n'est jamais
enregistré, pas plus qu'une requête rejetée à la validation : la clé est libérée et la tentative
suivante s'exécute de nouveau.
Rejouer une requête
Générez la clé une seule fois, au moment de l'intention métier, et réutilisez-la pour chaque tentative réseau de cette même intention.
curl https://api.wajub.com/transfers \
-H "Authorization: $WAJUB_API_KEY" \
-H "Idempotency-Key: payout-invoice-4172" \
-H "Content-Type: application/json" \
-d '{
"amount": 100000,
"currency": "XAF",
"beneficiary": {
"phone": "237670000000",
"channel": "cm.mtn",
"name": "Amina Nkeng"
}
}'Envoyez-la une seconde fois et vous recevez la première réponse, avec le même id de transfert et
un en-tête Idempotent-Replayed: true. Rien n'a été créé, aucun argent n'a bougé.
Le SDK PHP invente une clé si vous ne le faites pas
La clé qu'il ajoute de lui-même ne couvre que ses propres nouvelles tentatives réseau, pas les
vôtres : rappeler create() génère une nouvelle clé et crée une seconde ressource. Passez un
RequestOptions(idempotencyKey: ...) explicite dès que l'appel peut être relancé par votre
propre code, un worker de file d'attente ou un utilisateur qui clique deux fois.
Réutiliser une clé avec un corps différent
Rejouer n'est sûr que s'il s'agit de la même requête. Si vous réutilisez une clé avec un payload différent, Wajub refuse au lieu de renvoyer en silence une ressource que vous n'avez pas demandée :
Ce qui est comparé, et pendant combien de temps, dépend de l'endpoint.
| Endpoint | Ce qui est comparé | Pendant combien de temps |
|---|---|---|
POST /payments | Le corps entier | Toute la vie du paiement |
POST /transfers | amount, currency, beneficiary, description, reason, reference, metadata | 24 heures |
POST /refunds | Le corps entier pendant 24 heures, puis le paiement et amount | Toute la vie du remboursement |
POST /payments/{id} et /splits | channel, data, split_count, split_amounts | 24 heures |
| Tout le reste | Le corps entier | 24 heures |
Sur POST /transfers, plus rien n'est comparé une fois les 24 heures passées : la clé renvoie le
transfert d'origine, quoi que dise le nouveau corps.
Sur POST /customers/{id}/tax_ids et POST /disputes/{id}/messages, le client ou le litige du
chemin est comparé aussi : la même clé sur un autre répond ce 422. Les pièces jointes d'un
message sont comparées par leur contenu.
Une clé de remboursement réutilisée après 24 heures pour un autre paiement ou un autre montant est
refusée par le même 422, avec le message
This Idempotency-Key was already used with a different refund request.
Un en-tête mal formé arrive sous la forme du même 422 avec un autre message,
The Idempotency-Key header format is invalid.
Concurrence
Une requête qui arrive pendant que la première avec la même clé est encore en cours n'attend pas.
Elle reçoit tout de suite un 409, et le même appel quelques instants plus tard reçoit la réponse
enregistrée. Vous n'avez jamais deux créations en cours pour une même clé, ni une ressource écrite
à moitié.
Les SDK serveur relancent ce 409 pour vous avec la même clé et renvoient la réponse enregistrée.
Une clé par intention métier, pas par tentative
Dérivez la clé de quelque chose de déjà unique dans votre propre domaine : un ID de commande, un
numéro de facture, une ligne de paie. payout-invoice-4172 survit à un redémarrage du processus,
à un redéploiement et à une nouvelle tentative de la file d'attente. Un UUID généré dans la
boucle de nouvelles tentatives ne survit à aucun des trois.
Repérer une requête rejouée
Dans Konsole → Journaux API, une réponse servie par la
protection d'idempotence porte un badge replayed, ce qui vous permet de distinguer un appel en
double d'une ressource en double.