Oui pour les templates : ajoutez idempotency_key (128 caractères max) dans le corps de POST /v1/messages ; le rejeu renvoie le message d'origine sans renvoyer ni redébiter. Non pour reply et interactive, qui n'ont pas cette clé.
Astuce
Oui pour les templates : ajoutez idempotency_key (128 caractères max) dans le corps de POST /v1/messages ; le rejeu renvoie le message d'origine sans renvoyer ni redébiter. Non pour reply et interactive, qui n'ont pas cette clé.
Votre serveur envoie un reçu de paiement. L'appel met 12 secondes, votre client HTTP abandonne et réessaie. Sans protection, le client reçoit deux reçus et vous payez deux fois. Oui, il existe une protection : idempotency_key. Un second appel avec la même clé ne renvoie pas de message : il retourne le résultat du premier.
Appel 1 POST /v1/messages idempotency_key="recu-8842" → envoyé, 1 débit, id=9d3e…
(coupure réseau, votre client réessaie)
Appel 2 POST /v1/messages idempotency_key="recu-8842" → renvoie id=9d3e…, rien renvoyé, rien débité
curl
curl -X POST "https://wa-api.geskap.com/v1/messages" \
-H "Authorization: Bearer $GESKAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+237690000001",
"idempotency_key": "recu-8842",
"template": {
"name": "recu_paiement",
"language": "fr",
"variables": ["Awa", "15 000 FCFA"]
}
}'
Node.js
const res = await fetch("https://wa-api.geskap.com/v1/messages", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GESKAP_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"to": "+237690000001",
"idempotency_key": "recu-8842",
"template": {
"name": "recu_paiement",
"language": "fr",
"variables": ["Awa", "15 000 FCFA"]
}
}),
});
console.log(res.status, await res.json());
PHP
$ch = curl_init("https://wa-api.geskap.com/v1/messages");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("GESKAP_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"to" => "+237690000001",
"idempotency_key" => "recu-8842",
"template" => [
"name" => "recu_paiement",
"language" => "fr",
"variables" => ["Awa", "15 000 FCFA"],
],
]),
]);
$res = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $res;
Python
import os, requests
res = requests.request(
"POST",
"https://wa-api.geskap.com/v1/messages",
headers={"Authorization": f"Bearer {os.environ['GESKAP_API_KEY']}"},
json={
"to": "+237690000001",
"idempotency_key": "recu-8842",
"template": {
"name": "recu_paiement",
"language": "fr",
"variables": ["Awa", "15 000 FCFA"],
},
},
timeout=15,
)
print(res.status_code, res.json())
La clé va dans le corps JSON de la requête, à côté de to et de template. Ce n'est pas un en-tête HTTP : un en-tête Idempotency-Key est ignoré. Elle fait 128 caractères au plus.
Le même id, le même status, le même wa_message_id, le même credits_charged. credits_remaining n'y figure pas (rien n'a été débité) et l'en-tête X-Credits-Remaining n'est pas posé : ne vous en servez pas pour reconnaître un rejeu.
Une clé par message logique, jamais par tentative. Prenez un identifiant de votre métier : recu-8842, rdv-1031-rappel-veille, commande-557-livree. Générer une valeur au hasard à chaque tentative annule la protection.
Réessayez avec la même clé après un délai d'attente, une coupure ou une erreur serveur. Si l'envoi a échoué pour de bon (402 crédits insuffisants, ou 502 refus de Meta), la clé est libérée : le même appel avec la même clé repart normalement, par exemple après avoir rechargé.
| Situation | Ce que vous obtenez |
|---|---|
| Premier appel réussi, rejeu | Le message d'origine, sans renvoi ni débit |
| Deux appels simultanés avec la même clé | Un seul envoi ; l'autre reçoit le résultat du premier, parfois avec status: "pending" |
| Premier appel refusé (402, 502) | La clé est libérée, le rejeu est un vrai nouvel essai |
POST /v1/messages/reply et POST /v1/messages/interactive n'ont pas de champ idempotency_key. Un rejeu envoie un second message. Dédoublonnez de votre côté : gardez le wa_message_id du message entrant auquel vous répondez, et ne répondez qu'une fois par message reçu (voir Archiver messages et médias).
Attention
Piège : la clé retrouve le message d'origine sans comparer le contenu. Réutiliser recu-8842 pour un autre destinataire ou un autre template renvoie le premier envoi, sans rien envoyer de nouveau. Une clé = un message.
Attention
Piège : l'idempotence ne couvre que votre propre rejeu. Elle ne dédoublonne pas deux événements différents de votre système qui produisent deux clés différentes pour le même fait : construisez la clé à partir du fait (la commande), pas de l'événement technique.
Par le connecteur MCP, deux envois identiques dans les 5 minutes sont déjà regroupés automatiquement, et une clé explicite l'emporte. Pour le coût d'un envoi réussi, voir Coût d'un message.
Preuve — vérifié dans le code de l'API : api_schemas.py, api_send.py, api_usage.py, 040_dev_api_usage.sql, mcp_server.py.