Documentation

Connexion Directe WhatsApp

Comment connecter un numéro par QR, envoyer des messages, recevoir les événements dans votre système et éviter les restrictions de Meta.

Mis à jour le 12 novembre 2026

Sommaire

Vue d'ensemble

La Connexion Directe relie un numéro WhatsApp ordinaire (personnel ou WhatsApp Business) à notre API en scannant un code QR, comme pour WhatsApp Web. Vous envoyez ensuite des messages et recevez ce que vos clients écrivent, depuis n8n, votre CRM ou votre propre code.

Ce que vous recevez :

  • une clé d'accès pour envoyer des messages 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 et suivez votre consommation.

Toutes les requêtes vont vers :

https://wa.automate-ai.digital

Connaissez le risque avant de commencer. La Connexion Directe n'est pas le canal officiel de Meta. Meta peut restreindre un numéro qu'elle considère comme automatisé, et cela échappe à notre contrôle. Suivez les bonnes pratiques et, pour de gros volumes ou des campagnes, utilisez l'API Officielle.

Démarrer

1. Connecter le numéro

Dans l'espace client, sous Mes produits, cliquez sur Connecter ce numéro. Un code QR apparaît. Sur le téléphone, ouvrez WhatsApp, allez dans Appareils connectés → Connecter un appareil et scannez le code.

Le code reste actif 5 minutes. S'il expire, cliquez sur Reconnecter. Le numéro apparaît comme connecté dès la fin du scan.

Le téléphone n'a pas besoin de rester en ligne : la connexion fonctionne comme un appareil de plus sur votre compte.

2. Configurer le webhook et la clé

Dans l'espace client, sous Intégration technique :

  1. 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.

  2. Clé d'accès : générez la clé. Elle n'apparaît, elle aussi, qu'une seule fois.

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.id est le numéro au format international, sans + ni espaces. Pour un groupe : { "kind": "group", "id": "ID_DU_GROUPE" }.

  • 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) : le providerMessageId du 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 est parti. Le résultat arrive ensuite sur le webhook, sous forme de message.status.

Les messages partent dans l'ordre d'arrivée, avec un petit intervalle aléatoire entre eux. C'est voulu : les envois en rafale sont ce qui pousse Meta à restreindre des numéros.

Types de contenu

type

Champs

text

body (jusqu'à 4096 caractères)

image

media, caption

video

media, caption

audio

media, voiceNote (true l'envoie comme note vocale)

document

media, filename, caption

location

latitude, longitude, name, address

media vaut { "kind": "url", "url": "https://…" }, ou { "kind": "base64", "data": "…", "mimeType": "image/png" }.

Les boutons n'existent pas sur ce canal. Une requête avec "type": "buttons" échoue avec CAPABILITY_UNAVAILABLE. L'API ne les transforme pas en liste numérotée : la réponse du client arriverait en texte libre, et un flux qui attend le clic attendrait pour toujours. Pour les boutons, utilisez l'API Officielle.

Les modèles non plus. Avec la Connexion Directe, vous pouvez écrire à n'importe qui à tout moment, sans modèles ni fenêtre de 24 heures. Utilisez toujours /messages.

Groupes

Le numéro peut envoyer et recevoir des messages dans les groupes dont il fait partie. Pour que votre flux ne soit pas submergé par toutes les conversations de groupe, seuls les messages des groupes que vous autorisez arrivent sur le webhook.

  1. Listez les groupes du numéro :

curl https://wa.automate-ai.digital/instances/ID_DU_NUMERO/groups -H "x-api-key: $CLE"
{ "ok": true, "data": [{ "id": "120363012345678901", "name": "Équipe commerciale" }] }
  1. Autorisez ceux que vous voulez recevoir. La liste remplace la précédente et s'applique immédiatement :

curl -X PATCH https://wa.automate-ai.digital/instances/ID_DU_NUMERO \
  -H "x-api-key: $CLE" -H "content-type: application/json" \
  -d '{ "allowedGroups": ["120363012345678901"] }'

Les messages de groupe arrivent avec via, le groupe, et from, l'auteur.

Événements

Chaque événement arrive sur votre webhook en POST, avec un corps JSON. Ce canal a trois types :

type

Quand

message.received

quelqu'un a écrit

message.status

un envoi a changé d'état : sent ou failed

connection.updated

l'état du numéro a changé

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": "baileys",
  "occurredAt": "2026-11-12T18:41:07.000Z",
  "providerMessageId": "3EB0C767D26B8A1F",
  "from": "33612345678",
  "pushName": "Marie",
  "content": { "type": "text", "body": "Bonjour, où en est ma commande ?" },
  "windowExpiresAt": null
}

Types de content :

type

Champs

text

body

media

mediaType (image, video, audio, document, sticker), storageUrl, storagePath, mimeType, filename, caption

location

latitude, longitude, name

reaction

emoji, targetMessageId

unsupported

providerType : un format que l'API ne lit pas encore

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.

message.status

Sur ce canal arrivent sent (le message est parti) et failed (il n'est pas parti : failureCode dit pourquoi). Chacun contient le messageId que l'API vous a renvoyé à l'envoi. Les états « livré » et « lu » ne sont pas envoyés sur ce canal.

connection.updated

Contient status : connecting, qr_required, connected, disconnected ou logged_out.

  • disconnected : la connexion est tombée. L'API se reconnecte seule.

  • logged_out avec requiresReauth: true : le numéro doit être reconnecté dans l'espace client, avec un nouveau QR. Cela arrive, par exemple, quand quelqu'un ferme la session dans Appareils connectés sur le téléphone.

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 :

  1. refusez la requête s'il manque x-signature ou x-timestamp ;

  2. refusez-la si x-timestamp s'écarte de plus de 5 minutes de votre horloge ;

  3. calculez le HMAC sur le corps brut, octet par octet. Si vous resérialisez le JSON, la signature échoue pour certains événements ;

  4. 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.

Erreurs

Dans la réponse à la requête. Elles se corrigent dans la requête elle-même :

HTTP

code

Quand

400

INVALID_PAYLOAD

le corps n'est pas correct (le message dit pourquoi)

401

UNAUTHORIZED

clé absente, erronée ou révoquée

404

INSTANCE_NOT_FOUND

le numéro n'existe pas dans votre compte

409

INSTANCE_NOT_CONNECTED

uniquement sur GET /groups : le numéro n'est pas connecté

Sur le webhook, dans un message.status à failed. Ce qui dépend de WhatsApp arrive ici, après le 202 :

failureCode

Quand

INSTANCE_NOT_CONNECTED

le numéro n'est pas connecté. La file réessaie avant d'abandonner

CAPABILITY_UNAVAILABLE

quelque chose que ce canal ne fait pas (par exemple, les boutons)

PROVIDER_REJECTED

WhatsApp a refusé ou n'a pas confirmé l'envoi. La raison figure dans failureReason

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.

  • 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é.

Bonnes pratiques

Ce que Meta considère comme une automatisation abusive, et qui la pousse à restreindre des numéros :

  • des messages à des personnes qui ne les ont pas demandés, surtout en nombre et avec le même texte ;

  • beaucoup de messages d'affilée vers des contacts qui n'ont jamais écrit au numéro ;

  • des numéros neufs qui envoient tout de suite en volume.

Ce qui aide :

  • répondez à ceux qui vous écrivent, et envoyez des avis aux clients qui vous ont déjà parlé ;

  • variez le texte et évitez les liens raccourcis ;

  • démarrez doucement avec un nouveau numéro ;

  • pour les campagnes, utilisez l'API Officielle avec des modèles approuvés.

Consommation et limites

  • Chaque numéro inclut 3 000 messages envoyés par cycle de facturation. Les messages reçus ne comptent pas.

  • Au-delà, vous achetez des blocs de 1 000 messages dans l'espace client.

  • La consommation de chaque numéro figure dans l'espace client, sous Mes produits.

Une remarque sur les expéditeurs. WhatsApp commence à identifier certaines conversations par un code interne au lieu du numéro de téléphone. Quand le téléphone de l'expéditeur n'est pas connu, le message est enregistré mais n'est pas envoyé au webhook : le champ from doit être un numéro auquel on peut répondre, et ce code n'en est pas un.