تسجيل الدخولابدأ الفترة التجريبية المجانية
Meta Tech Provider

استقبال webhooks من WhatsApp في Supabase Edge Function

يمكن أن تكون Supabase Edge Function وجهة خطاف الويب (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.

ما الذي يجب أن تفعله Edge Function؟

أمران، على طريقتين.

عند GET تجيب عن تحقق Meta. اقرأ hub.mode وhub.verify_token وhub.challenge من سلسلة الاستعلام، وقارن رمز التحقق (verify token) بالرمز الذي أدخلته للوجهة، ثم أعد قيمة التحدي كنص عادي في جسم الاستجابة مع الحالة 200. أعد 403 إذا لم يتطابق الرمز.

عند POST تستقبل الأحداث. حلّل الجسم، واستخرج الرسائل من entry وchanges وvalue، وأدرجها في جدول، ثم أعد 200. اجعل الاستجابة سريعة: تعدّ Meta الاستجابة البطيئة أو الفاشلة تسليمًا فاشلًا وتعيد المحاولة، فتصلك الرسالة نفسها مرتين.

اضبط عنوان URL الخاص بالدالة وجهةً لـ webhook الرقم في 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 هذه الترويسة، فترفض المنصة طلب GET للتحقق وكل عمليات التسليم، ولا يرى كودك أيًّا منها.

عطّله لهذه الدالة فقط. إما أن تنشر بالأمر supabase functions deploy whatsapp --no-verify-jwt، أو تضبط verify_jwt = false ضمن قسم الدالة في supabase/config.toml. تصف وثائق Supabase هذه الحالة بالضبط: نقطة نهاية (endpoint) عامة يستدعيها مزوّد webhook خارجي.

وبعد إيقاف تحقق المنصة تحتاج الدالة إلى تحققها الخاص. ترويسة التوقيع غير متاحة لك، لأن Meta توقّع بالسر الخاص بالتطبيق المالك للاشتراك. ضع سرًّا عشوائيًا طويلًا في عنوان URL الوجهة كمعامل استعلام، وارفض الطلبات التي تخلو منه، كما يفعل المثال عند POST.

كيف تخزّن الدالة الرسائل وترد؟

خزّن أولًا ثم ردّ لاحقًا. نفّذ upsert على معرّف رسالة WhatsApp مع قيد فريد، لتصل إعادات المحاولة من Meta إلى الصف نفسه بدل إنشاء نسخة مكررة. احتفظ بقيمة phone_number_id المستقبِل في كل صف إذا كان أكثر من رقم يشير إلى الدالة.

أما العمل البطيء، مثل استدعاء نموذج أو واجهة برمجة التطبيقات (API) أخرى قبل الرد، فسلّمه إلى EdgeRuntime.waitUntil وأعد الاستجابة فورًا. تواصل الدالة العمل حتى يكتمل هذا الوعد، بينما تكون Meta قد تلقّت 200 بالفعل. تتوقف Supabase عن الانتظار بعد 150 ثانية من الخمول وتعيد 504، لذلك فالمعالج الذي ينجز كل عمله قبل الاستجابة هو الأكثر عرضة لتسبّب إعادات المحاولة.

تذهب الردود إلى نقطة النهاية (endpoint) الخاصة بالرسائل في Cloud API، مع معرّف الرقم ورمز الوصول (token)، وكلاهما مخزّن كأسرار للدالة. خلال 24 ساعة من آخر رسالة للعميل يصل النص الحر، وبعدها لا يصل إلا قالب معتمد.

هل تصل الرسائل المرسلة من الهاتف إلى الدالة؟

نعم. على رقم Coexistence يستمر تطبيق WhatsApp Business بالعمل، وعندما يردّ أحدهم منه تسلّم Meta صدى تلك الرسالة الصادرة إلى الوجهة نفسها، تحت الحقل smb_message_echoes ومصفوفة message_echoes بدل messages.

وسّع المحلّل ليقرأ الاثنين، وخزّن عمود اتجاه (direction). عندها يحتوي الجدول على المحادثة كاملة، أي رسائل العميل وردود النشاط التجاري، وهذا ما يريد معظم الناس الاستعلام عنه لاحقًا: أزمنة الاستجابة، والمحادثات التي بلا رد، ووقت آخر رسالة واردة لكل جهة اتصال.

هذه القيمة الأخيرة هي أيضًا مدخل كل مسار إرسال، لأنها تحدد هل يمكن أن يكون الرد نصًا حرًا أم يجب أن يكون قالبًا. ويمكن لعرض (view) فوق الجدول أن يوفرها لكل جهة اتصال لبقية تطبيقك.

الأخطاء الشائعة

  • نشر الدالة مع ترك التحقق من JWT مفعّلًا. ترفض Supabase طلبات Meta بالخطأ 401 قبل أن يعمل المعالج، ولا تصل أي رسالة.
  • إعادة التحدي ملفوفًا في JSON. تحتاج Meta إلى القيمة الخام كجسم للاستجابة، وإلا يفشل التحقق بصمت.
  • تنفيذ عمل بطيء قبل الاستجابة. تعيد Meta محاولة ما تراه فاشلًا، فتُعالَج الرسالة نفسها مرتين.
  • إيقاف التحقق من JWT دون أي فحص خاص بك. يستطيع كل من يعثر على عنوان URL أن يرسل رسائل مزيفة إلى الجدول.
إنجاز ذلك باستخدام EasyCoexistence

اربط الرقم عبر easycoexistence.com، واضبط وجهة webhook على عنوان URL الخاص بالدالة مع رمز التحقق، وخزّن معرّف الرقم ورمز الوصول كأسرار للدالة. من US$ 9 لكل رقم شهريًا، وتنخفض إلى US$ 1 مع زيادة عدد الأرقام، مع فترة تجريبية مجانية لأول 3 أيام.

الأسئلة الشائعة

هل يعمل هذا على خطة Supabase المجانية؟

تعمل Edge Functions على الخطة المجانية، بحد زمني قدره 150 ثانية مقابل 400 ثانية في الخطط المدفوعة. والمعالج الذي يستجيب بسرعة يبقى بعيدًا جدًا عن أي من الحدين.

هل أستخدم مفتاح service role داخل الدالة؟

فقط على جهة الخادم، كما في المثال، مع قراءته من بيئة الدالة. فهو يتجاوز أمان مستوى الصف، ولذلك يجب ألا يصل إلى المتصفح أبدًا.

هل تخدم دالة واحدة عدة أرقام؟

نعم. وجّه وجهة webhook لكل رقم إليها، وفرّع المعالجة بحسب phone_number_id في كل حمولة.

هل ترى EasyCoexistence الرسائل؟

لا. تسلّم Meta كل webhook مباشرة إلى دالتك، ولا تخزّن EasyCoexistence محتوى المحادثات أبدًا.

تابع القراءة

هل أنت مستعد للبدء؟

أعد إعداد WhatsApp Coexistence خلال دقائق، وليس أشهر. يواصل التطبيق العمل على الهاتف.

ابدأ الفترة التجريبية المجانية

تم التحقق في

WhatsApp webhooks في Supabase Edge Functions