Recevez message.status et message.inbound en temps réel, signés HMAC-SHA256. Vérifiez toujours la signature.
Plutôt que de sonder GET /v1/messages/{id}, enregistrez UN endpoint HTTPS et nous y postons les événements en temps réel : mises à jour de statut de vos envois, et messages entrants de vos clients. Nous ne faisons tourner aucun bot sur votre numéro — nous vous relayons l'entrant.
POST /v1/console/webhook
Attention — Le secret n'est affiché qu'une fois
À l'enregistrement, le secret de signature est renvoyé une seule fois — stockez-le. Un GET ultérieur renvoie { url, configured } sans jamais le secret ; DELETE désactive le webhook.
Chaque événement est un POST vers votre URL. La livraison est best-effort (~8 s de timeout, pas de retries automatiques pour l'instant) : répondez 2xx rapidement et faites le gros du travail de façon asynchrone.
En-tête de signature
X-Camairetech-Signature: sha256=<hex hmac-sha256 du corps brut>
Note — Chaque envoi est journalisé — surveillez le vôtre
Comme il n'y a pas de retry automatique, une livraison échouée vers votre URL est silencieuse pour vous. La console (Logs, catégorie « Webhook ») et GET /v1/logs?category=webhook journalisent chaque envoi vers votre endpoint avec son statut (succès/échec) et la raison de l'échec — c'est le seul moyen de savoir qu'un événement n'est jamais arrivé chez vous. Les 6 autres catégories (Envois, Réception, Crédits, Rejets, Clés, Numéros) couvrent le reste de l'activité de votre compte.
message.status
{
"event": "message.status",
"data": {
"id": "6f1c…",
"wa_message_id": "wamid…",
"status": "delivered",
"recipient_masked": "2376****3456",
"error": null
},
"timestamp": "2026-08-04T09:14:07.123456+00:00"
}
message.inbound
{
"event": "message.inbound",
"data": {
"from": "+237699123456",
"contact_name": "Jean Mballa",
"number": "1234567890",
"wa_message_id": "wamid…",
"type": "text",
"text": "Bonjour",
"media_id": null,
"media_url": null
},
"timestamp": "…"
}
type vaut text ou un type média (image, audio, video, document, sticker). media_id est l'identifiant Meta du média ; quand le média est hébergé, media_url est une URL absolue (GET /v1/media/{id}, redirection 302 vers une URL signée éphémère, conservée pour votre fenêtre de rétention) — null si rien n'est encore hébergé ou si l'hébergement est désactivé pour votre compte. number est le phone_number_id qui a reçu le message — repassez-le en from sur POST /v1/messages/reply pour répondre depuis le même numéro. Le même message est aussi stocké et relisible via GET /v1/messages/inbound — voir « Recevoir des messages ».
message.echo
{
"event": "message.echo",
"data": {
"to": "+237699123456",
"number": "1234567890",
"wa_message_id": "wamid…",
"type": "text",
"text": "Bonjour",
"media_id": null
},
"timestamp": "…"
}
message.echo est le pendant sortant de message.inbound : vous (ou un coéquipier) avez envoyé ce message à ce contact depuis l'application WhatsApp Business elle-même (Coexistence), pas via cette API — nous le relayons pour que votre historique reste complet. to est le contact destinataire ; number est le phone_number_id qui a envoyé le message. Média : métadonnées seules (media_id + media_mime, jamais de media_url) — ce média n'est pas téléchargeable depuis Meta. Même dualité stockage/push que message.inbound : relisible via GET /v1/messages/inbound?direction=echo (le champ direction distingue in de echo).
Recalculez le HMAC sur les octets bruts du corps (avant tout parsing JSON) et comparez à l'en-tête ; rejetez en cas d'écart. Re-sérialiser le corps changerait la signature.
Vérification (Python)
import hmac, hashlib
def verify(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header or "")
Vérification (Node.js)
const crypto = require("crypto");
function verify(rawBody, header, secret) {
const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header || ""));
}