Stocker les messages reçus (Firebase)

Pas-à-pas complet : recevoir le webhook message.inbound et l'écrire dans Firestore, depuis une Cloud Function.

Pourquoi stocker chez vous

Nous ne stockons plus les messages entrants : le webhook message.inbound est le seul événement, livré une fois, en temps réel. Si vous voulez un historique de conversation, un tableau de bord, ou nourrir votre CRM, il faut l'écrire vous-même dans une base au moment où vous le recevez. Ce guide détaille le cas Firebase/Firestore de bout en bout ; le même schéma marche avec Supabase ou n'importe quelle base : recevez le webhook → écrivez où vous voulez.

Étape 1 — Créer la collection Firestore

Créez une collection wa_messages (Firestore, base « native mode »). Chaque document représentera un message reçu, avec les champs du payload message.inbound plus un horodatage serveur.

Champs du document

ParamètreTypeRequisDescription
fromstringNonNuméro du client, E.164.
contact_namestring | nullNonNom du profil WhatsApp du client, si Meta le fournit.
numberstringNonphone_number_id qui a reçu le message (pour répondre avec le bon numéro).
textstring | nullNonTexte du message (null pour un média sans légende).
typestringNontext, image, audio, video, document ou sticker.
media_idstring | nullNonIdentifiant Meta du média (pas les octets).
wa_message_idstringNonwamid Meta — utile pour dédupliquer une redelivery.
timestampstring (ISO)NonHorodatage de réception envoyé par Meta.
receivedAtTimestampNonHorodatage serveur Firestore (FieldValue.serverTimestamp()).

Étape 2 — Une Cloud Function HTTPS qui reçoit le webhook

Créez une fonction onRequest (2ᵉ génération ou 1ʳᵉ, peu importe) qui : (1) vérifie la signature HMAC de l'en-tête X-Camairetech-Signature avec votre secret de webhook (affiché une seule fois à l'enregistrement — voir la section Webhooks), (2) ignore tout événement autre que message.inbound, puis (3) écrit un document dans wa_messages.

Attention — Vérifiez sur le corps brut
Le HMAC se calcule sur les octets bruts du corps, avant tout JSON.parse. Avec les Cloud Functions Firebase, req.rawBody expose ce Buffer même quand Express a déjà parsé req.body — utilisez-le pour la vérification.

functions/index.js

const functions = require("firebase-functions");
const admin = require("firebase-admin");
const crypto = require("crypto");

admin.initializeApp();
const db = admin.firestore();

// Store the webhook secret with `firebase functions:config:set camairetech.webhook_secret="…"`
// (or use a Secret Manager binding on 2nd-gen functions).
const WEBHOOK_SECRET = functions.config().camairetech.webhook_secret;

exports.waInbound = functions.https.onRequest(async (req, res) => {
  if (req.method !== "POST") {
    return res.status(405).send("method not allowed");
  }

  const rawBody = req.rawBody; // Buffer — verify BEFORE using the parsed req.body
  const signature = req.get("X-Camairetech-Signature") || "";
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", WEBHOOK_SECRET).update(rawBody).digest("hex");

  const signatureOk =
    signature.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));

  if (!signatureOk) {
    return res.status(401).send("invalid signature");
  }

  const { event, data } = req.body;
  if (event !== "message.inbound") {
    return res.status(200).send("ignored");
  }

  await db.collection("wa_messages").add({
    from: data.from,
    contact_name: data.contact_name || null,
    number: data.number,
    text: data.text || null,
    type: data.type,
    media_id: data.media_id || null,
    wa_message_id: data.wa_message_id,
    timestamp: data.timestamp,
    receivedAt: admin.firestore.FieldValue.serverTimestamp(),
  });

  return res.status(200).send("ok");
});

Déployez avec firebase deploy --only functions:waInbound. L'URL déployée ressemble à https://REGION-PROJECT.cloudfunctions.net/waInbound (ou votre domaine si vous utilisez Firebase Hosting en rewrite).

Étape 3 — Enregistrer l'URL dans la console

Dans la console développeur, onglet Webhook, collez l'URL de la Cloud Function et enregistrez. Le secret de signature s'affiche une seule fois : copiez-le dans WEBHOOK_SECRET (config Firebase ou Secret Manager) avant de fermer la page.

Étape 4 — Règles de sécurité Firestore

La Cloud Function écrit avec le SDK Admin, qui contourne les règles de sécurité — vous n'avez donc rien à ouvrir en écriture. Verrouillez wa_messages contre tout accès client direct ; lisez-la uniquement depuis votre propre backend authentifié ou votre tableau de bord.

firestore.rules

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /wa_messages/{docId} {
      // Only your Cloud Function (Admin SDK) writes here — Admin SDK calls
      // bypass these rules entirely. Lock out every direct client read/write;
      // serve the data through your own authenticated backend instead.
      allow read, write: if false;
    }
  }
}

Cas d'usage

Astuce — Même schéma avec Supabase ou une autre base
Rien de spécifique à Firebase dans ce schéma : recevez le POST message.inbound, vérifiez sa signature HMAC, puis écrivez la ligne où vous voulez — une table Supabase/Postgres, une queue, un data warehouse. Seule l'étape 2 change de SDK.