> ## Documentation Index
> Fetch the complete documentation index at: https://docs.waaconnect.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Recevez en temps réel les events messages entrants et sortants de votre session WhatsApp

Toutes les routes utilisent le header `x-api-key`. Un abonnement webhook est
toujours rattaché à une session (pas au compte entier) — cohérent avec le
reste de l'API (statuts récurrents, contacts).

<Note>
  La création et la suppression d'un abonnement webhook se font depuis le
  [dashboard WaaConnect](https://app.waaconnect.com), pas via l'API à clé.
  Seule la lecture (liste des abonnements actifs) est disponible ici.
</Note>

## Catalogue d'events

| Event                 | Déclenchement                                                                                   |
| --------------------- | ----------------------------------------------------------------------------------------------- |
| `message.received`    | Un message est reçu — envoyé **au moment même de la réception**, sans être relu depuis une base |
| `message.sent`        | Un message envoyé par cette session a bien été transmis à WhatsApp                              |
| `message.status`      | Accusé de livraison/lecture d'un message envoyé (`delivered`, `read`, `played`, ...)            |
| `message.deleted`     | Un message (envoyé ou reçu) est supprimé — par vous ou par votre contact                        |
| `message.edited`      | Un message (envoyé ou reçu) est édité — par vous ou par votre contact                           |
| `message.reaction`    | Une réaction emoji est ajoutée (ou retirée) sur un message                                      |
| `flow.triggered`      | Un flow (automatisation) démarre suite à un message reçu                                        |
| `autoreply.triggered` | Une règle d'auto-réponse par mot-clé se déclenche                                               |

Passez `events: ["*"]` à la création pour tout recevoir, ou une liste
explicite pour ne recevoir que certains events.

## Format de l'enveloppe

Tous les events partagent la même structure :

```json theme={null}
{
  "event": "message.received",
  "eventId": "b7e4b8b0-2222-4444-9999-abcdef123456",
  "sessionId": "uuid-session-12",
  "tenantId": "uuid-tenant-1",
  "timestamp": "2026-07-19T10:00:00.000Z",
  "data": { }
}
```

`eventId` est unique par event — utilisez-le pour dédupliquer si vous
recevez un retry après avoir déjà traité l'event.

### `data` selon l'event

```json theme={null}
// message.received
{
  "chatId": "22997000000@s.whatsapp.net",
  "chatType": "individual",
  "from": "22997000000@s.whatsapp.net",
  "fromPhone": "22997000000@s.whatsapp.net",
  "type": "conversation",
  "content": "Bonjour, vous êtes dispo ?",
  "pushName": "Aicha",
  "messageTimestamp": 1774511000
}

// message.sent
{
  "id": "uuid-message-451",
  "toNumber": "22997000000@s.whatsapp.net",
  "type": "text",
  "content": "Bonjour, votre commande est prête.",
  "waMessageId": "3EB0B430B438B4F26B29",
  "remoteJid": "22997000000@s.whatsapp.net"
}

// message.status
{
  "waMessageId": "3EB0B430B438B4F26B29",
  "remoteJid": "22997000000@s.whatsapp.net",
  "status": "delivered"
}

// message.deleted
{
  "remoteJid": "22997000000@s.whatsapp.net",
  "waMessageId": "3EB0B430B438B4F26B29",
  "fromMe": false,
  "deletedBy": "contact"
}

// message.edited
{
  "remoteJid": "22997000000@s.whatsapp.net",
  "waMessageId": "3EB0B430B438B4F26B29",
  "fromMe": false,
  "editedBy": "contact",
  "newContent": "Bonjour, vous êtes disponible demain ?"
}

// message.reaction
{
  "remoteJid": "22997000000@s.whatsapp.net",
  "waMessageId": "3EB0B430B438B4F26B29",
  "fromMe": true,
  "emoji": "👍"
}

// flow.triggered
{
  "flowId": "uuid-flow-1",
  "flowName": "Accueil nouveau client",
  "chatId": "22997000000@s.whatsapp.net"
}

// autoreply.triggered
{
  "ruleId": "uuid-rule-1",
  "keyword": "tarif",
  "chatId": "22997000000@s.whatsapp.net"
}
```

## `chatType`, `fromPhone` et identifiants `@lid`

WhatsApp adresse aujourd'hui beaucoup de contacts via un identifiant
opaque `@lid` (ex: `39741751881852@lid`) plutôt que le JID classique basé
sur le numéro de téléphone (`@s.whatsapp.net`) — une évolution du
protocole liée à la confidentialité du numéro, pas une anomalie.

* **`chatType`** (`individual` / `group` / `status` / `broadcast`) permet
  d'identifier directement le type de conversation sans avoir à parser
  `chatId` vous-même.
* **`fromPhone`** est une tentative de résolution du numéro réel derrière
  un `@lid`. **Peut être `null`** si la correspondance n'est pas encore
  connue (typiquement pour un tout nouveau contact) — pas de garantie de
  résolution immédiate ni systématique.

<Note>
  Pour répondre à un message reçu, utilisez **`chatId`** comme destinataire
  (`to` de [Envoyer un message texte](/api-reference/messages/send-text)).
  Dans un groupe, `from` désigne le membre qui a écrit, pas le groupe
  lui-même — n'utilisez jamais `from` comme destinataire dans ce cas.
</Note>

**Envoyer directement à un `@lid` fonctionne** — pas besoin d'attendre ou
d'avoir la résolution `fromPhone` pour répondre :

* L'URL/numéro passé en `to` est utilisé tel quel dès qu'il contient déjà
  un `@` (donc un `chatId` en `@lid` n'est jamais reformaté à tort comme
  un numéro).
* WhatsApp/Baileys gère nativement le chiffrement vers un destinataire
  `@lid`.
* **Nuance** : l'étape interne de vérification "ce numéro existe-t-il sur
  WhatsApp ?" n'est pas supportée par WhatsApp pour un `@lid` — elle est
  silencieusement ignorée pour ce cas précis (pas d'erreur), et l'envoi
  part quand même normalement vers le `chatId` d'origine.

`fromPhone`, quand il est renseigné (non `null`), fonctionne aussi comme
destinataire — c'est un JID classique, sans particularité.

## Sécurité — vérifier la signature

Chaque requête envoyée à votre URL inclut deux headers :

* `X-Waaconnect-Event` : le type d'event (ex: `message.received`)
* `X-Waaconnect-Signature` : `sha256=<hmac>` — HMAC-SHA256 du corps JSON
  brut, signé avec le `secret` renvoyé à la création de l'abonnement.

```js theme={null}
const crypto = require("crypto");

function isValid(rawBody, signatureHeader, secret) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader),
    Buffer.from(expected),
  );
}
```

<Warning>
  Le `secret` n'est affiché qu'**une seule fois**, au moment de la création
  de l'abonnement depuis le dashboard. Conservez-le : il n'est jamais
  réaffiché ensuite, y compris via [Lister les webhooks](/api-reference/webhooks/list).
</Warning>

## Fiabilité

* Livraison via une file dédiée avec retry : 5 tentatives, backoff
  exponentiel (5s / 25s / 125s / ...), timeout 8s par tentative.
* Après épuisement des tentatives, l'event n'est plus retenté — votre
  endpoint doit répondre `2xx` rapidement pour être considéré comme reçu.

## Routes

* [Lister les abonnements](/api-reference/webhooks/list)
