ΣύνδεσηΞεκινήστε δωρεάν δοκιμή
Meta Tech Provider

Λήψη webhooks του WhatsApp σε Supabase Edge Function

Μια Supabase Edge Function μπορεί να είναι απευθείας ο προορισμός webhook του αριθμού. Απαντά στο GET επαλήθευσης της Meta με την τιμή hub.challenge, εισάγει κάθε μήνυμα που φτάνει με POST σε έναν πίνακα Postgres και απαντά μέσω του Cloud API. Κάντε deploy με απενεργοποιημένη την επαλήθευση JWT, επειδή το Supabase απαιτεί από προεπιλογή έγκυρο JWT και η Meta δεν μπορεί να στείλει κάποιο.

verify_jwt = true

Η προεπιλογή του Supabase για τις Edge Functions: απορρίπτει κάθε αίτημα χωρίς έγκυρο JWT πριν εκτελεστεί ο handler σας.

--no-verify-jwt

Η παράμετρος του supabase functions deploy που απενεργοποιεί την επαλήθευση JWT για μια function.

150 s

Το χρονικό όριο αδράνειας αιτήματος για μια Edge Function: αν δεν υπάρξει απόκριση μέχρι τότε, επιστρέφεται 504.

Τι πρέπει να κάνει η Edge Function;

Δύο πράγματα, σε δύο μεθόδους.

Στο GET απαντά στην επαλήθευση της Meta. Διαβάστε τα hub.mode, hub.verify_token και hub.challenge από το query string, συγκρίνετε το token με αυτό που δηλώσατε ως verify token του προορισμού και επιστρέψτε το challenge ως απλό κείμενο με κατάσταση 200. Αν το token δεν ταιριάζει, επιστρέψτε 403.

Στο POST λαμβάνει τα συμβάντα. Αναλύστε το σώμα, εξαγάγετε τα μηνύματα από τα entry, changes και value, εισαγάγετέ τα σε έναν πίνακα και επιστρέψτε 200. Κρατήστε την απόκριση γρήγορη: η Meta θεωρεί μια αργή ή αποτυχημένη απάντηση ως αποτυχημένη παράδοση και ξαναδοκιμάζει, με αποτέλεσμα το ίδιο μήνυμα να φτάνει δύο φορές.

Ορίστε το URL της function ως προορισμό webhook του αριθμού στο EasyCoexistence, με το ίδιο verify token, και η Meta αρχίζει να παραδίδει.

supabase/functions/whatsapp/index.ts
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");
});

Γιατί η function επιστρέφει 401 στη Meta;

Επειδή η επαλήθευση JWT είναι ενεργή. Το Supabase ελέγχει ένα JWT στην κεφαλίδα Authorization πριν εκτελεστεί ο handler μιας Edge Function, και αυτό ισχύει από προεπιλογή για κάθε function. Η Meta δεν στέλνει τέτοια κεφαλίδα, οπότε η πλατφόρμα απορρίπτει τόσο το GET επαλήθευσης όσο και κάθε παράδοση, και ο κώδικάς σας δεν τα βλέπει ποτέ.

Απενεργοποιήστε την επαλήθευση μόνο για αυτή τη function. Κάντε deploy με supabase functions deploy whatsapp --no-verify-jwt ή ορίστε verify_jwt = false στην ενότητα της function στο supabase/config.toml. Η τεκμηρίωση του Supabase περιγράφει ακριβώς αυτή την περίπτωση: ένα δημόσιο endpoint που το καλεί εξωτερικός πάροχος webhook.

Με τον έλεγχο της πλατφόρμας κλειστό, η function χρειάζεται τον δικό της. Η κεφαλίδα υπογραφής δεν είναι διαθέσιμη σε εσάς, αφού η Meta υπογράφει με το app secret της εφαρμογής που κατέχει τη συνδρομή. Βάλτε ένα μακρύ τυχαίο μυστικό στο URL προορισμού ως παράμετρο query και απορρίψτε τα αιτήματα που δεν το περιέχουν, όπως κάνει το παράδειγμα στο POST.

Πώς πρέπει η function να αποθηκεύει και να απαντά;

Πρώτα αποθήκευση, μετά απάντηση. Κάντε upsert με βάση το id του μηνύματος WhatsApp και έναν περιορισμό μοναδικότητας, ώστε οι επαναλήψεις της Meta να καταλήγουν στην ίδια γραμμή αντί να δημιουργούν διπλότυπο. Κρατήστε το phone_number_id που παρέλαβε το μήνυμα σε κάθε γραμμή, αν περισσότεροι από έναν αριθμοί δείχνουν στη function.

Για οτιδήποτε αργό, όπως η κλήση ενός μοντέλου ή ενός άλλου API πριν από την απάντηση, αναθέστε την εργασία στο EdgeRuntime.waitUntil και επιστρέψτε αμέσως την απόκριση. Η function συνεχίζει να εκτελείται μέχρι να ολοκληρωθεί το promise, ενώ η Meta έχει ήδη λάβει το 200 της. Το Supabase σταματά να περιμένει στα 150 δευτερόλεπτα αδράνειας και επιστρέφει 504, οπότε ένας handler που κάνει όλη τη δουλειά πριν απαντήσει είναι ο πιο πιθανός να προκαλέσει επαναλήψεις.

Οι απαντήσεις πηγαίνουν στο endpoint messages του Cloud API, με το phone number id και το token του αριθμού, αποθηκευμένα ως secrets της function. Εντός 24 ωρών από το τελευταίο μήνυμα του πελάτη παραδίδεται ελεύθερο κείμενο· εκτός αυτού του χρονικού ορίου, μόνο ένα εγκεκριμένο template.

Φτάνουν στη function τα μηνύματα που στέλνονται από το τηλέφωνο;

Ναι. Σε έναν αριθμό Coexistence η εφαρμογή WhatsApp Business συνεχίζει να λειτουργεί, και όταν κάποιος απαντά από αυτήν, η Meta παραδίδει μια ηχώ του εξερχόμενου μηνύματος στον ίδιο προορισμό, κάτω από το πεδίο smb_message_echoes και έναν πίνακα message_echoes αντί για messages.

Επεκτείνετε τον parser ώστε να διαβάζει και τα δύο και να αποθηκεύει μια στήλη direction. Ο πίνακας τότε περιέχει ολόκληρη τη συνομιλία, τα μηνύματα του πελάτη και τις απαντήσεις της επιχείρησης, κάτι που οι περισσότεροι θέλουν να ανακτήσουν εκ των υστέρων: χρόνους απόκρισης, αναπάντητες συνομιλίες, την ώρα του τελευταίου εισερχόμενου ανά επαφή.

Αυτή η τελευταία τιμή είναι και η είσοδος που χρειάζεται κάθε διαδρομή αποστολής, αφού καθορίζει αν μια απάντηση μπορεί να είναι ελεύθερο κείμενο ή πρέπει να είναι template. Ένα view πάνω στον πίνακα μπορεί να την εκθέτει ανά επαφή στην υπόλοιπη εφαρμογή σας.

Συνηθισμένα λάθη

  • Deploy με την επαλήθευση JWT ενεργή. Το Supabase απορρίπτει τα αιτήματα της Meta με 401 πριν εκτελεστεί ο handler, και κανένα μήνυμα δεν φτάνει ποτέ.
  • Επιστροφή του challenge τυλιγμένου σε JSON. Η Meta χρειάζεται την ακατέργαστη τιμή ως σώμα, αλλιώς η επαλήθευση αποτυγχάνει αθόρυβα.
  • Αργή εργασία πριν από την απόκριση. Η Meta ξαναδοκιμάζει ό,τι θεωρεί αποτυχία και το ίδιο μήνυμα επεξεργάζεται δύο φορές.
  • Απενεργοποίηση της επαλήθευσης JWT χωρίς δικό σας έλεγχο. Όποιος βρει το URL μπορεί να στείλει πλαστά μηνύματα στον πίνακα.
Κάντε το με EasyCoexistence

Συνδέστε τον αριθμό στο easycoexistence.com, ορίστε ως προορισμό webhook το URL της Edge Function με το verify token σας και αποθηκεύστε το phone number id και το token ως secrets της function. Από US$ 9 ανά αριθμό τον μήνα, με πτώση στα US$ 1 όσο προσθέτετε αριθμούς, και οι πρώτες 3 ημέρες δωρεάν.

Συχνές ερωτήσεις

Λειτουργεί αυτό στο δωρεάν πλάνο του Supabase;

Οι Edge Functions τρέχουν στο δωρεάν πλάνο, με όριο 150 δευτερολέπτων wall clock έναντι 400 δευτερολέπτων στα επί πληρωμή πλάνα. Ένας handler webhook που απαντά γρήγορα μένει πολύ κάτω και από τα δύο.

Πρέπει να χρησιμοποιήσω το service role key στη function;

Μόνο στην πλευρά του server, όπως εδώ, διαβάζοντάς το από το περιβάλλον της function. Παρακάμπτει το row level security, οπότε δεν πρέπει να φτάσει ποτέ σε browser.

Μπορεί μία function να εξυπηρετεί περισσότερους αριθμούς;

Ναι. Ορίστε ως προορισμό webhook κάθε αριθμού την ίδια function και διακλαδώστε με βάση το phone_number_id σε κάθε payload.

Βλέπει το EasyCoexistence τα μηνύματα;

Όχι. Η Meta παραδίδει κάθε webhook απευθείας στη function σας, και το EasyCoexistence δεν αποθηκεύει ποτέ το περιεχόμενο των συνομιλιών.

Συνεχίστε την ανάγνωση

Έτοιμοι να ξεκινήσετε;

Ρυθμίστε το WhatsApp Coexistence σε λίγα λεπτά, όχι σε μήνες. Η εφαρμογή συνεχίζει να λειτουργεί στο τηλέφωνο.

Ξεκινήστε δωρεάν δοκιμή

Επαληθεύτηκε στις

WhatsApp webhooks σε Supabase Edge Functions