Documentation
API Officielle WhatsApp
Comment connecter votre numéro à la plateforme de Meta, envoyer des messages et des modèles, et recevoir les événements dans votre système.
Mis à jour le 12 novembre 2026
Sommaire
- Vue d'ensemble
- Démarrer
- 1. Connecter le numéro
- 2. Configurer le webhook et la clé
- 3. Utiliser le flux n8n prêt à l'emploi
- Authentification
- Envoyer des messages
- Types de contenu
- Boutons de réponse
- Fenêtre de 24 heures et modèles
- Envoyer un modèle
- Créer des modèles
- Événements
- message.received
- template.status
- connection.updated
- Vérifier la signature
- État des envois
- Relier l'état au message
- Erreurs
- Livraison des événements
- Limites du canal
- Coûts
Vue d'ensemble
L'API Officielle relie votre numéro WhatsApp à la plateforme de Meta (WhatsApp Cloud API) et vous donne une API simple pour envoyer des messages et recevoir ce que vos clients écrivent. Elle fonctionne avec n8n, avec votre CRM ou avec votre propre code.
Ce que vous recevez :
une clé d'accès pour envoyer des messages et des modèles, et consulter l'état de vos numéros ;
un webhook : chaque message reçu et chaque changement d'état arrivent à l'adresse de votre choix, signés ;
l'espace client, où vous connectez le numéro, créez des modèles de message et suivez votre consommation.
Toutes les requêtes vont vers :
https://wa.automate-ai.digitalLes messages sont facturés par Meta, directement sur votre compte (voir Coûts). L'abonnement couvre le numéro et l'API.
Démarrer
1. Connecter le numéro
Dans l'espace client, sous Mes produits, cliquez sur Connecter le numéro officiel et suivez le parcours de Meta : choisissez ou créez le compte WhatsApp Business, confirmez le numéro et autorisez la connexion. À la fin, le numéro apparaît comme connecté.
Meta demande deux choses :
la vérification de l'entreprise auprès de Meta, pour sortir des limites d'envoi initiales ;
un moyen de paiement sur le compte WhatsApp Business : sans lui, Meta cesse d'envoyer les messages.
2. Configurer le webhook et la clé
Dans l'espace client, sous Intégration technique :
Webhook : indiquez l'adresse qui recevra les événements. L'espace client affiche une seule fois le secret du webhook, qui sert à vérifier la signature. Conservez-le.
Clé d'accès : générez la clé. Elle n'apparaît, elle aussi, qu'une seule fois.
3. Utiliser le flux n8n prêt à l'emploi
Si vous utilisez n8n, le même endroit propose le bouton Télécharger le flux n8n. Le flux vérifie déjà la signature, répond aux messages et envoie des modèles. Importez-le, activez-le, collez son URL de production à l'étape Webhook, puis collez le secret et la clé là où le flux l'indique.
Authentification
Chaque requête porte la clé dans l'en-tête x-api-key :
curl https://wa.automate-ai.digital/instances/ID_DU_NUMERO \
-H "x-api-key: wak_xxxxxxxx_xxxxxxxx"Toutes les réponses ont la même forme :
{ "ok": true, "data": { "...": "..." } }{ "ok": false, "error": { "code": "INSTANCE_NOT_FOUND", "message": "…", "retryable": false } }L'ID_DU_NUMERO (instanceId) figure dans chaque événement reçu par votre webhook.
Une nouvelle clé invalide la précédente dès sa génération. Tout ce qui utilisait l'ancienne cesse de fonctionner jusqu'à sa mise à jour.
Envoyer des messages
curl -X POST https://wa.automate-ai.digital/instances/ID_DU_NUMERO/messages \
-H "x-api-key: $CLE" -H "content-type: application/json" \
-d '{
"to": { "kind": "contact", "id": "33612345678" },
"content": { "type": "text", "body": "Bonjour ! Votre commande est en route." },
"idempotencyKey": "commande-1234-confirmation"
}'to.idest le numéro au format international, sans+ni espaces.idempotencyKey(facultatif, recommandé) : si la même requête est répétée avec la même clé, le message ne part pas deux fois.replyTo(facultatif) : leproviderMessageIddu message auquel vous répondez, pour l'afficher en citation.
La réponse est 202 avec l'identifiant du message :
{ "ok": true, "data": { "messageId": "44444444-…", "queuedAt": "2026-11-12T10:00:00.000Z" } }202 signifie que le message est entré dans la file, pas qu'il a été livré. Le résultat arrive ensuite sur le webhook, sous forme de message.status (voir État des envois).
Types de contenu
| Champs |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
media vaut { "kind": "url", "url": "https://…" }, ou { "kind": "base64", "data": "…", "mimeType": "image/png" }.
Boutons de réponse
{
"to": { "kind": "contact", "id": "33612345678" },
"content": {
"type": "buttons",
"body": "Confirmez-vous le rendez-vous de demain à 15 h ?",
"buttons": [
{ "id": "confirmer", "title": "Confirmer" },
{ "id": "reporter", "title": "Reporter" }
]
}
}Quand le client appuie sur un bouton, vous recevez un message.received dont le content.type est interactive_reply, avec le même id dans replyId. C'est sur lui que votre flux s'appuie pour décider.
Fenêtre de 24 heures et modèles
C'est la règle la plus importante du canal officiel :
dans les 24 heures qui suivent le dernier message du client, vous pouvez envoyer des messages libres (
/messages) et des modèles ;en dehors de cette fenêtre, uniquement des modèles approuvés par Meta (
/messages/template).
Une réponse à un message que vous venez de recevoir est toujours dans la fenêtre. Pour lancer une conversation (rappel, confirmation, avis), on utilise toujours un modèle.
Chaque message.received contient windowExpiresAt, le moment où la fenêtre se ferme.
Un message libre hors fenêtre est accepté avec 202 puis échoue sur le webhook, avec le failureCode WINDOW_CLOSED. L'API ne le remplace pas d'elle-même par un modèle.
Envoyer un modèle
curl -X POST https://wa.automate-ai.digital/instances/ID_DU_NUMERO/messages/template \
-H "x-api-key: $CLE" -H "content-type: application/json" \
-d '{
"to": { "kind": "contact", "id": "33612345678" },
"templateName": "commande_confirmee",
"languageCode": "fr",
"bodyParameters": ["Marie", "A-12"],
"buttonParameters": [{ "type": "url", "index": 1, "text": "A-12" }],
"idempotencyKey": "commande-A-12-confirmee"
}'Champ | À quoi il sert |
|---|---|
| le nom du modèle, tel qu'il apparaît dans l'espace client |
| la langue du modèle ( |
| les valeurs de |
| la valeur de la variable de l'en-tête : du texte, ou une image |
| la valeur de chaque bouton de lien à partie variable |
Bouton de lien à partie variable. Dans un modèle avec le lien https://boutique.fr/commande/{{1}}, chaque envoi doit indiquer ce qui remplace {{1}}. index est la position du bouton dans le modèle, en partant de 0 et en comptant tous les boutons (réponses rapides et téléphone compris). text est seulement la partie variable (A-12), jamais le lien entier. Sans cette valeur, Meta refuse l'envoi.
Créer des modèles
Dans l'espace client, sur le numéro officiel, onglet Modèles, cliquez sur Créer un modèle. Il y a quatre étapes : nom et catégorie, texte avec variables et exemples, boutons, et relecture. Meta examine chaque modèle, en général entre quelques minutes et quelques heures.
Utilitaire : avis sur quelque chose que le client a demandé (commande, rendez-vous, paiement). Approuvé plus vite.
Marketing : promotions et nouveautés.
Les modèles avec image en en-tête, carrousel ou authentification se créent dans le WhatsApp Manager de Meta. L'espace client propose un raccourci.
Quand Meta décide, vous recevez un événement template.status (voir Événements). Seul un modèle à l'état approved peut être envoyé.
Événements
Chaque événement arrive sur votre webhook en POST, avec un corps JSON. Il y a quatre types :
| Quand |
|---|---|
| le client a écrit |
| un envoi a changé d'état : |
| l'état du numéro a changé |
| un modèle a été approuvé, refusé, mis en pause… |
Ignorez les types que vous ne connaissez pas, en répondant 2xx. La liste va s'allonger, et un flux qui suppose « si ce n'est pas A, c'est B » traite un nouveau type comme ce qu'il n'est pas.
message.received
{
"type": "message.received",
"eventId": "0f0f6ab7-2a2f-4f0b-9b6a-9a5f7d5d31c2",
"instanceId": "0d9b1b6f-6a2a-4a4c-9f66-3f6a3f0b6a2a",
"provider": "cloud_api",
"occurredAt": "2026-11-12T18:41:07.000Z",
"providerMessageId": "wamid.HBgM…",
"from": "33612345678",
"pushName": "Marie",
"content": { "type": "text", "body": "Bonjour, où en est ma commande ?" },
"windowExpiresAt": "2026-11-13T18:41:07.000Z"
}Types de content :
| Champs |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Le storageUrl du média est temporaire : il sert à télécharger le fichier tout de suite, pas à le conserver.
Si le regroupement des messages est activé sur votre numéro, les textes consécutifs d'un même client arrivent en un seul événement, séparés par des retours à la ligne. Cet événement contient tous les identifiants dans providerMessageIds.
template.status
{
"type": "template.status",
"instanceId": "0d9b1b6f-…",
"template": { "id": "806312974732579", "name": "commande_confirmee", "language": "fr", "category": "utility" },
"status": "rejected",
"reason": "invalid_format",
"rejection": { "reason": "…", "recommendation": "…" }
}status est en minuscules : pending, approved, rejected, paused, disabled, entre autres. Meta ajoute des états sans prévenir : traitez ceux qui vous intéressent et ignorez les autres. Un compte avec plusieurs numéros reçoit un avis par numéro ; dédupliquez par template.id avec status.
connection.updated
Contient status (connected, disconnected, logged_out…) et requiresReauth. Avec requiresReauth: true, le numéro doit être reconnecté dans l'espace client : l'autorisation de Meta n'est plus valable.
Vérifier la signature
Chaque requête du webhook contient :
x-timestamp: 1794519022
x-signature: sha256=9c1f…La signature est un HMAC-SHA256, calculé avec le secret du webhook, sur x-timestamp + "." + corps brut. Pour la vérifier :
refusez la requête s'il manque
x-signatureoux-timestamp;refusez-la si
x-timestamps'écarte de plus de 5 minutes de votre horloge ;calculez le HMAC sur le corps brut, octet par octet. Si vous resérialisez le JSON, la signature échoue pour certains événements ;
comparez avec une comparaison à temps constant.
const crypto = require("crypto");
function signatureValide(secret, timestamp, corpsBrut, signature, signatureSuivante) {
if (!signature || !timestamp) return false;
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
const attendu = Buffer.from(
"sha256=" + crypto.createHmac("sha256", secret).update(`${timestamp}.${corpsBrut}`).digest("hex")
);
const correspond = (h) => !!h && Buffer.from(h).length === attendu.length && crypto.timingSafeEqual(Buffer.from(h), attendu);
return correspond(signature) || correspond(signatureSuivante);
}Vérifiez avant de répondre. Si la signature échoue, répondez 401 : l'événement est conservé de notre côté et n'est pas perdu. Si vous répondez 2xx avant de vérifier, un événement refusé ensuite est perdu sans trace.
Changer le secret sans perdre d'événements. Pendant un changement, chaque requête contient aussi x-signature-next, signé avec le nouveau secret. Acceptez l'une ou l'autre signature. Vous pouvez ainsi passer au nouveau secret quand vous voulez, sans période où les événements échouent.
État des envois
| Signification |
|---|---|
| Meta a accepté le message |
| Meta a accepté le message et l'a retenu pour évaluer sa qualité. Ni succès ni échec pour l'instant |
| il est arrivé sur le téléphone du client |
| le client l'a lu |
| il n'est pas parti : |
Avec held, attendez l'état suivant avant de confirmer quoi que ce soit au client.
Relier l'état au message
Les événements d'état contiennent messageId (l'identifiant que l'API vous a renvoyé), providerMessageId (l'identifiant de Meta), ou les deux. Jamais aucun des deux.
POST /messages → { messageId: "4444…" }
message.status sent → { messageId: "4444…", providerMessageId: "wamid.X" } ← la paire
message.status delivered → { providerMessageId: "wamid.X" }
message.status read → { providerMessageId: "wamid.X" }Conservez le providerMessageId reçu avec sent (ou held). Les delivered et read suivants ne contiennent que celui-là.
Erreurs
Dans la réponse à la requête. Elles se corrigent dans la requête elle-même :
HTTP |
| Quand |
|---|---|---|
400 |
| le corps n'est pas correct (le message dit pourquoi) |
401 |
| clé absente, erronée ou révoquée |
404 |
| le numéro n'existe pas dans votre compte |
Sur le webhook, dans un message.status à failed. Tout ce qui dépend de Meta arrive ici, après le 202 :
| Quand | Où regarder |
|---|---|---|
| message libre hors de la fenêtre de 24 heures | envoyez un modèle |
| le modèle n'existe pas dans cette langue, n'est pas approuvé, ou a été mis en pause | onglet Modèles de l'espace client |
| le nombre de variables ne correspond pas au modèle, ou le texte est trop long | le corps de la requête |
| quelque chose que ce canal ne fait pas (par exemple, un envoi à un groupe) | |
| le numéro n'est pas connecté | espace client |
| Meta a refusé pour une autre raison | le texte de |
Livraison des événements
Répondez 2xx dès réception, avant de traiter l'événement. Chaque tentative dispose de 10 secondes. Un endpoint qui ne répond qu'à la fin du flux risque de recevoir le même événement une deuxième fois.
Dédupliquez par
eventId. Chaque événement est livré au moins une fois et peut arriver en double.Triez par
occurredAt, pas par ordre d'arrivée. Undeliveredpeut arriver avant lesent.Nouvelles tentatives : sur un 5xx, un 429 ou sans réponse, nous réessayons jusqu'à 8 fois sur environ 30 minutes. Un 4xx (sauf 429) n'est pas réessayé : l'événement est conservé de notre côté.
Limites du canal
Le canal officiel n'a pas :
de groupes : il n'envoie ni ne reçoit de messages de groupe ;
d'indicateur « en train d'écrire », ni de suppression des messages envoyés ;
d'option note vocale : Meta décide selon le format du fichier (un audio ogg/opus s'affiche comme note vocale) ;
d'en-tête de modèle en base64 : utilisez une image par URL.
Ces requêtes échouent avec CAPABILITY_UNAVAILABLE, jamais en silence.
Coûts
L'abonnement (Officiel Essentiel, Business ou Scale) couvre les numéros et l'API, sans limite de messages de notre côté.
Les messages sont facturés par Meta, par message livré, sur votre compte WhatsApp Business. Le prix dépend de la catégorie (marketing, utilitaire, authentification) et du pays du client.
Les réponses dans la fenêtre de 24 heures sont des messages de service, gratuits jusqu'à la limite mensuelle fixée par Meta.
Sans moyen de paiement sur le compte Meta, Meta cesse d'envoyer les messages, y compris les réponses dans la fenêtre.
Les prix en vigueur figurent sur la page de tarification de la plateforme WhatsApp de Meta.
