Idempotence
Sécurisez les nouvelles tentatives pour qu'un délai d'expiration ne transforme jamais un paiement en deux.
Le problème n'est pas l'échec des requêtes. Il vient du fait que certaines échouent après que le serveur a effectué le travail. Délai d'expiration, connexion interrompue ou pod redémarré pendant l'opération : votre code reçoit une erreur sans savoir si le paiement a été créé.
Une clé d'idempotence élimine cette incertitude. Vous associez la même chaîne à la première tentative et à toutes les suivantes. Wajub renvoie alors le résultat initial au lieu d'effectuer deux fois l'opération.
Le fonctionnement détaillé se trouve dans la référence API
Cette page explique comment utiliser les clés dans une intégration réelle. Pour le contrat de l'en-tête, consultez Idempotence.
L'en-tête
L'en-tête est Idempotency-Key, sans préfixe X-. Vous choisissez sa valeur dans le format que
Wajub vérifie avant tout autre traitement de la requête.
| Règle | Valeur |
|---|---|
| Caractères autorisés | A-Z, a-z, 0-9 et . _ : - |
| Longueur | De 1 à 128 caractères |
| Toute autre valeur | 422, avec le motif dans errors.idempotency_key |
| Portée | La clé, votre équipe et l'environnement, ensemble |
La portée est plus importante qu'il n'y paraît. Une même clé utilisée dans la sandbox et en mode
live correspond à deux clés indépendantes. Rejouer un test de la sandbox ne peut donc pas entrer en
conflit avec la production. Pour une requête contenant X-Sync, le compte connecté fait aussi
partie de la portée. Une même clé peut ainsi désigner « cette commande » chez plusieurs vendeurs
d'un panier de marketplace.
Une espace, une barre oblique ou un # provoque le rejet immédiat de la clé. Un numéro de commande
comme #4172 ou un UUID entre accolades échoue donc avant même l'examen du paiement.
Endpoints qui la prennent en charge
L'idempotence ne se limite pas aux paiements. Tous les endpoints de création ci-dessous lisent l'en-tête.
| Endpoint | Résultat d'une répétition |
|---|---|
POST /payments | La réponse initiale et, après 24 heures, toujours le même paiement |
POST /payments/{id} | Le résultat initial du traitement |
POST /transfers | Le transfert initial |
POST /refunds | Le remboursement initial |
POST /customers | Le client initial |
POST /beneficiaries | Le bénéficiaire initial |
POST /invoices | La facture initiale |
POST /links | Le lien de paiement initial |
POST /webhooks | L'endpoint initial, secret inclus |
Le SDK Node envoie déjà une clé, et c'est le piège
Si vous utilisez @wajub/node sans transmettre idempotencyKey, le SDK en génère une pour chaque
requête autre que GET et DELETE. Il crée à chaque fois un nouvel UUID aléatoire.
L'en-tête est donc toujours présent, mais ne vous protège pas. La nouvelle tentative utilise une clé différente de la première, précisément la situation que l'idempotence doit éviter. La simple présence de l'en-tête ne suffit pas. Sa valeur doit être identique lors des deux tentatives.
curl https://api.wajub.com/payments \
-H "Authorization: sk_test.kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0eU3i…" \
-H "Idempotency-Key: ORDER-4172" \
-H "Content-Type: application/json" \
-d '{
"amount": 5000,
"currency": "XAF",
"email": "buyer@example.com",
"reference": "ORDER-4172"
}'Choisir le contenu de la clé
La clé doit survivre à votre processus. Générez-la avant la première tentative, enregistrez-la avec
la commande et relisez-la à chaque nouvelle tentative. Une clé créée avec Date.now() ou un nouvel
UUID au moment de l'appel change à chaque tentative, ce qui revient à ne pas avoir de clé.
| Stratégie | Clé | Cas adapté |
|---|---|---|
| Une commande, une seule tentative | ORDER-4172 | La plupart des checkouts. Simple et sans risque d'erreur |
| Une commande, tentatives numérotées | ORDER-4172-A2 | Vous permettez au client de réessayer volontairement après un échec |
| UUID enregistré | 5b2c… enregistré avec la commande | Les identifiants de vos commandes ne doivent pas être exposés |
Utilisez la deuxième stratégie lorsqu'un paiement Mobile Money échoue et que le client souhaite réessayer. L'augmentation du compteur est une action volontaire. Toutes les nouvelles tentatives accidentelles au sein d'un même essai aboutissent toujours à un seul paiement.
La clé appartient à la commande, pas à la requête
Enregistrez-la sur la ligne correspondant à la commande à payer. Si votre logique de nouvelle tentative doit inventer la clé, elle se trouve au mauvais endroit.
Ce qui constitue une même requête
Pour POST /payments, Wajub calcule le hash d'un instantané de votre payload et le conserve avec la
clé. Les champs suivants font partie de cet instantané.
amount, currency, email, phone, name, customer_id, description, reference,
callback, expires.in, theming, items, telemetry.
Tous les autres champs, y compris metadata, en sont exclus. Réutiliser la même clé avec un nouvel
objet metadata renvoie le paiement initial sans le modifier. Ne placez donc pas dans les
métadonnées une information que la répétition doit mettre à jour.
Si un champ de l'instantané change, la clé et le payload ne correspondent plus. Wajub refuse la requête au lieu de deviner lequel des deux est correct.
Cette erreur provient toujours d'un défaut dans votre code, jamais d'un problème temporaire. Vous avez réutilisé une clé qui aurait dû être renouvelée ou modifié un montant sans changer la clé. Ne réessayez pas cette requête.
Trois couches, dont une seule est permanente
POST /payments utilise plusieurs niveaux de protection. Identifier la couche qui répond permet de
comprendre le résultat reçu.
| Couche | Durée | Fonction |
|---|---|---|
| Verrou | 10 secondes | Deux requêtes simultanées avec la même clé attendent au lieu de se concurrencer |
| Cache | 24 heures | La réponse initiale est renvoyée à l'identique, statut inclus |
| Index unique | Permanente | La base de données refuse un second paiement avec cette clé, quel que soit l'état du cache |
Après 24 heures, le cache a disparu, mais pas l'index. Une répétition renvoie donc toujours le même
paiement au lieu d'en créer un second. Une différence est importante : de nouveaux
authorization_url et authorization_token sont générés, car une session de checkout ne sert
qu'une fois. L'identifiant du paiement ne change pas.
Les autres endpoints utilisent le verrou et le cache, mais pas l'index. Considérez 24 heures comme la période utile pour tous les endpoints et fiez-vous à vos propres enregistrements au-delà.
`reference` n'est pas une clé d'idempotence
reference est une chaîne libre qui n'est soumise à aucune règle d'unicité dans l'API. Envoyer
deux fois la même reference crée deux paiements. Ce champ sert à retrouver un paiement avec vos
propres références, pas à éviter les doublons.
Les webhooks nécessitent leur propre déduplication
Les clés d'idempotence protègent les appels que vous effectuez. Elles ne protègent pas les appels de Wajub vers votre système, qui sont volontairement répétés cinq fois après 30 secondes, 1 minute, 5 minutes, 10 minutes et 1 heure. Si un handler répond lentement ou si un déploiement arrive au mauvais moment, vous recevrez deux fois le même événement.
L'identifiant de l'événement reste identique lors de chaque nouvelle tentative de livraison.
Enregistrez-le dans une table avant d'agir, plutôt qu'en mémoire. Un Set interne au processus se
vide lors du déploiement qui a justement créé le retard que vous allez recevoir.
CREATE TABLE processed_events (
event_id TEXT PRIMARY KEY,
processed_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Returns a row the first time, nothing on every replay.
INSERT INTO processed_events (event_id)
VALUES ($1)
ON CONFLICT (event_id) DO NOTHING
RETURNING event_id;Répondez à la livraison avec 200 dans les deux cas. Une répétition déjà traitée n'est pas une
erreur. Toute autre réponse demande à Wajub de la renvoyer.
Pages associées
- IdempotenceLe contrat de l'en-tête, le comportement en cas de conflit et la durée de conservation.
- Gestion des erreursLes échecs qui justifient la nouvelle tentative sécurisée par cette page.
- Nouvelles tentatives et ordreLe calendrier des livraisons et ce que Wajub considère comme une tentative échouée.
- Accepter un paiement, guide completLa même clé utilisée dans le cycle de vie complet d'une commande.