My App

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 principal
  • apps/server/src/routes/whatsapp.ts — webhook entrant Meta
  • apps/server/src/routes/staff.ts — endpoints staff (activate, status, stop, sync-link)
  • packages/api/src/routers/notification-phone.router.ts — endpoints manager
  • packages/api/src/services/notification-runner.ts — runner qui dépile les jobs

Variables d'environnement

VariableObligatoireDescription
META_WABA_IDouiID du WhatsApp Business Account (visible sur business.facebook.com)
META_PHONE_NUMBER_IDouiID Meta du numéro WhatsApp utilisé pour l'envoi
META_ACCESS_TOKENouiToken d'accès — System User token permanent en prod, temporaire 24h en dev
META_APP_SECRETouiClé secrète de l'app Meta (signe les webhooks via HMAC SHA-256)
META_VERIFY_TOKENouiString random définie par toi, utilisée pour valider l'URL webhook côté Meta
META_WHATSAPP_NUMBERnonNuméro WhatsApp Wcare au format E.164 (ex: +15556354086) — utilisé par les liens wa.me. Si absent, lookup automatique via API Meta
META_API_VERSIONnonVersion Graph API (défaut: v25.0)
META_TEMPLATE_ACTIVATION_NAMEen prod uniquementNom du template approuvé pour le msg de bienvenue. Sans cette variable, le service envoie du texte libre via sendRaw (sandbox uniquement).
META_TEMPLATE_ACTIVATION_LANGnonLangue du template (défaut: fr)
WHATSAPP_MONTHLY_CAP_CENTSnonPlafond mensuel en centimes (défaut: 1000 = 10€)

Setup initial (côté Meta)

1. Créer l'app Meta

  1. Va sur https://developers.facebook.com/appsCréer une app
  2. Type : Business
  3. Use case : Tisser des liens avec votre clientèle via WhatsApp
  4. Lie l'app à ton Business Portfolio Reciprok
  5. Note l'App ID + l'App Secret (Settings → Basic → Clé secrète)

2. WhatsApp Business Account (WABA)

  1. business.facebook.com → Paramètres businessComptes WhatsApp
  2. Crée le WABA :
    • Catégorie : Services professionnels
    • Display name : Wcare
  3. 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
  4. 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.ts

Output 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 :

  1. business.facebook.com → Paramètres businessUtilisateurs système
  2. Ajouter → nom wcare-server → rôle Admin
  3. Ajouter des actifs → coche le WABA Wcare → Contrôle complet
  4. Générer un nouveau token :
    • App : sélectionne l'app Wcare
    • Expiration : Jamais
    • Permissions : whatsapp_business_messaging + whatsapp_business_management
  5. Copie immédiatement le token — Meta ne le réaffiche jamais.

4. Webhook

  1. App Meta → WhatsApp → Configuration → Webhooks
  2. URL de rappel : https://api.preprod.wcare.fr/whatsapp/webhook (ou api.wcare.fr/... en prod)
  3. Verify token : la string aléatoire que tu mets dans META_VERIFY_TOKEN
  4. Clic Vérifier et enregistrer → Wcare doit répondre 200 <challenge> (vérifie via curl avant)
  5. 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: ping123

5. Whitelist destinataires (sandbox uniquement)

En sandbox, le numéro de test ne peut envoyer qu'à 5 destinataires explicitement whitelistés.

  1. App Meta → WhatsApp → API setup → "À"
  2. Manage phone number list → ajoute jusqu'à 5 numéros
  3. 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=1000

Aprè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

  1. business.facebook.com → WhatsApp Manager (lien : https://business.facebook.com/wa/manage/message-templates)
  2. Créer un template :
    • Nom : wcare_activation_welcome
    • Catégorie : UTILITY
    • Langue : Français (fr)

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=fr

puis 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: alive

Vé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 ping

Doit 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 :

  1. Dashboard → Personnel → bouton WA sur la fiche d'un membre
  2. Prompt → saisir le numéro E.164 → confirmer

Cible un notification_phone (manager) :

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

  1. Chaque membre scanne le QR + saisit le PIN équipe
  2. Clic Activer mes alertes WhatsApp → saisit son propre numéro
  3. Une whatsapp_session est insérée avec staff_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_STOP depuis 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 :

  1. Génère un nouveau token (cf. section "Token permanent")
  2. Update META_ACCESS_TOKEN dans Dokploy
  3. Reload le service app
  4. 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 messages pas 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 curl du GET handshake
  • Signature rejetée : META_APP_SECRET ne 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 → mauvais META_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. Augmenter WHATSAPP_MONTHLY_CAP_CENTS ou 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"

  1. Vérifier que le numéro saisi est correct (logs Dokploy : [whatsapp] 🛠️ MANUAL session opened … → +33...)
  2. Vérifier l'envoi sortant ([whatsapp] 📤 sending … puis ✅ message accepted ou ❌ ...)
  3. En sandbox : vérifier que le numéro est whitelisté dans la liste des destinataires Meta
  4. En prod : vérifier que le template wcare_activation_welcome est 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

TypeTarif 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

On this page