Envoyer un rappel de rendez-vous la veille, avec boutons Confirmer et Annuler

Oui : votre serveur lance un envoi planifié la veille (l'API n'a pas de planificateur), avec un template UTILITY à variables et deux boutons de réponse rapide ; le clic revient sur votre webhook.

Envoyer un rappel de rendez-vous la veille, avec boutons Confirmer et Annuler

Astuce
Oui : votre serveur lance un envoi planifié la veille (l'API n'a pas de planificateur), avec un template UTILITY à variables et deux boutons de réponse rapide ; le clic revient sur votre webhook.

Scénario

Une clinique veut réduire les absences : la veille de chaque rendez-vous, le patient reçoit un rappel avec deux boutons, « Confirmer » et « Annuler ». Un clic suffit, il n'a rien à taper.

Oui, c'est faisable. Deux choses à savoir : l'API n'a pas de planificateur (c'est votre serveur qui décide de l'heure d'envoi, par exemple une tâche cron à 17 h), et le rappel part hors des 24 h, donc en template. Pas de template, pas de message : voir Écrire à un client qui ne vous a jamais écrit.

Conversation

Cron    : 17:00, rendez-vous de demain trouvés dans votre base
Vous    : POST /v1/messages  to=+237690000001  variables=["Awa", "jeudi 5 novembre", "09:00", "Clinique Exemple"]
Awa     : « Bonjour Awa, rappel de votre rendez-vous demain jeudi 5 novembre à 09:00 chez Clinique Exemple. »
          [ Confirmer ]  [ Annuler ]
Awa     : (touche « Confirmer »)
Webhook : message.inbound  from=+237690000001  type=text  text="Confirmer"

Code

  1. Une seule fois, créez le template (catégorie UTILITY, deux boutons quick_reply) et attendez APPROVED :

curl

curl -X POST "https://wa-api.geskap.com/v1/templates" \
  -H "Authorization: Bearer $GESKAP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "rappel_rdv_veille",
  "language": "fr",
  "category": "UTILITY",
  "body_text": "Bonjour {{1}}, rappel de votre rendez-vous demain {{2}} à {{3}} chez {{4}}.",
  "example_values": ["Awa", "jeudi 5 novembre", "09:00", "Clinique Exemple"],
  "buttons": [
    { "kind": "quick_reply", "text": "Confirmer" },
    { "kind": "quick_reply", "text": "Annuler" }
  ]
}'

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": "rappel_rdv_veille",
    "language": "fr",
    "category": "UTILITY",
    "body_text": "Bonjour {{1}}, rappel de votre rendez-vous demain {{2}} à {{3}} chez {{4}}.",
    "example_values": ["Awa", "jeudi 5 novembre", "09:00", "Clinique Exemple"],
    "buttons": [
      { "kind": "quick_reply", "text": "Confirmer" },
      { "kind": "quick_reply", "text": "Annuler" }
    ]
  }),
});
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" => "rappel_rdv_veille",
    "language" => "fr",
    "category" => "UTILITY",
    "body_text" => "Bonjour {{1}}, rappel de votre rendez-vous demain {{2}} à {{3}} chez {{4}}.",
    "example_values" => ["Awa", "jeudi 5 novembre", "09:00", "Clinique Exemple"],
    "buttons" => [
      ["kind" => "quick_reply", "text" => "Confirmer"],
      ["kind" => "quick_reply", "text" => "Annuler"],
    ],
  ]),
]);
$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": "rappel_rdv_veille",
        "language": "fr",
        "category": "UTILITY",
        "body_text": "Bonjour {{1}}, rappel de votre rendez-vous demain {{2}} à {{3}} chez {{4}}.",
        "example_values": ["Awa", "jeudi 5 novembre", "09:00", "Clinique Exemple"],
        "buttons": [
            {"kind": "quick_reply", "text": "Confirmer"},
            {"kind": "quick_reply", "text": "Annuler"},
        ],
    },
    timeout=15,
)
print(res.status_code, res.json())
  1. Chaque soir, pour chaque rendez-vous du lendemain, envoyez-le :

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": "rappel_rdv_veille",
    "language": "fr",
    "variables": ["Awa", "jeudi 5 novembre", "09:00", "Clinique Exemple"]
  },
  "idempotency_key": "rappel-RDV-2041"
}'

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": "rappel_rdv_veille",
      "language": "fr",
      "variables": ["Awa", "jeudi 5 novembre", "09:00", "Clinique Exemple"]
    },
    "idempotency_key": "rappel-RDV-2041"
  }),
});
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" => "rappel_rdv_veille",
      "language" => "fr",
      "variables" => ["Awa", "jeudi 5 novembre", "09:00", "Clinique Exemple"],
    ],
    "idempotency_key" => "rappel-RDV-2041",
  ]),
]);
$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": "rappel_rdv_veille",
            "language": "fr",
            "variables": ["Awa", "jeudi 5 novembre", "09:00", "Clinique Exemple"],
        },
        "idempotency_key": "rappel-RDV-2041",
    },
    timeout=15,
)
print(res.status_code, res.json())
  1. Votre webhook reçoit le clic. Retrouvez le rendez-vous par le numéro from : c'est celui à qui vous avez envoyé le rappel. Voir Savoir quel bouton le client a touché et Reconnaître un client à son numéro.
Attention
Piège : un bouton de template n'embarque pas votre identifiant de rendez-vous. Le clic ne porte que le libellé touché (text, et button_id qui vaut le payload Meta, à défaut le libellé). Pour savoir quel rendez-vous est confirmé, gardez de votre côté « dernier rappel envoyé à ce numéro » et rattachez-y la réponse. Si un patient a deux rendez-vous le même jour, mettez les deux dans le même rappel (une variable) plutôt que d'envoyer deux rappels.
Attention
Piège : deux boutons maximum par template (3 pour des boutons sans template), libellé de 25 caractères au plus. Pour proposer un nouveau créneau après un « Annuler », attendez la réponse du client : elle rouvre la fenêtre de 24 h, et vous pouvez alors envoyer des boutons de créneau sans nouveau template. Voir aussi Confirmer, annuler, modifier.
Attention
Piège : si le client ne répond pas, rien n'arrive sur votre webhook : traitez l'absence de réponse comme « non confirmé » côté serveur. Le statut de livraison (delivered, read) arrive, lui, par message.status.

Envoyez avec une idempotency_key (ex. rappel-RDV-2041) : si votre tâche cron rejoue, le client ne reçoit pas deux rappels (voir Idempotence).

Preuve — vérifié dans le code de l'API : api_schemas.py, api_send.py, meta_webhooks.py.