Pas-à-pas complet : recevoir le webhook message.inbound et l'écrire dans Firestore, depuis une Cloud Function.
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.
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ètre | Type | Requis | Description |
|---|---|---|---|
from | string | Non | Numéro du client, E.164. |
contact_name | string | null | Non | Nom du profil WhatsApp du client, si Meta le fournit. |
number | string | Non | phone_number_id qui a reçu le message (pour répondre avec le bon numéro). |
text | string | null | Non | Texte du message (null pour un média sans légende). |
type | string | Non | text, image, audio, video, document ou sticker. |
media_id | string | null | Non | Identifiant Meta du média (pas les octets). |
wa_message_id | string | Non | wamid Meta — utile pour dédupliquer une redelivery. |
timestamp | string (ISO) | Non | Horodatage de réception envoyé par Meta. |
receivedAt | Timestamp | Non | Horodatage serveur Firestore (FieldValue.serverTimestamp()). |
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).
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.
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;
}
}
}
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.