Le clic arrive sur votre webhook en message.inbound, type text, avec le libellé touché dans text et votre identifiant dans button_id.
Astuce
Le clic arrive sur votre webhook en message.inbound, type text, avec le libellé touché dans text et votre identifiant dans button_id.
Vous avez envoyé des boutons, un menu en liste ou un template à boutons de réponse rapide. Le client touche un choix. Votre serveur doit savoir lequel pour continuer la conversation.
{ "event": "message.inbound",
"data": { "from": "+237690000001", "contact_name": "Awa", "number": "100000000000001",
"wa_message_id": "wamid.HBg…", "message_id": "9d3e…",
"type": "text", "text": "Prendre RDV", "button_id": "rdv:new",
"media_id": null, "media_url": null, "media_mime": null,
"timestamp": "2026-10-30T09:14:07+00:00" },
"timestamp": "2026-10-30T09:14:07+00:00" }
| Champ | Contenu |
|---|---|
| type | "text" pour un clic, comme pour un message tapé |
| text | le libellé touché (titre du bouton ou de la ligne) |
| button_id | l'id que vous avez donné au bouton ou à la ligne ; pour un bouton de réponse rapide de template, son libellé (Meta le renvoie comme payload) ; null si le client a tapé le texte lui-même |
| number | le numéro (phone_number_id) qui a reçu le clic : repassez-le en from pour répondre du même numéro |
Le gestionnaire vérifie la signature, puis aiguille sur button_id :
Node.js
// Express — receive the webhook, verify it, then react.
import express from "express";
import crypto from "node:crypto";
const app = express();
const SECRET = process.env.GESKAP_WEBHOOK_SECRET; // shown once when you register the webhook
// Keep the RAW bytes: the signature covers them, not a re-serialized JSON.
app.post("/webhooks/whatsapp", express.raw({ type: "application/json" }), (req, res) => {
const expected = "sha256=" + crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
const got = req.get("X-Camairetech-Signature") || "";
if (got.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
return res.sendStatus(401);
}
res.sendStatus(200); // answer fast: one attempt, 8 s timeout, no retry
const { event, data } = JSON.parse(req.body.toString("utf8"));
if (event === "message.inbound") onInbound(data); // your code
});
app.listen(3000);
PHP
<?php
// Receive the webhook, verify it, then react.
$raw = file_get_contents("php://input"); // raw bytes, before json_decode
$expected = "sha256=" . hash_hmac("sha256", $raw, getenv("GESKAP_WEBHOOK_SECRET"));
$got = $_SERVER["HTTP_X_CAMAIRETECH_SIGNATURE"] ?? "";
if (!hash_equals($expected, $got)) {
http_response_code(401);
exit;
}
http_response_code(200);
$payload = json_decode($raw, true);
if ($payload["event"] === "message.inbound") {
on_inbound($payload["data"]); // your code
}
Python
# Flask — receive the webhook, verify it, then react.
import hashlib, hmac, json, os
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["GESKAP_WEBHOOK_SECRET"].encode()
@app.post("/webhooks/whatsapp")
def whatsapp_webhook():
raw = request.get_data() # raw bytes, before any JSON parsing
expected = "sha256=" + hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-Camairetech-Signature", "")):
abort(401)
payload = json.loads(raw)
if payload["event"] == "message.inbound":
on_inbound(payload["data"]) # your code
return "", 200
Dans onInbound : si data.button_id vaut rdv:new, lancez la prise de rendez-vous ; s'il est null, traitez data.text comme une saisie libre.
Attention
Piège : n'aiguillez jamais sur le libellé. Deux boutons peuvent porter le même titre (« Détails » sur deux produits), et un client peut taper le mot à la main. button_id est la seule clé fiable. Mettez-y ce dont vous avez besoin pour agir : order:1042, slot:2026-11-03T09:00.
Attention
Piège : il n'y a pas de type interactive ni button dans le webhook : un clic arrive en type: "text". Testez button_id, pas type.
Pas de webhook ? Les mêmes messages, avec leur texte, se relisent par GET /v1/messages/inbound (voir Archiver messages et médias).
Preuve — vérifié dans le code de l'API : meta_webhooks.py, webhooks.py, client_webhooks.py ; appliqué par la démo WhatsApp : webhook.