Oui : terminez l'URL du bouton par {{1}} à la création (avec url_example), puis donnez la fin du lien dans button_url_param à chaque envoi.
Astuce
Oui : terminez l'URL du bouton par {{1}} à la création (avec url_example), puis donnez la fin du lien dans button_url_param à chaque envoi.
Vous envoyez une facture ou un suivi de colis : chaque client doit atterrir sur sa page, par exemple un lien de paiement unique. Un bouton de template a normalement une adresse fixe, la même pour tous.
Oui, un bouton peut porter un lien différent par client. À la création, l'adresse se termine par {{1}}. À chaque envoi, vous donnez uniquement la fin du lien ; WhatsApp la colle derrière la partie fixe approuvée.
Vous : POST /v1/messages to=+237690000001 variables=["Awa", "15 000 FCFA"] button_url_param="plk_x7k2m9qa"
Awa : « Bonjour Awa, votre facture de 15 000 FCFA est prête. »
[ Payer ] → https://pay.example.com/p/plk_x7k2m9qa
curl
curl -X POST "https://wa-api.geskap.com/v1/templates" \
-H "Authorization: Bearer $GESKAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "facture_lien_paiement",
"language": "fr",
"category": "UTILITY",
"body_text": "Bonjour {{1}}, votre facture de {{2}} est prête.",
"example_values": ["Awa", "15 000 FCFA"],
"buttons": [
{
"kind": "url",
"text": "Payer",
"url": "https://pay.example.com/p/{{1}}",
"url_example": "https://pay.example.com/p/plk_a1b2c3d4"
}
]
}'
Node.js
const res = await fetch("https://wa-api.geskap.com/v1/templates", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GESKAP_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "facture_lien_paiement",
"language": "fr",
"category": "UTILITY",
"body_text": "Bonjour {{1}}, votre facture de {{2}} est prête.",
"example_values": ["Awa", "15 000 FCFA"],
"buttons": [
{
"kind": "url",
"text": "Payer",
"url": "https://pay.example.com/p/{{1}}",
"url_example": "https://pay.example.com/p/plk_a1b2c3d4"
}
]
}),
});
console.log(res.status, await res.json());
PHP
$ch = curl_init("https://wa-api.geskap.com/v1/templates");
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([
"name" => "facture_lien_paiement",
"language" => "fr",
"category" => "UTILITY",
"body_text" => "Bonjour {{1}}, votre facture de {{2}} est prête.",
"example_values" => ["Awa", "15 000 FCFA"],
"buttons" => [
[
"kind" => "url",
"text" => "Payer",
"url" => "https://pay.example.com/p/{{1}}",
"url_example" => "https://pay.example.com/p/plk_a1b2c3d4",
],
],
]),
]);
$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/templates",
headers={"Authorization": f"Bearer {os.environ['GESKAP_API_KEY']}"},
json={
"name": "facture_lien_paiement",
"language": "fr",
"category": "UTILITY",
"body_text": "Bonjour {{1}}, votre facture de {{2}} est prête.",
"example_values": ["Awa", "15 000 FCFA"],
"buttons": [
{
"kind": "url",
"text": "Payer",
"url": "https://pay.example.com/p/{{1}}",
"url_example": "https://pay.example.com/p/plk_a1b2c3d4",
},
],
},
timeout=15,
)
print(res.status_code, res.json())
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",
"template": {
"name": "facture_lien_paiement",
"language": "fr",
"variables": ["Awa", "15 000 FCFA"],
"button_url_param": "plk_x7k2m9qa"
},
"idempotency_key": "facture-F-2026-0412"
}'
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",
"template": {
"name": "facture_lien_paiement",
"language": "fr",
"variables": ["Awa", "15 000 FCFA"],
"button_url_param": "plk_x7k2m9qa"
},
"idempotency_key": "facture-F-2026-0412"
}),
});
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",
"template" => [
"name" => "facture_lien_paiement",
"language" => "fr",
"variables" => ["Awa", "15 000 FCFA"],
"button_url_param" => "plk_x7k2m9qa",
],
"idempotency_key" => "facture-F-2026-0412",
]),
]);
$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",
"template": {
"name": "facture_lien_paiement",
"language": "fr",
"variables": ["Awa", "15 000 FCFA"],
"button_url_param": "plk_x7k2m9qa",
},
"idempotency_key": "facture-F-2026-0412",
},
timeout=15,
)
print(res.status_code, res.json())
| Élément | Règle |
|---|---|
| URL du bouton | https://, un domaine et un chemin, puis {{1}} tout à la fin (https://pay.example.com/p/{{1}}) |
| url_example | adresse https complète qui commence par la partie fixe, sans {{ ; obligatoire avec {{1}}, refusée sans |
| button_url_param | fin du lien seulement, 200 caractères au plus, sans espace |
| Par template | un seul bouton à lien variable |
| À l'envoi | button_url_param obligatoire si le template a ce bouton, refusé sinon (400) |
Attention
Piège : button_url_param ne reçoit jamais l'adresse complète ni un autre domaine : https://…, //… et www. sont refusés, et une fin qui ferait changer d'hôte aussi. Donnez plk_x7k2m9qa ou CMD-1042, pas https://pay.example.com/p/plk_x7k2m9qa.
Attention
Piège : une URL sans chemin (https://pay.example.com{{1}}) est refusée à la création, car la fin du lien pourrait prolonger le nom de domaine. Mettez toujours un /chemin/ avant {{1}}.
Attention
Piège : un lien wa.me dans un bouton URL est refusé par Meta (erreur 2388081), et l'API le refuse avant l'envoi. Pour ouvrir une conversation ou recevoir un choix, utilisez un bouton de réponse rapide.
Attention
Piège : le lien est visible de qui reçoit le message. Mettez-y un jeton aléatoire à usage unique et de courte durée, jamais un numéro de client ou de commande devinable seul pour une page sensible : voir Données sensibles : lien unique et PIN.
Hors de ce besoin, un bouton à lien fixe se crée avec "kind": "url" et une URL sans {{1}} (ni url_example). Premier envoi, statuts et erreurs : Écrire à un client qui ne vous a jamais écrit.
Preuve — vérifié dans le code de l'API : templates.py, api_send.py, api_schemas.py, test_button_url_variable.py, WHATSAPP_DEV_API.md.