Ricevere i webhook di WhatsApp in una Edge Function di Supabase
Una Edge Function di Supabase può essere direttamente la destinazione del webhook del numero. Risponde al GET di verifica di Meta con il valore di hub.challenge, inserisce in una tabella Postgres ogni messaggio ricevuto via POST e risponde tramite la Cloud API. Fai il deploy con la verifica JWT disattivata: Supabase richiede un JWT valido per impostazione predefinita e Meta non può inviarlo.
L'impostazione predefinita di Supabase per le Edge Function: rifiuta qualsiasi richiesta senza un JWT valido prima che il tuo handler venga eseguito.
Il flag di supabase functions deploy che disattiva la verifica JWT per una funzione.
Timeout di inattività della richiesta per una Edge Function: se non c'è risposta entro quel tempo, ottieni un 504.
Cosa deve fare la Edge Function?
Due cose, su due metodi.
Su GET risponde alla verifica di Meta. Leggi hub.mode, hub.verify_token e hub.challenge dalla query string, confronta il token con quello che hai inserito come verify token della destinazione e restituisci il challenge come corpo in testo semplice con stato 200. Restituisci 403 se il token non corrisponde.
Su POST riceve gli eventi. Fai il parsing del corpo, estrai i messaggi da entry, changes e value, inseriscili in una tabella e restituisci 200. Mantieni la risposta veloce: Meta considera una risposta lenta o fallita come una consegna non riuscita e riprova, quindi lo stesso messaggio arriva due volte.
Imposta l'URL della funzione come destinazione del webhook del numero in EasyCoexistence, con lo stesso verify token, e Meta inizia a consegnare.
import { createClient } from "npm:@supabase/supabase-js@2";
const db = createClient(Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!);
Deno.serve(async (req) => {
const url = new URL(req.url);
if (req.method === "GET") {
const ok = url.searchParams.get("hub.verify_token") === Deno.env.get("VERIFY_TOKEN");
return new Response(ok ? url.searchParams.get("hub.challenge") : "forbidden", { status: ok ? 200 : 403 });
}
if (url.searchParams.get("key") !== Deno.env.get("URL_SECRET")) return new Response("no", { status: 401 });
const body = await req.json();
const rows = (body.entry ?? []).flatMap((e: any) => e.changes ?? [])
.flatMap((c: any) => (c.value?.messages ?? []).map((m: any) => ({
wamid: m.id, sender: m.from, kind: m.type, text: m.text?.body ?? null,
phone_number_id: c.value.metadata?.phone_number_id, raw: m,
})));
if (rows.length) await db.from("whatsapp_messages").upsert(rows, { onConflict: "wamid" });
return new Response("ok");
});Perché la funzione restituisce 401 a Meta?
Perché la verifica JWT è attiva. Supabase valida un JWT nell'header Authorization prima che l'handler di una Edge Function venga eseguito, ed è l'impostazione predefinita per ogni funzione. Meta non invia nessun header di questo tipo, quindi la piattaforma rifiuta sia il GET di verifica sia ogni consegna, e il tuo codice non li vede mai.
Disattivala solo per questa funzione. Puoi fare il deploy con supabase functions deploy whatsapp --no-verify-jwt, oppure impostare verify_jwt = false nella sezione della funzione in supabase/config.toml. La documentazione di Supabase descrive proprio questo caso: un endpoint pubblico chiamato da un provider di webhook esterno.
Con il controllo della piattaforma disattivato, la funzione ha bisogno di uno suo. L'header della firma non è disponibile, perché Meta firma con l'app secret dell'app che possiede la sottoscrizione. Metti un segreto lungo e casuale nell'URL di destinazione come parametro di query e rifiuta le richieste che ne sono prive, come fa l'esempio su POST.
Come deve salvare e rispondere la funzione?
Prima salva, poi rispondi. Fai un upsert sull'id del messaggio WhatsApp con un vincolo di unicità, così i nuovi tentativi di Meta finiscono sulla stessa riga invece di creare un duplicato. Se più numeri puntano alla funzione, conserva in ogni riga il phone_number_id che ha ricevuto il messaggio.
Per qualsiasi operazione lenta, come chiamare un modello o un'altra API prima di rispondere, affida il lavoro a EdgeRuntime.waitUntil e restituisci subito la risposta. La funzione continua a girare finché quella promise non si chiude, mentre Meta ha già il suo 200. Supabase smette di aspettare dopo 150 secondi di inattività e restituisce 504, quindi un handler che fa tutto il lavoro prima di rispondere è quello che più probabilmente causa nuovi tentativi.
Le risposte vanno all'endpoint dei messaggi della Cloud API, con il phone number id e il token del numero salvati come segreti della funzione. Entro 24 ore dall'ultimo messaggio del cliente il testo libero viene consegnato; fuori da quella finestra solo un template approvato.
I messaggi inviati dal telefono arrivano alla funzione?
Sì. Su un numero Coexistence l'app WhatsApp Business continua a funzionare e, quando qualcuno risponde da lì, Meta consegna un'eco di quel messaggio in uscita alla stessa destinazione, nel campo smb_message_echoes e in un array message_echoes invece che in messages.
Estendi il parser per leggere entrambi e salva una colonna direction. La tabella contiene così l'intera conversazione, le righe del cliente e le risposte dell'azienda, ed è ciò che la maggior parte delle persone vuole interrogare dopo: tempi di risposta, conversazioni senza risposta, l'ultimo orario in entrata per contatto.
Quell'ultimo valore è anche l'input di cui ogni percorso di invio ha bisogno, perché decide se una risposta può essere testo libero o deve essere un template. Una vista sulla tabella può esporlo per contatto al resto della tua app.
Errori comuni
- Fare il deploy lasciando attiva la verifica JWT. Supabase rifiuta le richieste di Meta con 401 prima che l'handler venga eseguito e nessun messaggio arriva mai.
- Restituire il challenge avvolto in JSON. Meta vuole il valore grezzo come corpo, altrimenti la verifica fallisce in silenzio.
- Fare lavoro lento prima di rispondere. Meta riprova ciò che vede come un fallimento e lo stesso messaggio viene elaborato due volte.
- Disattivare la verifica JWT senza alcun controllo tuo. Chiunque trovi l'URL può inserire messaggi falsi nella tabella.
Collega il numero su easycoexistence.com, imposta come destinazione del webhook l'URL della Edge Function con il tuo verify token e salva il phone number id e il token come segreti della funzione. Da US$ 9 per numero al mese, fino a US$ 1 man mano che aggiungi numeri, con i primi 3 giorni gratuiti.
Domande frequenti
Funziona sul piano gratuito di Supabase?
Le Edge Function girano sul piano gratuito, con un limite di tempo reale di 150 secondi contro i 400 dei piani a pagamento. Un handler di webhook che risponde in fretta resta ben dentro entrambi.
Devo usare la service role key nella funzione?
Solo lato server, come qui, leggendola dall'ambiente della funzione. Aggira la row level security, quindi non deve mai arrivare a un browser.
Una funzione può servire più numeri?
Sì. Punta la destinazione del webhook di ogni numero alla funzione e distingui in base al phone_number_id di ogni payload.
EasyCoexistence vede i messaggi?
No. Meta consegna ogni webhook direttamente alla tua funzione ed EasyCoexistence non memorizza mai il contenuto delle conversazioni.
Continua a leggere
Pronto per iniziare?
Configura WhatsApp Coexistence in pochi minuti, non in mesi. L'app continua a funzionare sul telefono.
Inizia la prova gratuitaVerificato il