# Agent IA WhatsApp avec n8n : un workflow prêt pour la production

> Construisez un agent IA WhatsApp avec n8n prêt pour la production : webhook signé, réponse 200 immédiate, anti-doublons, nœud AI Agent avec mémoire, relais humain.

Source: https://whasp.ai/fr/blog/agent-ia-whatsapp-n8n-workflow · 2026-10-20

## L’essentiel

- Un agent IA WhatsApp sur n8n ne demande qu'un nœud Webhook, un nœud Code qui vérifie la signature, un nœud AI Agent avec mémoire et un nœud HTTP Request qui envoie la réponse.
- Activez l'option Raw Body du nœud Webhook, car la signature couvre les octets exacts envoyés par whasp, et un corps JSON resérialisé peut échouer à la vérification.
- Répondez 200 juste après la vérification, éliminez les doublons avec un nœud Remove Duplicates basé sur l'identifiant d'événement, et envoyez les réponses avec une Idempotency-Key pour qu'une nouvelle tentative n'envoie jamais deux fois.
- En production, utilisez Postgres Chat Memory ou une autre mémoire adossée à une base de données, car Simple Memory vit dans la mémoire du processus n8n et oublie une conversation après une heure de silence.
- Le relais vers un humain se gère dans n8n, pas dans whasp : gardez un indicateur par conversation dans une Data table et vérifiez-le avant chaque appel à l'IA.

Pour construire un agent IA WhatsApp avec n8n, pointez un nœud Webhook sur les événements entrants de l'agent, vérifiez la signature dans un nœud Code, répondez 200 aussitôt, écartez les événements en double, exécutez un nœud AI Agent avec un modèle de chat et une mémoire indexée sur le téléphone de l'expéditeur, puis envoyez la réponse avec un nœud HTTP Request. Ce guide utilise whasp pour le côté WhatsApp et a été exécuté sur n8n 2.43.8.

## Pourquoi ne pas utiliser le nœud WhatsApp de n8n ?

Le [nœud WhatsApp Business Cloud](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.whatsapp/) intégré à n8n envoie messages, templates et médias via la WhatsApp Business Platform officielle de Meta. Depuis le 15 janvier 2026, les conditions de cette plateforme interdisent les assistants IA généralistes, comme l'explique [WhatsApp interdit les chatbots IA en 2026](https://whasp.ai/fr/blog/interdiction-chatbot-ia-whatsapp-2026), et [les autres options](https://whasp.ai/fr/blog/mettre-agent-ia-whatsapp-options) ont chacune leurs compromis. Ce guide utilise un numéro que whasp exploite. n8n n'a besoin d'aucun nœud spécial pour cela : whasp envoie des événements en POST à une URL et reçoit les réponses par une simple API HTTPS, donc les nœuds Webhook et HTTP Request suffisent.

La chaîne compte huit étapes : Webhook, Verify signature (Code), Respond to Webhook, Text message? (If), Remove Duplicates, Is STOP? (If), AI Agent avec son modèle et sa mémoire, Send reply (HTTP Request). Nous avons tout exécuté, sauf le nœud Postgres, sur n8n 2.43.8 le 19 octobre 2026, avec le modèle de langage et l'API whasp remplacés par des simulations.

Il vous faut un n8n joignable en HTTPS public (derrière un reverse proxy, définissez `N8N_WEBHOOK_URL`), un numéro whasp, une clé d'API et une credential pour un modèle.

## Comment recevoir les événements whasp dans n8n ?

Ajoutez un nœud Webhook et réglez :

- HTTP Method : POST. Path : `whasp`.
- Respond : Using 'Respond to Webhook' Node.
- Options, Raw Body : activé.

Le [nœud Webhook](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.webhook/) a une URL de test et une URL de production. L'URL de test (`/webhook-test/whasp`) fonctionne tant que vous cliquez sur Listen for Test Event. L'URL de production (`/webhook/whasp`) ne fonctionne qu'après la publication du workflow. Enregistrez l'URL de production auprès de whasp. La réponse contient le secret de signature (`whsec_...`), que vous stockez dans `WHASP_WEBHOOK_SECRET` :

```bash
curl -X PUT https://api.whasp.ai/v1/webhook \
  -H "Authorization: Bearer $WHASP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://n8n.example.com/webhook/whasp"}'
```

Raw Body est essentiel. whasp signe les octets exacts qu'il envoie, selon le schéma Standard Webhooks. Avec l'option activée, le nœud conserve ces octets dans `binary.data`, à côté du `body` analysé. Resérialiser le JSON analysé peut modifier les espaces ou l'ordre des clés et faire échouer la vérification.

Appelez `POST /v1/webhook/test` pour envoyer un événement `webhook.test` et le voir arriver.

## Comment vérifier la signature dans un nœud Code ?

Ajoutez un nœud Code nommé `Verify signature`, en mode Run Once for All Items, langage JavaScript :

```js
const crypto = require('crypto');

if (!$env.WHASP_WEBHOOK_SECRET) throw new Error('Set WHASP_WEBHOOK_SECRET (the whsec_ secret from whasp)');

const headers = $input.first().json.headers;
const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];

// Les octets exacts que whasp a signés : l'option Raw Body du nœud Webhook les garde dans binary.data.
const raw = await this.helpers.getBinaryDataBuffer(0, 'data');

const key = Buffer.from($env.WHASP_WEBHOOK_SECRET.replace(/^whsec_/, ''), 'base64');
const expected = crypto.createHmac('sha256', key)
  .update(`${id}.${timestamp}.`)
  .update(raw)
  .digest();

const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300; // 5 minutes
const signed = String(headers['webhook-signature'] ?? '').split(' ').some((part) => {
  const [version, sig] = part.split(',');
  if (version !== 'v1' || !sig) return false;
  const given = Buffer.from(sig, 'base64');
  return given.length === expected.length && crypto.timingSafeEqual(given, expected);
});

if (!id || !timestamp || !fresh || !signed) return [{ json: { valid: false } }];

const event = JSON.parse(raw.toString('utf8'));
const m = event.data?.message ?? {};
return [{
  json: {
    valid: true,
    eventId: id,
    type: event.type,
    number: m.number,   // le numéro whasp qui l'a reçu
    chat: m.chat,       // le téléphone de l'expéditeur, au format E.164
    name: m.sender_name ?? null,
    text: m.text ?? '',
  },
}];
```

Le code suit la règle de whasp : HMAC-SHA256 sur `<id>.<timestamp>.<raw body>`, avec pour clé la partie du secret décodée en base64 après `whsec_`, et un horodatage de moins de cinq minutes. Il renvoie `valid: false` au lieu de lever une exception. Une erreur avant le nœud Respond to Webhook fait répondre 500 à n8n, et whasp réessaierait l'événement jusqu'à 12 fois. Un 401 propre vaut mieux.

Deux réglages piègent souvent les utilisateurs d'un n8n 2.x auto-hébergé, et tous deux échouent avec un message clair. Depuis [n8n 2.0](https://docs.n8n.io/2-0-breaking-changes/), le nœud Code bloque par défaut les variables d'environnement, et sans `N8N_BLOCK_ENV_ACCESS_IN_NODE=false` ce nœud échoue avec « access to env vars denied ». Les modules intégrés sont bloqués eux aussi : sans `NODE_FUNCTION_ALLOW_BUILTIN=crypto`, vous obtenez « Module 'crypto' is disallowed ». Avec des task runners externes, cette seconde variable doit figurer dans la configuration propre au runner. Sur n8n Cloud, [`crypto` est autorisé](https://docs.n8n.io/code/code-node/). Si vous ne pouvez pas définir de variables d'environnement, créez une variable personnalisée là où votre offre en propose, et lisez `$vars.WHASP_WEBHOOK_SECRET` à la place.

## Comment répondre vite et ignorer les doublons ?

whasp considère comme livrée toute réponse 2xx reçue en moins de 10 secondes. Sinon, il réessaie après 30 secondes, 2 minutes, 10 minutes, 30 minutes, puis toutes les heures, jusqu'à 12 fois. Les événements peuvent arriver deux fois et dans le désordre. Trois nœuds s'en chargent.

1. **Respond to Webhook.** Respond With : No Data. Response Code : `{{ $json.valid ? 200 : 401 }}`. Le nœud transmet son entrée après avoir répondu : le workflow continue donc de tourner alors que whasp a déjà sa réponse.
2. **Text message? (If).** Continuez seulement quand `valid` est vrai, que `type` vaut `message.received` et que `text` n'est pas vide. Tout le reste (`message.sent`, `number.status_changed`, une photo sans texte) tombe dans la branche false. Reliez-y `message.failed` et `number.status_changed` à une alerte.
3. **Remove Duplicates.** Operation : Remove Items Processed in Previous Executions. Keep Items Where : Value Is New. Value to Dedupe On : `{{ $json.eventId }}`. L'historique par défaut compte 10 000 éléments.

Nous avons envoyé deux fois le même événement signé, puis un événement altéré : le premier a reçu une réponse, le doublon a reçu un 200 sans second passage, et l'événement altéré a reçu un 401. Une limite : une fois le 200 renvoyé, un plantage dans n8n fait perdre cet événement, car whasp ne le renverra pas. Si cela compte pour vous, écrivez chaque événement dans une table ou une file avant de l'acquitter. [Webhook WhatsApp : signature, nouvelles tentatives, doublons](https://whasp.ai/fr/blog/webhook-whatsapp-signature-doublons) détaille ce schéma.

## Comment configurer le nœud AI Agent, la mémoire et la réponse ?

**AI Agent.** Source for Prompt (User Message) : Define below. Prompt (User Message) : `{{ $json.text }}`. Dans Options, System Message, écrivez qui est l'agent et comment il doit écrire, par exemple « Tu es Ava, une assistante sympathique qui écrit sur WhatsApp. Texte brut uniquement, pas de Markdown. Pose une question à la fois. » Pour un prompt plus complet, voyez [Prompt pour agent IA WhatsApp : écrire comme une personne](https://whasp.ai/fr/blog/prompt-agent-ia-whatsapp). La réponse de l'agent arrive dans `$json.output`.

**Chat model.** Rattachez un OpenAI Chat Model ou un Anthropic Chat Model. Tous deux chargent la liste des modèles depuis votre compte : choisissez-y un modèle actuel.

**Memory.** Rattachez Postgres Chat Memory. Réglez Session ID sur Define below et Key sur `{{ $json.number }}:{{ $json.chat }}`, pour que chaque personne ait un historique par numéro. La table par défaut est `n8n_chat_histories` et Context Window Length vaut 5 par défaut : augmentez-la selon le budget de votre prompt. Évitez Simple Memory en production. Son [code source](https://github.com/n8n-io/n8n/blob/master/packages/%40n8n/nodes-langchain/nodes/memory/MemoryBufferWindow/MemoryBufferWindow.node.ts) garde les sessions dans une map à l'intérieur du processus n8n et supprime toute session inactive depuis une heure, et [sa documentation](https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.memorybufferwindow/) indique qu'elle ne fonctionne pas en queue mode. Les serveurs de whasp ne conservent aucun message : l'historique est donc le vôtre, et une base de données que vous maîtrisez est le bon endroit. [Agent IA WhatsApp avec mémoire : historique, résumés, faits](https://whasp.ai/fr/blog/agent-ia-whatsapp-memoire-conversation) va plus loin.

**Send reply.** Ajoutez un nœud HTTP Request :

| Champ | Valeur |
| --- | --- |
| Method, URL | POST, `https://api.whasp.ai/v1/messages` |
| Authentication | Generic Credential Type, Header Auth : name `Authorization`, value `Bearer wsk_...` |
| Send Headers | `Idempotency-Key` : `{{ $('Verify signature').first().json.eventId }}` |
| Send Body, JSON, Using JSON | `{{ { to: $('Verify signature').first().json.chat, text: $json.output } }}` |
| Settings | Retry On Fail activé, Max Tries 3, Wait Between Tries 2000 ms |

Construisez le corps comme une expression qui renvoie un objet. Coller le texte du modèle dans une chaîne JSON casse dès le premier guillemet ou saut de ligne. La clé d'idempotence rend les nouvelles tentatives sûres : la même clé renvoie le premier message et n'envoie jamais deux fois. n8n autorise au plus 5 essais et 5 secondes entre deux, ce qui couvre les petits incidents, pas les pannes. whasp répond 202 et envoie les messages d'un même numéro dans l'ordre, à environ une seconde d'intervalle.

## Comment gérer STOP, les erreurs et les rafales ?

whasp désinscrit lui-même une conversation quand la personne envoie STOP, UNSUBSCRIBE, OPTOUT, CANCEL, END, QUIT ou n'importe quelle graphie de « opt out », et les envois suivants vers cette conversation renvoient 403 `chat_opted_out`. Le message arrive tout de même sur votre webhook : ajoutez donc un nœud If **Is STOP?** après Remove Duplicates. Réglez la valeur sur l'expression `{{ /^(STOP|UNSUBSCRIBE|OPTOUT|CANCEL|END|QUIT)$/.test($json.text) || /^opt[\s_-]*out$/i.test($json.text) }}` et la condition sur Boolean, is true. Sur la branche true, sautez le modèle et envoyez un seul HTTP Request avec `"override_optout": true` et une phrase comme « Désinscription faite. Écris-moi quand tu veux pour reprendre. » Les demandes polies comme « arrête, s'il te plaît » restent à votre charge.

Pour les échecs, ouvrez Workflow Settings, définissez un Error workflow, et construisez-le à partir d'un nœud Error Trigger qui publie sur Slack ou par e-mail. Lisez bien les 429 : `rate_limited` signifie plus de 100 requêtes d'API en 10 secondes par compte, et `number_daily_limit` signifie que le numéro a envoyé 1 500 messages en 24 heures. Un 409 `number_unavailable` signifie que le numéro est banni, hors ligne ou retiré.

Les rafales demandent une décision. Une personne qui envoie trois textos en cinq secondes déclenche trois exécutions et reçoit trois réponses. Des utilisateurs du forum n8n recommandent de [regrouper les messages rapides d'un client en un seul tour](https://community.n8n.io/t/title-building-a-production-ready-whatsapp-automation-with-n8n-architecture-security-rate-limiting-retries-and-human-handoff/322131) avant d'appeler le modèle. Nous n'avons pas construit cette partie.

## Comment passer une conversation à un humain ?

whasp est un simple tuyau : il n'a ni boîte de réception ni prise en main, donc l'état du relais vit dans n8n. Un schéma qui fonctionne utilise une [Data table](https://docs.n8n.io/data/data-tables/) nommée `chat_state`, avec des colonnes `chat` et `handoff`. Nous n'avons pas exécuté cette partie, mais les opérations des nœuds sont documentées.

1. Après Remove Duplicates, ajoutez un nœud Data table : Row, If Row Exists, avec `chat` égal à `{{ $json.chat }}` et `handoff` true. La branche true transmet le message à Slack ou à votre helpdesk et s'arrête. La branche false continue vers l'agent.
2. Posez l'indicateur avec un Upsert row de la Data table quand le texte correspond à une règle comme `/humain|vraie personne/i`, ou quand votre agent le demande. Envoyez ensuite « Une personne va te répondre ici très bientôt. »
3. Construisez un second workflow qui se déclenche quand l'équipe répond, appelle `POST /v1/messages` avec la même conversation, et remet `handoff` à false par un upsert sur une commande de reprise.

Vérifiez l'indicateur une seconde fois juste avant d'envoyer la réponse du modèle, car un relais peut démarrer pendant que le modèle réfléchit.

## Que faire ensuite ?

Publiez le workflow, enregistrez son URL de production avec `PUT /v1/webhook`, envoyez `POST /v1/webhook/test`, puis écrivez à votre numéro depuis votre propre téléphone. Si rien n'arrive, `GET /v1/webhook/deliveries` montre chaque tentative et son statut. Le [quickstart](https://whasp.ai/docs#quickstart) détaille l'API. Pour le même flux en code, voyez [Intégration OpenAI WhatsApp : créer un agent GPT en TypeScript](https://whasp.ai/fr/blog/integration-openai-whatsapp-agent-gpt).

## Questions

### Comment connecter n8n à WhatsApp pour un agent IA ?

Recevez les messages entrants avec un nœud Webhook, exécutez un nœud AI Agent, puis envoyez la réponse avec un nœud HTTP Request qui appelle POST https://api.whasp.ai/v1/messages. Le nœud WhatsApp Business Cloud de n8n utilise l'API officielle de Meta, qui ne convient pas à un assistant généraliste.

### Pourquoi mon nœud Code n8n indique-t-il que le module crypto n'est pas autorisé ?

Depuis n8n 2.0, le nœud Code s'exécute sur des task runners et bloque par défaut les modules intégrés. Sur n8n auto-hébergé, définissez NODE_FUNCTION_ALLOW_BUILTIN=crypto là où le task runner lit son environnement. n8n Cloud autorise déjà crypto.

### Pourquoi mon nœud Code n8n indique-t-il « access to env vars denied » ?

n8n 2.0 bloque par défaut les variables d'environnement dans le nœud Code. Sur n8n auto-hébergé, définissez N8N_BLOCK_ENV_ACCESS_IN_NODE=false, ou stockez le secret dans une variable personnalisée et lisez-le avec $vars.

### Comment empêcher n8n de répondre deux fois au même message WhatsApp ?

Placez un nœud Remove Duplicates après le Webhook, réglé pour retirer les éléments traités lors d'exécutions précédentes, et dédoublonnez sur l'identifiant d'événement tiré de l'en-tête webhook-id. Envoyez ensuite la réponse avec ce même identifiant comme Idempotency-Key.

### Quel nœud de mémoire n8n choisir pour un agent WhatsApp ?

Utilisez Postgres Chat Memory ou Redis Chat Memory, avec le numéro de téléphone de l'expéditeur comme clé de session. Simple Memory vit dans le processus, est supprimée après une heure sans utilisation et ne fonctionne pas en queue mode.
