MasukMulai uji coba gratis
Meta Tech Provider

Menerima webhook WhatsApp di Supabase Edge Function

Supabase Edge Function bisa langsung menjadi tujuan webhook sebuah nomor. Fungsi ini menjawab GET verifikasi Meta dengan nilai hub.challenge, memasukkan setiap pesan yang dikirim lewat POST ke tabel Postgres, dan membalas lewat Cloud API. Deploy dengan verifikasi JWT dimatikan, karena Supabase secara default mewajibkan JWT yang valid dan Meta tidak bisa mengirimkannya.

verify_jwt = true

Pengaturan default Supabase untuk Edge Function, yang menolak setiap permintaan tanpa JWT valid sebelum handler Anda berjalan.

--no-verify-jwt

Flag supabase functions deploy yang menonaktifkan verifikasi JWT untuk satu fungsi.

150 s

Batas waktu diam permintaan untuk Edge Function; jika tidak ada respons sampai batas itu, hasilnya 504.

Apa yang harus dilakukan Edge Function?

Dua hal, pada dua metode.

Pada GET, fungsi menjawab verifikasi Meta. Baca hub.mode, hub.verify_token, dan hub.challenge dari query string, bandingkan token dengan verify token yang Anda masukkan pada tujuan webhook, lalu kembalikan challenge sebagai body teks biasa dengan status 200. Kembalikan 403 jika token tidak cocok.

Pada POST, fungsi menerima event. Parse body, ambil pesan dari entry, changes, dan value, masukkan ke tabel, lalu kembalikan 200. Jaga respons tetap cepat: Meta menganggap jawaban yang lambat atau gagal sebagai pengiriman yang gagal dan mencoba kembali, sehingga pesan yang sama datang dua kali.

Atur URL fungsi sebagai tujuan webhook nomor di EasyCoexistence, dengan verify token yang sama, lalu Meta mulai mengirim.

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

Mengapa fungsi mengembalikan 401 ke Meta?

Karena verifikasi JWT aktif. Supabase memvalidasi JWT di header Authorization sebelum handler Edge Function berjalan, dan itu adalah pengaturan default untuk setiap fungsi. Meta tidak mengirim header semacam itu, sehingga platform menolak GET verifikasi maupun setiap pengiriman, dan kode Anda tidak pernah melihatnya.

Matikan hanya untuk fungsi ini. Deploy dengan supabase functions deploy whatsapp --no-verify-jwt, atau atur verify_jwt = false pada bagian fungsi tersebut di supabase/config.toml. Dokumentasi Supabase menjelaskan kasus yang persis sama ini: endpoint publik yang dipanggil oleh penyedia webhook dari luar.

Setelah pemeriksaan platform dimatikan, fungsi perlu pemeriksaan sendiri. Header tanda tangan tidak tersedia bagi Anda, karena Meta menandatangani dengan app secret milik aplikasi yang memegang langganan. Taruh secret acak yang panjang di URL tujuan sebagai parameter query dan tolak permintaan tanpa secret itu, seperti pada contoh untuk POST.

Bagaimana fungsi sebaiknya menyimpan dan membalas?

Simpan dulu, balas belakangan. Lakukan upsert pada id pesan WhatsApp dengan unique constraint, agar percobaan ulang dari Meta masuk ke baris yang sama dan tidak membuat duplikat. Simpan phone_number_id penerima di setiap baris jika lebih dari satu nomor mengarah ke fungsi ini.

Untuk pekerjaan yang lambat, misalnya memanggil model atau API lain sebelum membalas, serahkan ke EdgeRuntime.waitUntil dan kembalikan respons segera. Fungsi tetap berjalan sampai promise itu selesai, sementara Meta sudah menerima 200-nya. Supabase berhenti menunggu setelah 150 detik waktu diam dan mengembalikan 504, jadi handler yang menyelesaikan semua pekerjaan sebelum merespons paling mungkin memicu percobaan ulang.

Balasan dikirim ke endpoint pesan Cloud API dengan phone number id dan token nomor tersebut, yang disimpan sebagai secret fungsi. Dalam 24 jam sejak pesan terakhir pelanggan, teks bebas terkirim; di luar itu hanya template yang disetujui.

Apakah pesan yang dikirim dari ponsel sampai ke fungsi?

Ya. Pada nomor Coexistence, aplikasi WhatsApp Business tetap berfungsi, dan ketika seseorang membalas dari sana, Meta mengirim echo dari pesan keluar itu ke tujuan yang sama, di bawah field smb_message_echoes dan array message_echoes, bukan messages.

Perluas parser agar membaca keduanya dan simpan kolom direction. Tabel lalu berisi percakapan lengkap, ucapan pelanggan dan jawaban bisnis, yang biasanya ingin Anda kueri kemudian: waktu respons, utas yang belum dijawab, waktu pesan masuk terakhir per kontak.

Nilai terakhir itu juga menjadi masukan bagi setiap jalur pengiriman, karena menentukan apakah balasan boleh berupa teks bebas atau harus berupa template. Sebuah view atas tabel dapat menyediakannya per kontak untuk bagian lain aplikasi Anda.

Kesalahan umum

  • Deploy dengan verifikasi JWT tetap aktif. Supabase menolak permintaan Meta dengan 401 sebelum handler berjalan, dan tidak ada pesan yang pernah tiba.
  • Mengembalikan challenge yang dibungkus JSON. Meta membutuhkan nilai mentah sebagai body, atau verifikasi gagal diam-diam.
  • Mengerjakan pekerjaan lambat sebelum merespons. Meta mencoba kembali apa yang dianggapnya gagal, dan pesan yang sama diproses dua kali.
  • Mematikan verifikasi JWT tanpa pemeriksaan sendiri. Siapa pun yang menemukan URL dapat mengirim pesan palsu ke tabel.
Melakukan ini dengan EasyCoexistence

Hubungkan nomor di easycoexistence.com, atur tujuan webhook-nya ke URL Edge Function dengan verify token Anda, lalu simpan phone number id dan token sebagai secret fungsi. Mulai US$ 9 per nomor per bulan, turun menjadi US$ 1 seiring bertambahnya nomor, dengan 3 hari pertama gratis.

Pertanyaan yang sering diajukan

Apakah ini berfungsi di paket gratis Supabase?

Edge Function berjalan di paket gratis, dengan batas wall clock 150 detik dibandingkan 400 detik di paket berbayar. Handler webhook yang merespons dengan cepat tetap jauh di dalam kedua batas itu.

Haruskah saya memakai service role key di dalam fungsi?

Hanya di sisi server, seperti di sini, dibaca dari environment fungsi. Kunci ini melewati row level security, jadi tidak boleh sampai ke browser.

Bisakah satu fungsi melayani beberapa nomor?

Bisa. Arahkan tujuan webhook setiap nomor ke fungsi itu dan bercabang berdasarkan phone_number_id di setiap payload.

Apakah EasyCoexistence melihat pesannya?

Tidak. Meta mengirim setiap webhook langsung ke fungsi Anda, dan EasyCoexistence tidak pernah menyimpan isi percakapan.

Lanjut membaca

Siap memulai?

Konfigurasikan WhatsApp Coexistence dalam hitungan menit, bukan bulan. Aplikasi tetap berfungsi di ponsel.

Mulai uji coba gratis

Diverifikasi pada

Webhook WhatsApp di Supabase Edge Function