WhatsApp (Meta Business API)
Setup complet, opérations courantes, et résolution d'incidents pour la couche WhatsApp.
Cette page est le runbook complet côté WhatsApp. Tout ce qui suit doit être à jour — si tu touches au flow WhatsApp, mets à jour ce doc dans la foulée. C'est la source de vérité quand un incident arrive en prod ou en preprod.
Vue d'ensemble
Wcare envoie des notifications WhatsApp via la Meta Cloud API (anciennement WhatsApp Business API). La règle d'or, héritée de la spec Maxime :
Wcare n'initie JAMAIS une conversation WhatsApp sauf via un template approuvé. Les notifs de signalement passent toujours par une fenêtre 24h ouverte côté Meta — donc gratuites.
Stratégie de coût :
- Fenêtre user-initiated ouverte par le destinataire →
service= gratuit - 1er contact via template utility approuvé → ~0.024€/msg en France
- Hard cap 10€/mois/établissement (
WHATSAPP_MONTHLY_CAP_CENTS=1000) — au-delà, fallback automatique email seul.
Composants
flowchart TD
A[Client scan QR + signale] --> B[reportService.create]
B --> C[enqueueNotifications]
C --> D[notification_job table]
D --> E[notification-runner tick 5s]
E --> F{Session WA ouverte ?}
F -->|Oui staff| G[sendServiceMessage → Meta API]
F -->|Oui phone| H[sendServiceMessageToPhone → Meta API]
F -->|Non| I[Fallback email manager]
J[Staff scan QR + clic Activer] --> K[whatsappActivate route]
K --> L[whatsappSession insert]
L --> M[sendActivationWelcome]
M --> N{Template configuré ?}
N -->|Oui PROD| O[sendTemplate → Meta API]
N -->|Non sandbox| P[sendRaw texte libre → Meta API]
Q[Staff envoie WCARE_ACTIF] --> R[Meta push webhook]
R --> S[handleActif → whatsappSession insert + sendRaw confirmation]Sources :
packages/api/src/services/whatsapp.service.ts— service principalapps/server/src/routes/whatsapp.ts— webhook entrant Metaapps/server/src/routes/staff.ts— endpoints staff (activate, status, stop, sync-link)packages/api/src/routers/notification-phone.router.ts— endpoints managerpackages/api/src/services/notification-runner.ts— runner qui dépile les jobs
Variables d'environnement
| Variable | Obligatoire | Description |
|---|---|---|
META_WABA_ID | oui | ID du WhatsApp Business Account (visible sur business.facebook.com) |
META_PHONE_NUMBER_ID | oui | ID Meta du numéro WhatsApp utilisé pour l'envoi |
META_ACCESS_TOKEN | oui | Token d'accès — System User token permanent en prod, temporaire 24h en dev |
META_APP_SECRET | oui | Clé secrète de l'app Meta (signe les webhooks via HMAC SHA-256) |
META_VERIFY_TOKEN | oui | String random définie par toi, utilisée pour valider l'URL webhook côté Meta |
META_WHATSAPP_NUMBER | non | Numéro WhatsApp Wcare au format E.164 (ex: +15556354086) — utilisé par les liens wa.me. Si absent, lookup automatique via API Meta |
META_API_VERSION | non | Version Graph API (défaut: v25.0) |
META_TEMPLATE_ACTIVATION_NAME | en prod uniquement | Nom du template approuvé pour le msg de bienvenue. Sans cette variable, le service envoie du texte libre via sendRaw (sandbox uniquement). |
META_TEMPLATE_ACTIVATION_LANG | non | Langue du template (défaut: fr) |
WHATSAPP_MONTHLY_CAP_CENTS | non | Plafond mensuel en centimes (défaut: 1000 = 10€) |
Setup initial (côté Meta)
1. Créer l'app Meta
- Va sur https://developers.facebook.com/apps → Créer une app
- Type : Business
- Use case : Tisser des liens avec votre clientèle via WhatsApp
- Lie l'app à ton Business Portfolio Reciprok
- Note l'App ID + l'App Secret (Settings → Basic → Clé secrète)
2. WhatsApp Business Account (WABA)
- business.facebook.com → Paramètres business → Comptes WhatsApp
- Crée le WABA :
- Catégorie : Services professionnels
- Display name :
Wcare
- Ajoute un numéro :
- Sandbox dev : Meta en fournit un automatique (
+1 555 …) — gratuit, valable 90 jours, 5 destinataires whitelistés max - Prod : numéro acheté/fourni, vérification SMS/voice, ~24-72h de review Meta pour le display name
- Sandbox dev : Meta en fournit un automatique (
- Note le Phone Number ID + le WABA ID (sur la page API setup)
3a. Token long-lived 60 jours (quick win, sans Business Verification)
Si tu n'as pas (encore) fait la vérification entreprise Meta, le quickest fix est d'échanger ton token court 24h contre un long-lived de 60 jours. Endpoint Meta non documenté dans l'UI mais qui marche pour WhatsApp Cloud :
# 1. Génère un token court via developers.facebook.com → app → WhatsApp → API setup
# 2. Lance ce script avec les bonnes env vars
META_APP_ID=<app-id> \
META_APP_SECRET=<app-secret> \
META_ACCESS_TOKEN=<short-token-24h> \
bun scripts/extend-meta-token.tsOutput type :
✅ Long-lived token obtained — valid 60 days (until 2026-07-11T16:00:00Z)
→ Paste this into Dokploy → service `app` → Environment:
META_ACCESS_TOKEN=EAAY...long-lived-token...Tu copies dans Dokploy → Reload → le bandeau dashboard affiche maintenant
Token valide 60j. À refaire ~tous les 50 jours, le dashboard te prévient
quand il reste < 7j.
3b. Token permanent (System User)
Le token temporaire 24h ne tient pas la prod. Génère un System User token :
- business.facebook.com → Paramètres business → Utilisateurs système
- Ajouter → nom
wcare-server→ rôle Admin - Ajouter des actifs → coche le WABA Wcare → Contrôle complet
- Générer un nouveau token :
- App : sélectionne l'app Wcare
- Expiration : Jamais
- Permissions :
whatsapp_business_messaging+whatsapp_business_management
- Copie immédiatement le token — Meta ne le réaffiche jamais.
4. Webhook
- App Meta → WhatsApp → Configuration → Webhooks
- URL de rappel :
https://api.preprod.wcare.fr/whatsapp/webhook(ouapi.wcare.fr/...en prod) - Verify token : la string aléatoire que tu mets dans
META_VERIFY_TOKEN - Clic Vérifier et enregistrer → Wcare doit répondre
200 <challenge>(vérifie viacurlavant) - Sous le webhook, section Webhook fields : abonne-toi à :
messages(obligatoire — push les WCARE_ACTIF/STOP)message_template_status_update(utile — push delivery + status)
Test webhook manuel :
curl "https://api.preprod.wcare.fr/whatsapp/webhook?hub.mode=subscribe&hub.verify_token=<TON_VERIFY_TOKEN>&hub.challenge=ping123"
# Doit répondre exactement: ping1235. Whitelist destinataires (sandbox uniquement)
En sandbox, le numéro de test ne peut envoyer qu'à 5 destinataires explicitement whitelistés.
- App Meta → WhatsApp → API setup → "À"
- Manage phone number list → ajoute jusqu'à 5 numéros
- Chaque numéro reçoit un code WhatsApp à confirmer
En prod, plus de whitelist : tu envoies à n'importe qui qui t'a contacté ou via template approuvé.
Setup côté Wcare (Dokploy preprod / prod)
Toutes les variables listées ci-dessus vont dans Dokploy → ton projet → service app → Environment :
META_WABA_ID=2693528371020925
META_PHONE_NUMBER_ID=1119214124602499
META_API_VERSION=v25.0
META_VERIFY_TOKEN=wcare-meta-verify-<random>
META_APP_SECRET=<app-secret-de-meta>
META_ACCESS_TOKEN=<system-user-token-permanent>
META_WHATSAPP_NUMBER=+15556354086
META_TEMPLATE_ACTIVATION_NAME=wcare_activation_welcome # en prod uniquement
META_TEMPLATE_ACTIVATION_LANG=fr
WHATSAPP_MONTHLY_CAP_CENTS=1000Après chaque modification : Save puis Reload (pas besoin de redeploy complet — Reload suffit pour recharger les env vars).
Template Meta — Activation Welcome (PROD)
Quand tu passes l'app en mode Live, Meta exige que tout premier message à un destinataire qui n'a jamais écrit utilise un template approuvé. Ci-dessous le template à soumettre.
Soumettre le template
- business.facebook.com → WhatsApp Manager (lien : https://business.facebook.com/wa/manage/message-templates)
- Créer un template :
- Nom :
wcare_activation_welcome - Catégorie :
UTILITY - Langue :
Français (fr)
- Nom :
Body
Bonjour {{1}},
Vos alertes WhatsApp Wcare sont activées pour {{2}}.
Vous recevrez ici les signalements de votre établissement en temps réel.Exemples de variables (pour la review Meta) :
{{1}}:Anne{{2}}:Hotel HelloWorld
Bouton (optionnel mais recommandé)
Type : Quick Reply
Texte : Confirmer
Le clic du bouton est considéré comme une réponse user-initiated par Meta — ça ouvre la fenêtre 24h gratuite côté serveur.
Après approbation Meta (24-72h)
Mets dans Dokploy :
META_TEMPLATE_ACTIVATION_NAME=wcare_activation_welcome
META_TEMPLATE_ACTIVATION_LANG=frpuis Reload. Le service bascule automatiquement de sendRaw à sendTemplate.
Opérations courantes
Vérifier que le webhook tourne
curl "https://api.preprod.wcare.fr/whatsapp/webhook?hub.mode=subscribe&hub.verify_token=<META_VERIFY_TOKEN>&hub.challenge=alive"
# Réponse attendue: aliveVérifier l'état du token Meta
Le dashboard /dashboard/staff affiche un bandeau en haut :
- 🟢 Vert + numéro affiché = tout va bien
- 🔴 Rouge avec code d'erreur = token mort / phone ID inaccessible / etc.
Tu peux aussi tester manuellement :
TOKEN="EAAY..."
curl -s "https://graph.facebook.com/v25.0/<META_PHONE_NUMBER_ID>?fields=display_phone_number" \
-H "Authorization: Bearer $TOKEN"Réponse OK : {"display_phone_number":"+1 555 ...","id":"..."}
Token mort : {"error":{"code":190,"message":"Authentication Error"}}
Tester l'envoi outbound (simulation signalement)
FP=$(openssl rand -hex 32)
curl -sS -X POST "https://api.preprod.wcare.fr/report/<TOILET_ID>/<TOKEN>" \
-H "Content-Type: application/json" \
-d "{\"category\":\"cleanliness\",\"predefinedIssueId\":\"<ISSUE_ID>\",\"fingerprint\":\"$FP\"}"Puis attendre ~5s (runner tick) et regarder les logs Dokploy filtre whatsapp|runner.
Tester le webhook inbound (simulation Meta)
Script fourni : scripts/test-whatsapp-webhook.ts. Lancer depuis la racine du repo :
META_APP_SECRET=<app-secret> bun scripts/test-whatsapp-webhook.ts pingDoit retourner 200 {"ok":true} et imprimer les logs [whatsapp] dans Dokploy.
Ouvrir une session manuellement (bypass admin)
Quand un staff n'a pas encore activé sa session via WCARE_ACTIF :
- Dashboard → Personnel → bouton WA sur la fiche d'un membre
- Prompt → saisir le numéro E.164 → confirmer
Cible un notification_phone (manager) :
- Dashboard → Notifications → toggle WhatsApp ON → bouton ⚡ Activer
Une fois la session ouverte, le pipeline d'envoi tourne normalement.
Cas particulier — Mode général (équipe partagée)
Pour les petits établissements (un resto, un bistrot, un petit hôtel) qui
ne veulent pas créer un compte par personne, on active le mode général
sur l'établissement : un seul staff_member virtuel "Équipe" avec un
PIN unique partagé par tout le personnel.
Le mode WhatsApp supporte plusieurs numéros simultanés sur ce staff unique :
- Chaque membre scanne le QR + saisit le PIN équipe
- Clic Activer mes alertes WhatsApp → saisit son propre numéro
- Une
whatsapp_sessionest insérée avecstaff_member_id = équipe,phone_e164 = <son num>. Une session par humain.
À chaque signalement, sendServiceMessage broadcast à toutes les
sessions ouvertes du staff "Équipe" (dédup par numéro). Tout le monde
reçoit l'alerte sur son WhatsApp.
⚠️ Important : handleActif et openSessionManually ne ferment que
les sessions du même numéro (rotation propre sur un même tel), pas
toutes les sessions du staff_member. Sinon Pierre fermerait les sessions
d'Anne et Mehdi en activant la sienne — pas ce qu'on veut.
Quand un membre quitte l'équipe :
- Bouton Terminer sur sa page report → ferme uniquement sa session
- OU envoi
WCARE_STOPdepuis son WhatsApp → idem - Les autres collègues continuent à recevoir.
Limites pratiques en sandbox : Meta plafonne à 5 destinataires whitelistés. En prod, plus de limite (sauf rate limits standards Meta).
Résolution d'incidents
"Authentication Error" (code 190)
Le token Meta est mort. Causes possibles :
- Token temporaire 24h expiré (passe au permanent System User)
- Token révoqué (par toi côté Meta, ou auto-révoqué par Meta après détection de leak)
- App a changé de propriétaire / permissions modifiées
Fix :
- Génère un nouveau token (cf. section "Token permanent")
- Update
META_ACCESS_TOKENdans Dokploy - Reload le service
app - Vérifie le bandeau dashboard → 🟢 vert
"Object ... does not exist or missing permissions" (code 100)
Le token n'a pas accès au phone number ID configuré.
- Vérifie que le token est généré sur le bon WABA (sandbox vs prod ne sont pas interchangeables)
- Lors de la génération du System User token : coche tous les WABA pertinents dans le popup
Le webhook ne reçoit jamais de message
Possible causes :
- Webhook fields
messagespas coché côté Meta (le plus fréquent) - App en mode Development sans ajouter le numéro source en tester : Meta filtre les events des numéros inconnus de l'app en dev. Ajoute le compte Facebook lié au numéro WhatsApp source dans App Roles → Testers.
- URL webhook injoignable : test manuel via
curldu GET handshake - Signature rejetée :
META_APP_SECRETne correspond pas à l'app
Regarde les logs Dokploy avec [whatsapp/route] :
- Si tu vois
signature header MISSING→ Meta n'envoie pas correctement, vérifie l'URL exacte - Si
invalid signature→ mauvaisMETA_APP_SECRET
Session ouverte mais aucun message envoyé
Logs Dokploy filtre [whatsapp] :
no_open_window→ la session a expiré ou été fermée. Ré-ouvrir via WCARE_ACTIF ou bouton admin.cap_reached→ plafond mensuel atteint. AugmenterWHATSAPP_MONTHLY_CAP_CENTSou attendre le 1er du mois.api_error+ erreur Meta → cf. sections token / phone ID ci-dessus.
Le staff dit "j'ai cliqué Activer mais pas reçu de msg"
- Vérifier que le numéro saisi est correct (logs Dokploy :
[whatsapp] 🛠️ MANUAL session opened … → +33...) - Vérifier l'envoi sortant (
[whatsapp] 📤 sending …puis✅ message acceptedou❌ ...) - En sandbox : vérifier que le numéro est whitelisté dans la liste des destinataires Meta
- En prod : vérifier que le template
wcare_activation_welcomeest approuvé par Meta
Sécurité
- Jamais commit le token Meta ou l'App Secret dans git. Tout passe par Dokploy secrets.
- Régénérer le token si tu suspectes une fuite (Meta scanne GitHub, pastebins…)
- Webhook signature : HMAC SHA-256 sur le raw body avec
META_APP_SECRET. Tout webhook non signé / mal signé est rejeté avec 401. - Les logs server logguent les premiers caractères des tokens uniquement, jamais le secret entier.
Coûts — modèle France
| Type | Tarif unitaire (FR) | Quand ça arrive |
|---|---|---|
| Service (fenêtre 24h ouverte) | 0€ | Réponse à un user-initiated msg, ou pendant les 24h après un user-initiated |
| Utility (template approuvé) | ~0.024€ | 1er msg vers un user qui n'a jamais écrit, en prod |
| Authentication | ~0.024€ | Codes OTP (pas utilisé par Wcare) |
| Marketing | ~0.07€ | Promo, hors utility/auth — interdit côté Wcare |
Source : pricing Meta avril 2026 (cf. PDF Meta — section EUR rates).
ADR liés
- ADR-03 Notifications — Choix multi-canal email/WhatsApp/in-app
- ADR-06 Staff checks — Auto-claim implicite au scan