Zaloguj sięRozpocznij bezpłatny okres próbny
Meta Tech Provider

Odbieranie webhooków WhatsApp w funkcji Supabase Edge Function

Funkcja Supabase Edge Function może być bezpośrednim celem webhooka numeru. Odpowiada na żądanie GET z weryfikacją od Meta, zwracając wartość hub.challenge, zapisuje każdą wiadomość przesłaną przez POST w tabeli Postgres i odpowiada przez Cloud API. Wdróż ją z wyłączoną weryfikacją JWT, ponieważ Supabase domyślnie wymaga prawidłowego JWT, a Meta go nie wyśle.

verify_jwt = true

Domyślne ustawienie Supabase dla Edge Functions: każde żądanie bez prawidłowego JWT jest odrzucane, zanim uruchomi się Twój handler.

--no-verify-jwt

Flaga polecenia supabase functions deploy, która wyłącza weryfikację JWT dla danej funkcji.

150 s

Limit bezczynności żądania dla Edge Function; brak odpowiedzi w tym czasie kończy się błędem 504.

Co musi robić Edge Function?

Dwie rzeczy, w dwóch metodach.

W metodzie GET odpowiada na weryfikację od Meta. Odczytaj hub.mode, hub.verify_token i hub.challenge z ciągu zapytania, porównaj token z tym, który podałeś jako token weryfikacyjny celu webhooka, i zwróć challenge jako zwykły tekst ze statusem 200. Gdy token się nie zgadza, zwróć 403.

W metodzie POST funkcja odbiera zdarzenia. Przeparsuj treść, wyciągnij wiadomości z entry, changes i value, zapisz je w tabeli i zwróć 200. Odpowiadaj szybko: Meta traktuje wolną lub nieudaną odpowiedź jako nieudaną dostawę i ponawia ją, co oznacza tę samą wiadomość dwa razy.

Ustaw adres URL funkcji jako cel webhooka numeru w EasyCoexistence, z tym samym tokenem weryfikacyjnym, a Meta zacznie dostarczać zdarzenia.

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");
});

Dlaczego funkcja zwraca Meta kod 401?

Ponieważ weryfikacja JWT jest włączona. Supabase sprawdza JWT w nagłówku Authorization, zanim uruchomi się handler Edge Function, i jest to ustawienie domyślne każdej funkcji. Meta nie wysyła takiego nagłówka, więc platforma odrzuca zarówno weryfikujące żądanie GET, jak i każdą dostawę, a Twój kod nigdy ich nie widzi.

Wyłącz weryfikację tylko dla tej funkcji. Wdróż ją poleceniem supabase functions deploy whatsapp --no-verify-jwt albo ustaw verify_jwt = false w sekcji tej funkcji w pliku supabase/config.toml. Dokumentacja Supabase opisuje dokładnie ten przypadek: publiczny endpoint wywoływany przez zewnętrznego dostawcę webhooków.

Gdy kontrola platformy jest wyłączona, funkcja potrzebuje własnej. Nagłówek z podpisem nie jest dla Ciebie dostępny, bo Meta podpisuje kluczem aplikacji, która jest właścicielem subskrypcji. Umieść w adresie URL celu długi losowy sekret jako parametr zapytania i odrzucaj żądania bez niego, tak jak robi to przykład w metodzie POST.

Jak funkcja ma zapisywać dane i odpowiadać?

Najpierw zapis, potem odpowiedź. Używaj upsert po identyfikatorze wiadomości WhatsApp z ograniczeniem unikalności, aby ponowienia od Meta trafiały do tego samego wiersza zamiast tworzyć duplikat. Jeśli do funkcji kieruje więcej niż jeden numer, zapisuj w każdym wierszu phone_number_id numeru odbierającego.

W przypadku wolnych operacji, takich jak wywołanie modelu lub innego API przed odpowiedzią, przekaż pracę do EdgeRuntime.waitUntil i od razu zwróć odpowiedź. Funkcja działa dalej, aż ta obietnica się rozstrzygnie, a Meta ma już swoje 200. Supabase przestaje czekać po 150 sekundach bezczynności i zwraca 504, więc handler, który wykonuje całą pracę przed odpowiedzią, najczęściej powoduje ponowienia.

Odpowiedzi trafiają do endpointu wiadomości Cloud API z identyfikatorem numeru telefonu i tokenem, przechowywanymi jako sekrety funkcji. W ciągu 24 godzin od ostatniej wiadomości klienta można wysłać dowolny tekst; poza tym oknem tylko zatwierdzony szablon.

Czy wiadomości wysłane z telefonu trafiają do funkcji?

Tak. Na numerze Coexistence aplikacja WhatsApp Business nadal działa, a gdy ktoś odpowiada z niej, Meta dostarcza echo tej wychodzącej wiadomości do tego samego celu, w polu smb_message_echoes i tablicy message_echoes, a nie w messages.

Rozbuduj parser tak, aby czytał oba i zapisywał kolumnę direction. Tabela zawiera wtedy całą rozmowę, wypowiedzi klienta i odpowiedzi firmy, a o to najczęściej pytamy później: czasy odpowiedzi, wątki bez odpowiedzi, czas ostatniej wiadomości przychodzącej dla każdego kontaktu.

Ta ostatnia wartość jest też danymi wejściowymi dla każdej ścieżki wysyłki, ponieważ decyduje, czy odpowiedź może być dowolnym tekstem, czy musi być szablonem. Widok na tabeli może udostępniać ją dla każdego kontaktu reszcie Twojej aplikacji.

Typowe błędy

  • Wdrożenie z pozostawioną weryfikacją JWT. Supabase odrzuca żądania Meta kodem 401, zanim uruchomi się handler, i żadna wiadomość nie dociera.
  • Zwracanie challenge opakowanego w JSON. Meta potrzebuje surowej wartości jako treści odpowiedzi, inaczej weryfikacja kończy się po cichu niepowodzeniem.
  • Wykonywanie wolnych operacji przed odpowiedzią. Meta ponawia to, co uzna za błędy, i ta sama wiadomość jest przetwarzana dwa razy.
  • Wyłączenie weryfikacji JWT bez żadnej własnej kontroli. Każdy, kto pozna adres URL, może wstawiać fałszywe wiadomości do tabeli.
Z EasyCoexistence zrobisz to

Połącz numer na easycoexistence.com, ustaw cel webhooka na adres URL Edge Function wraz z tokenem weryfikacyjnym i zapisz identyfikator numeru telefonu oraz token jako sekrety funkcji. Od US$ 9 za numer miesięcznie, spadając do US$ 1 w miarę dodawania numerów, z pierwszymi 3 dniami bezpłatnie.

Najczęściej zadawane pytania

Czy to działa w darmowym planie Supabase?

Edge Functions działają w darmowym planie, z limitem czasu 150 sekund wobec 400 sekund w planach płatnych. Handler webhooka, który odpowiada szybko, mieści się w obu z dużym zapasem.

Czy w funkcji powinienem używać klucza service role?

Tylko po stronie serwera, jak w tym przykładzie, odczytując go ze środowiska funkcji. Omija on zabezpieczenia na poziomie wierszy, więc nigdy nie może trafić do przeglądarki.

Czy jedna funkcja może obsługiwać kilka numerów?

Tak. Skieruj cel webhooka każdego numeru na nią i rozgałęziaj logikę według phone_number_id w każdym payloadzie.

Czy EasyCoexistence widzi wiadomości?

Nie. Meta dostarcza każdy webhook prosto do Twojej funkcji, a EasyCoexistence nigdy nie przechowuje treści rozmów.

Czytaj dalej

Gotowy, aby zacząć?

Skonfiguruj WhatsApp Coexistence w kilka minut, nie miesięcy. Aplikacja nadal działa na telefonie.

Rozpocznij bezpłatny okres próbny

Zweryfikowano w

Webhooki WhatsApp w Supabase Edge Functions