התחברותהתחילו תקופת ניסיון
Meta Tech Provider

קבלת וובהוקים של WhatsApp בפונקציית Supabase Edge

פונקציית Supabase Edge יכולה להיות יעד הוובהוק (Webhook) של המספר ישירות. היא עונה לבקשת ה-GET של האימות מ-Meta עם הערך hub.challenge, מכניסה כל הודעה שמגיעה ב-POST לטבלת Postgres, ומשיבה דרך Cloud API. מעלים אותה כשאימות JWT כבוי, כי Supabase דורשת כברירת מחדל JWT תקין ו-Meta לא יכולה לשלוח אחד כזה.

verify_jwt = true

ברירת המחדל של Supabase עבור Edge Functions: כל בקשה בלי JWT תקין נדחית עוד לפני שהקוד שלכם רץ.

--no-verify-jwt

הדגל של הפקודה supabase functions deploy שמכבה את אימות ה-JWT עבור פונקציה.

150 s

זמן ההמתנה ללא פעילות של Edge Function: בלי תגובה עד אז מוחזרת שגיאה 504

מה הפונקציה צריכה לעשות?

שני דברים, בשתי שיטות.

ב-GET הפונקציה עונה על האימות של Meta. קוראים את hub.mode, hub.verify_token ו-hub.challenge ממחרוזת השאילתה, משווים את האסימון לזה שהזנתם כאסימון האימות של היעד, ומחזירים את ה-challenge כגוף טקסט פשוט עם סטטוס 200. כשהאסימון לא תואם מחזירים 403.

ב-POST הפונקציה מקבלת אירועים. מפענחים את הגוף, שולפים את ההודעות מתוך entry, changes ו-value, מכניסים אותן לטבלה ומחזירים 200. חשוב להשיב מהר: Meta רואה תשובה איטית או כושלת כמשלוח שנכשל ומנסה שוב, וזה אומר אותה הודעה פעמיים.

מגדירים את כתובת ה-URL של הפונקציה כיעד הוובהוק של המספר ב-EasyCoexistence, עם אותו אסימון אימות, ו-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");
});

למה הפונקציה מחזירה 401 ל-Meta?

כי אימות JWT פעיל. Supabase בודקת JWT בכותרת Authorization לפני שהקוד של ה-Edge Function רץ, וזו ברירת המחדל לכל פונקציה. Meta לא שולחת כותרת כזו, ולכן הפלטפורמה דוחה גם את בקשת האימות וגם כל משלוח, והקוד שלכם אפילו לא רואה אותם.

מכבים את האימות לפונקציה הזאת בלבד. אפשר להעלות עם supabase functions deploy whatsapp --no-verify-jwt, או להגדיר verify_jwt = false בסעיף של הפונקציה בקובץ supabase/config.toml. התיעוד של Supabase מתאר בדיוק את המקרה הזה: נקודת קצה ציבורית שנקראת על ידי ספק וובהוקים חיצוני.

כשבדיקת הפלטפורמה כבויה, הפונקציה צריכה בדיקה משלה. כותרת החתימה לא זמינה לכם, כי Meta חותמת עם הסוד של האפליקציה שמחזיקה במינוי. מוסיפים סוד אקראי וארוך לכתובת היעד כפרמטר שאילתה, ודוחים בקשות בלעדיו, כמו שהדוגמה עושה ב-POST.

איך הפונקציה צריכה לשמור ולהשיב?

קודם שומרים, אחר כך משיבים. מבצעים upsert לפי מזהה ההודעה של WhatsApp עם אילוץ ייחודיות, כך שניסיונות חוזרים של Meta נוחתים על אותה שורה ולא יוצרים כפילות. שומרים בכל שורה את phone_number_id של המספר המקבל, אם יותר ממספר אחד מצביע על הפונקציה.

לכל עבודה איטית, כמו קריאה למודל או ל-API אחר לפני המענה, מעבירים את העבודה ל-EdgeRuntime.waitUntil ומחזירים תגובה מיד. הפונקציה ממשיכה לרוץ עד שההבטחה הזאת מסתיימת, ו-Meta כבר קיבלה את ה-200 שלה. Supabase מפסיקה להמתין אחרי 150 שניות ללא פעילות ומחזירה 504, ולכן מטפל שעושה את כל העבודה לפני התגובה הוא הסיכוי הגבוה ביותר לניסיונות חוזרים.

תשובות נשלחות לנקודת הקצה של ההודעות ב-Cloud API עם מזהה המספר והאסימון, ששמורים כסודות של הפונקציה. בתוך 24 שעות מההודעה האחרונה של הלקוח אפשר לשלוח טקסט חופשי, ומחוץ לחלון הזה רק תבנית מאושרת.

האם הודעות שנשלחות מהטלפון מגיעות לפונקציה?

כן. במספר Coexistence אפליקציית WhatsApp Business ממשיכה לעבוד, וכשמישהו משיב ממנה Meta שולחת הד של ההודעה היוצאת לאותו יעד, תחת השדה smb_message_echoes ובמערך message_echoes ולא ב-messages.

מרחיבים את המפענח כך שיקרא את שניהם, ושומרים עמודת direction. הטבלה מחזיקה אז את השיחה המלאה, הדברים של הלקוח והתשובות של העסק, וזה מה ששואלים עליו בדרך כלל אחר כך: זמני תגובה, שיחות שלא נענו, ומועד ההודעה הנכנסת האחרונה לכל איש קשר.

הערך האחרון הזה הוא גם הקלט שכל נתיב שליחה צריך, כי הוא קובע אם תשובה יכולה להיות טקסט חופשי או שחייבים תבנית. תצוגה (view) מעל הטבלה יכולה לחשוף אותו לכל איש קשר עבור שאר האפליקציה.

טעויות נפוצות

  • העלאה כשאימות JWT נשאר דלוק. Supabase דוחה את הבקשות של Meta עם 401 לפני שהמטפל רץ, ואף הודעה לא מגיעה.
  • החזרת ה-challenge עטוף ב-JSON. Meta צריכה את הערך הגולמי כגוף התגובה, אחרת האימות נכשל בשקט.
  • עבודה איטית לפני התגובה. Meta מנסה שוב את מה שנראה לה ככישלון, והודעה אחת מעובדת פעמיים.
  • כיבוי אימות JWT בלי בדיקה משלכם. כל מי שמוצא את כתובת ה-URL יכול להכניס הודעות מזויפות לטבלה.
עושים את זה עם EasyCoexistence

מחברים את המספר ב-easycoexistence.com, מגדירים את כתובת ה-Edge Function כיעד הוובהוק שלו יחד עם אסימון האימות, ושומרים את מזהה המספר והאסימון כסודות של הפונקציה. החל מ-US$ 9 למספר בחודש, יורד ל-US$ 1 ככל שמוסיפים מספרים, עם תקופת ניסיון של 3 ימים.

שאלות נפוצות

האם זה עובד בתוכנית החינמית של Supabase?

Edge Functions רצות גם בתוכנית החינמית, עם מגבלת זמן של 150 שניות לעומת 400 שניות בתוכניות בתשלום. מטפל וובהוק שמשיב מהר נשאר רחוק מאוד משתי המגבלות.

כדאי להשתמש במפתח service role בפונקציה?

רק בצד השרת, כמו בדוגמה, כשהוא נקרא ממשתני הסביבה של הפונקציה. המפתח עוקף אבטחה ברמת שורה, ולכן הוא אסור שיגיע לדפדפן.

אפשר לשרת כמה מספרים עם פונקציה אחת?

כן. מכוונים את יעד הוובהוק של כל מספר אליה, ומסתעפים לפי phone_number_id שמופיע בכל payload.

האם EasyCoexistence רואה את ההודעות?

לא. Meta שולחת כל וובהוק ישר לפונקציה שלכם, ו-EasyCoexistence לא שומרת תוכן של שיחות.

המשיכו לקרוא

מוכנים להתחיל?

מגדירים WhatsApp Coexistence בתוך דקות, לא חודשים. האפליקציה ממשיכה לעבוד בטלפון.

התחילו תקופת ניסיון בחינם

נבדק ב-

וובהוקים של WhatsApp ב-Supabase Edge Functions