Mettre un lien différent pour chaque client dans le bouton d'un template

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.

Mettre un lien différent pour chaque client dans le bouton d'un template

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.

Scénario

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.

Conversation

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

Code

  1. Créez le template. L'URL termine par {{1}} et url_example donne un exemple complet que Meta relit :

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())
  1. Une fois APPROVED, envoyez-le avec la fin du lien pour ce client :

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())

Règles exactes

ÉlémentRègle
URL du boutonhttps://, un domaine et un chemin, puis {{1}} tout à la fin (https://pay.example.com/p/{{1}})
url_exampleadresse https complète qui commence par la partie fixe, sans {{ ; obligatoire avec {{1}}, refusée sans
button_url_paramfin du lien seulement, 200 caractères au plus, sans espace
Par templateun seul bouton à lien variable
À l'envoibutton_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.