Oui — une question par message, les réponses gardées dans votre base (l'API ne retient aucun état), « annuler » à tout moment, et un drapeau d'urgence pour les demandes à traiter en priorité.
Astuce
Oui — une question par message, les réponses gardées dans votre base (l'API ne retient aucun état), « annuler » à tout moment, et un drapeau d'urgence pour les demandes à traiter en priorité.
Un client veut une consultation. Vous lui posez 4 questions, une par message, en boutons ou en liste quand les choix sont connus, en texte libre pour le récit. À la fin, vous créez le dossier chez vous, avec un drapeau « urgent » si le client a une échéance proche.
Oui, c'est possible sans template, dans les 24 h : chaque réponse du client rouvre la fenêtre. Il n'y a pas de « formulaire » côté API : c'est votre serveur qui garde l'état (à quelle question en est ce numéro) et envoie la suivante. C'est la logique de l'application de démonstration, en production.
Client : Je voudrais voir un avocat
Vous : De quel domaine s'agit-il ? [ Voir les domaines ] (liste)
Client : (touche « Droit du travail »)
Vous : Vous êtes plutôt… [ Un particulier ] [ Une entreprise ]
Client : (touche « Un particulier »)
Vous : Décrivez votre situation en quelques phrases. [ Annuler ]
Client : Licencié sans préavis, l'employeur refuse de payer mes indemnités.
Vous : Avez-vous une date limite ?
[ Moins de 7 jours ] [ Ce mois-ci ] [ Pas de date ]
Client : (touche « Moins de 7 jours »)
Vous : Situation transmise (réf. DEM-17). Vous serez rappelé aujourd'hui.
Envoi d'une étape à choix (les étapes à texte libre utilisent le texte libre) :
curl
curl -X POST "https://wa-api.geskap.com/v1/messages/interactive" \
-H "Authorization: Bearer $GESKAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+237690000001",
"body": "Avez-vous une date limite (audience, convocation, délai) ?",
"footer": "Tapez « annuler » pour arrêter",
"buttons": [
{ "id": "echeance:urgent", "title": "Moins de 7 jours" },
{ "id": "echeance:mois", "title": "Ce mois-ci" },
{ "id": "echeance:aucune", "title": "Pas de date" }
]
}'
Node.js
const res = await fetch("https://wa-api.geskap.com/v1/messages/interactive", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GESKAP_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"to": "+237690000001",
"body": "Avez-vous une date limite (audience, convocation, délai) ?",
"footer": "Tapez « annuler » pour arrêter",
"buttons": [
{ "id": "echeance:urgent", "title": "Moins de 7 jours" },
{ "id": "echeance:mois", "title": "Ce mois-ci" },
{ "id": "echeance:aucune", "title": "Pas de date" }
]
}),
});
console.log(res.status, await res.json());
PHP
$ch = curl_init("https://wa-api.geskap.com/v1/messages/interactive");
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",
"body" => "Avez-vous une date limite (audience, convocation, délai) ?",
"footer" => "Tapez « annuler » pour arrêter",
"buttons" => [
["id" => "echeance:urgent", "title" => "Moins de 7 jours"],
["id" => "echeance:mois", "title" => "Ce mois-ci"],
["id" => "echeance:aucune", "title" => "Pas de date"],
],
]),
]);
$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/interactive",
headers={"Authorization": f"Bearer {os.environ['GESKAP_API_KEY']}"},
json={
"to": "+237690000001",
"body": "Avez-vous une date limite (audience, convocation, délai) ?",
"footer": "Tapez « annuler » pour arrêter",
"buttons": [
{"id": "echeance:urgent", "title": "Moins de 7 jours"},
{"id": "echeance:mois", "title": "Ce mois-ci"},
{"id": "echeance:aucune", "title": "Pas de date"},
],
},
timeout=15,
)
print(res.status_code, res.json())
Moteur minimal côté serveur : un état par numéro, une étape à la fois.
// state[from] = { step: 0, answers: {} } — dans VOTRE base, pas en mémoire
const STEPS = [
{ key: "domaine", ask: sendDomainList },
{ key: "qualite", ask: sendQualiteButtons },
{ key: "resume", ask: (to) => sendText(to, "Décrivez votre situation en quelques phrases."),
validate: (t) => t.trim().length < 15 ? "Un peu plus de détails, s'il vous plaît." : null },
{ key: "echeance", ask: sendEcheanceButtons },
];
async function onInbound(data) {
const s = (await db.getState(data.from)) ?? null;
if (!s) return; // pas de formulaire en cours
if (/^(annuler|stop)$/i.test(data.text.trim())) { // « annuler » à tout moment
await db.clearState(data.from);
return sendText(data.from, "C'est annulé. Écrivez-moi quand vous voulez.");
}
const step = STEPS[s.step];
const value = data.button_id ? data.button_id.split(":")[1] : data.text; // choix = fin de button_id, récit = text
const err = step.validate?.(value);
if (err) return sendText(data.from, err); // on redemande la même étape
s.answers[step.key] = value;
if (s.step + 1 < STEPS.length) {
s.step += 1; await db.saveState(data.from, s);
return STEPS[s.step].ask(data.from);
}
const urgent = s.answers.echeance === "urgent"; // drapeau d'urgence
await db.createRequest({ phone: data.from, answers: s.answers, urgent });
await db.clearState(data.from);
await sendText(data.from, urgent ? "Transmis : réponse aujourd'hui." : "Transmis : réponse sous 24 h ouvrées.");
}
Dans la démonstration, les étapes peuvent aussi être conditionnelles (une étape est sautée selon les réponses précédentes), chaque étape affiche « Tapez « annuler » pour arrêter », et la réponse finale n'est pas la même pour une demande urgente (« en priorité », « aujourd'hui »).
| Élément | Limite |
|---|---|
| Choix par étape | 3 en boutons, 10 en liste |
| Texte d'une question (body) | 1 024 caractères en interactif, 4 096 en texte libre |
| État du formulaire | aucun côté API : à stocker chez vous, par numéro |
| Fenêtre entre deux réponses | 24 h : chaque réponse du client la relance |
Attention
Piège : l'API ne se souvient pas de l'étape. Si vous ne stockez pas l'état par numéro, un « Oui » tapé au milieu du formulaire est pris pour une nouvelle conversation. Donnez à chaque id un préfixe d'étape (qualite:particulier) et ignorez un id qui ne correspond pas à l'étape en cours (vieux bouton).
Attention
Piège : un client qui abandonne laisse un formulaire ouvert. Prévoyez « annuler » (ci-dessus) et une expiration de l'état (par exemple 24 h) : au-delà, la fenêtre est fermée et seule une template peut le relancer. Ne posez jamais de donnée sensible (mot de passe, pièce d'identité) : voir Données sensibles derrière un lien + PIN.
Preuve — vérifié dans le code de l'API : public_api.py, webhooks.py ; appliqué par la démo WhatsApp : avocat, engine.