Webhook14 Oktober 20269 mnt baca

Handler Webhook Anda Memanggil LLM. Pengirim Memutus Koneksi Lebih Dulu.

Slack menunggu 3 detik, Shopify 5, GitHub 10. Panggilan LLM lebih lama, jadi pengirim memutus koneksi. Jawab dulu, atau pakai relay.

DI DALAM HANDLER · PENGIRIM MENUNGGU MODEL ANDASlackverifikasi + panggilan LLM8 dtkSlack memutus koneksi pada 3 dtkretry → jalan dua kaliDI BALIK RELAY · HITUNGAN WAKTU PENGIRIM BERHENTI DI SANASlackAnyHookdisimpan, lalu 200antreanpercobaan ulanghandlerpanggilan LLM ≤60 dtkpengirim mendapat respons 200 dalam sekitar satu detik; model Anda mendapat waktu yang dibutuhkannya
Di dalam handler, pengirim ikut menunggu panggilan model Anda. Di balik relay, pengirim hanya menunggu relay.

Pengirim webhook menunggu hitungan detik, bukan menit: Slack memberi handler Anda 3 detik untuk menjawab, Shopify 5, GitHub 10, sedangkan handler yang memanggil LLM biasanya butuh lebih lama, sehingga pengirim mencatat kegagalan dan mencoba ulang (retry) atau menyerah; jika batas waktu terlewat cukup sering, Slack mematikan langganan event Anda, dan Shopify dapat menghapus webhook tersebut. Solusinya adalah menjawab 200 begitu permintaan terverifikasi dan memanggil model setelah respons terkirim, entah lewat pekerjaan latar belakang yang Anda jalankan sendiri atau di balik relay yang menjawab pengirim untuk Anda.

Sisa artikel ini adalah versi panjang dari dua kalimat itu: berapa lama setiap pengirim benar-benar menunggu, apa yang dilakukannya saat Anda terlambat, empat cara singkat untuk menjawab dulu dan mengerjakannya kemudian (beserta kelemahan masing-masing), dan apa yang berubah bila relay yang menyimpan event-nya.

TL;DR

  • Waktu tunggu pengirim mencakup seluruh handler Anda: pemeriksaan tanda tangan (signature), pembacaan database, dan panggilan model
  • Lewat dari batas itu, pengirim mencoba ulang. Handler yang sudah mengerjakan tugasnya akan mengerjakannya lagi: dua balasan di Slack, satu pesanan diberi tag dua kali, dua email
  • Jika cukup sering terlambat, Slack menonaktifkan event subscription Anda, Shopify bisa menghapus webhook-nya. GitHub sama sekali tidak pernah mencoba ulang
  • Jawab dulu, kerjakan kemudian: after() di Next.js, ctx.waitUntil() di Cloudflare (paling lama 30 detik), tugas latar belakang di Express atau FastAPI
  • Tidak satu pun dari cara itu menyimpan event jika pekerjaan latar belakangnya mati. Versi yang persisten butuh penyimpanan, worker, dan jadwal percobaan ulang, yang tidak lain adalah antrean
  • Relay adalah antrean yang tidak perlu Anda tulis: AnyHook menjawab pengirim dalam sekitar satu detik dan memberi handler Anda 60 detik di paket Free, dalam batas jatah komputasi (compute allowance) paket tersebut, dengan percobaan ulang

Berapa lama setiap pengirim menunggu

Data ini diambil dari dokumentasi masing-masing vendor dan kami kumpulkan beserta sumbernya di referensi provider webhook kami.

PengirimMenunggu 2xx dari AndaSetelah pengiriman gagal
Slack3 detik3 percobaan ulang: segera, setelah 1 menit, setelah 5 menit. Event subscription dinonaktifkan jika lebih dari 95% upaya gagal dalam 60 menit
LINE2 detik (jawaban yang lebih lambat dicatat sebagai request_timeout)Pengiriman ulang nonaktif secara default. LINE dapat menangguhkan pengiriman webhook jika server Anda terus gagal
Shopify5 detik (1 detik untuk terhubung)8 percobaan ulang dalam 4 jam. Subscription yang dibuat lewat Admin API dihapus setelah 8 kegagalan berturut-turut
HubSpot5 detik, untuk seluruh batch berisi hingga 100 notifikasiHingga 10 upaya yang tersebar dalam 24 jam
Paddle5 detikMode live: hingga 60 upaya dalam sekitar 2 hari
GitHub10 detikTidak ada. Anda dapat mengirim ulang secara manual selama 3 hari
Twilio15 detik secara default, dapat diatur1 percobaan ulang secara default, dapat diatur dari 0 sampai 5
StripeTidak dipublikasikan. Dokumentasinya meminta Anda mengembalikan 2xx sebelum logika yang lambatBackoff eksponensial hingga 3 hari dalam mode live

Batas waktu Anda yang sebenarnya adalah milik pengirim paling ketat yang mengirim ke Anda. Bot di Slack punya 3 detik, dan cold start bisa menghabiskan sebagian besar waktu itu sebelum baris pertama kode Anda berjalan.

Mengapa panggilan LLM membuatnya gagal

Panggilan model adalah langkah yang lambat di hampir setiap handler yang ditulis pada 2026. Completion singkat butuh beberapa detik; completion dengan pemanggilan tool, jawaban panjang, atau percobaan ulang di dalam SDK Anda butuh puluhan detik. Tambahkan cold start, dan pengirim sudah memutus koneksi sebelum token pertama tiba.

Apa yang terjadi selanjutnya tergantung pengirimnya, dan tidak ada satu pun yang baik:

  • Slack mencoba ulang. Handler Anda menerima event yang sama lagi saat eksekusi pertama masih menunggu model. Kedua eksekusi selesai dan keduanya mengirim pesan. Pengguna melihat bot menjawab dua kali
  • Shopify mencoba ulang, lalu menghapus. Delapan kegagalan berturut-turut dalam empat jam, dan subscription yang dibuat lewat Admin API hilang. Pesanan terus masuk, tetapi aplikasi Anda tidak lagi mendapat kabar tentangnya
  • GitHub tidak melakukan apa-apa. Pengiriman ditandai gagal dan berhenti di situ, kecuali ada yang mengirimnya ulang secara manual dalam tiga hari
  • Pengiriman ulang di LINE nonaktif kecuali Anda menyalakannya, jadi satu timeout berarti satu pesan yang tidak pernah dijawab bot Anda

Handler itu tidak gagal. Ia hanya lebih lambat daripada jam yang tidak dapat dilihatnya.

Solusinya: jawab dulu, kerjakan kemudian

Verifikasi permintaan, jawab 200, lalu panggil model. Setiap platform punya cara untuk tetap berjalan setelah respons dikirim. Masing-masing juga punya kelemahan, yang dijelaskan di bawah kodenya.

Next.js (App Router)

// app/api/slack/events/route.ts
import { after } from "next/server";

export async function POST(req: Request) {
  const raw = await req.text();
  if (!verifySlackSignature(raw, req.headers)) {
    return new Response("bad signature", { status: 401 });
  }
  const body = JSON.parse(raw);
  if (body.type === "url_verification") {
    return Response.json({ challenge: body.challenge });
  }

  after(async () => {
    const answer = await askModel(body.event.text); // the slow part
    await postToSlack(body.event.channel, answer);
  });

  return new Response(null, { status: 200 }); // well inside 3 seconds
}

Kelemahannya: after() berjalan selama durasi maksimum route tersebut dan tidak lebih. Jika panggilan model melempar error, atau instance-nya didaur ulang, event itu hilang. Slack sudah menerima respons 200 dan tidak akan mengirimnya lagi, dan tidak ada yang mencatat bahwa event itu pernah ada.

Cloudflare Workers

export default {
  async fetch(req: Request, env: Env, ctx: ExecutionContext) {
    const raw = await req.text();
    if (!(await verify(raw, req.headers, env))) {
      return new Response("bad signature", { status: 401 });
    }
    ctx.waitUntil(handle(JSON.parse(raw), env)); // runs after the response
    return new Response(null, { status: 200 });
  },
};

Kelemahannya: waitUntil mendapat paling lama 30 detik setelah respons, dibagi untuk semua yang Anda serahkan kepadanya, lalu dibatalkan. Panggilan model yang lambat tidak muat di sana. Jawaban Cloudflare sendiri untuk pekerjaan yang lebih panjang adalah Queue dengan consumer Worker, dan itu jawaban yang tepat. Panduan Workers kami membahas jebakan lain di platform ini.

Express atau server Node lain yang berjalan terus-menerus

app.post("/webhooks/slack", express.raw({ type: "application/json" }), (req, res) => {
  if (!verifySlackSignature(req.body, req.headers)) return res.sendStatus(401);
  res.sendStatus(200); // answer first
  setImmediate(() => handle(JSON.parse(req.body)).catch(console.error));
});

Kelemahannya: cara ini aman sampai prosesnya restart. Deploy, crash, atau proses yang dimatikan karena kehabisan memori di tengah panggilan akan menghilangkan apa pun yang sedang berjalan. Di host serverless, proses juga bisa dibekukan begitu respons terkirim.

Python (FastAPI)

from fastapi import BackgroundTasks, FastAPI, Request, Response

app = FastAPI()

@app.post("/webhooks/line")
async def line_webhook(request: Request, background: BackgroundTasks):
    raw = await request.body()
    if not verify_line_signature(raw, request.headers.get("x-line-signature")):
        return Response(status_code=401)
    background.add_task(handle_events, raw)  # runs after the response is sent
    return Response(status_code=200)

Kelemahannya sama dengan Express: tugas itu hidup di dalam proses. Restart prosesnya, dan tugas itu hilang, tanpa percobaan ulang dan tanpa catatan.

Kesamaan keempatnya

Masing-masing menjawab 200 untuk event yang, pada saat itu, hanya ada di memori. Pengirim tidak peduli lagi, dan jika pekerjaan latar belakang Anda gagal, tidak ada kesempatan kedua dan tidak ada yang bisa diperiksa sesudahnya.

Versi yang persisten punya bentuk yang sudah dikenal: catat event sebelum Anda menjawab, lalu biarkan worker memprosesnya dan mencobanya ulang saat gagal, dan simpan catatan yang cukup untuk melihat apa yang gagal lalu mengirimnya lagi. Itulah antrean dengan daftar dead-letter dan tombol putar ulang (replay). Membangunnya tidak sulit. Namun itu satu komponen lagi yang harus dijalankan, dipantau, dan bisa membangunkan Anda saat giliran on-call, padahal bot Anda tidak dibuat untuk itu. Panduan Vercel kami menunjukkan versi simpan-dulu-baru-jawab jika Anda ingin mengelolanya sendiri.

Jika Anda tidak ingin menjalankan antrean

Pasang relay di depan handler. Anda hanya mengganti URL webhook di sisi pengirim menjadi URL AnyHook Anda, tidak lebih.

  • Pengirim mendapat respons 200 dalam sekitar satu detik. Event disimpan sebelum respons 200 itu dikirim
  • Setelah itu handler Anda menerima event dengan batas waktu pengiriman 60 detik di paket Free, 120 detik di Pro, dan 5 menit di Scale, dalam batas jatah komputasi paket Anda. Panggilan model muat di dalamnya, jadi handler dapat bekerja langsung di dalam permintaan itu dan menjawab setelah selesai
  • Jika handler Anda gagal atau kehabisan waktu, AnyHook mencoba ulang dengan jadwalnya sendiri: 5 menit, 30 menit, 3 jam, 18 jam, lalu setiap hari, hingga 3 upaya di Free, 5 di Pro, dan 10 di Scale
  • Setiap event disimpan di log yang bisa dicari (3 hari di Free, lebih lama di paket berbayar), dan event mana pun dapat diputar ulang setelah Anda memperbaiki bug
  • AnyHook menjawab challenge url_verification dari Slack untuk Anda, jadi URL-nya berhasil disimpan pada percobaan pertama

Pengiriman bersifat setidaknya sekali (at-least-once). Handler yang sudah menyelesaikan tugasnya tetapi menjawab setelah batas waktu habis akan menerima event itu lagi, jadi buat handler Anda idempoten: lewati setiap event dengan header X-AnyHook-Delivery (ID event, sama di setiap percobaan ulang) yang sudah pernah Anda tangani. ID milik pengirim sendiri, seperti event_id dari Slack atau X-GitHub-Delivery dari GitHub, juga ikut diteruskan. Polanya ada di Event duplikat dan idempotensi.

Yang tidak bisa dilakukan relay adalah menjawab untuk Anda ketika pengirim membutuhkan body respons yang sebenarnya:

  • Slash command dan tombol interaktif Slack mengharapkan balasan Anda di dalam respons atau konfirmasi cepat. Jawab dari endpoint Anda sendiri dengan 200 kosong, lalu kirim jawaban sebenarnya ke response_url yang dikirim Slack, yang menerima hingga lima pesan dalam 30 menit
  • Interaksi Discord membutuhkan respons awal dalam 3 detik. Balas dengan deferred response (tipe 5), lalu edit dengan jawaban model saat jawaban itu tiba; token interaksi tetap berlaku selama 15 menit
  • Webhook suara dan pesan Twilio mengharapkan TwiML sebagai balasan. Jawab segera dengan TwiML, lalu kirim jawaban yang lambat lewat REST API

Relay dibuat untuk webhook event, jenis yang hanya butuh 2xx: Events API Slack, LINE, Shopify, GitHub, HubSpot, Stripe, dan sebagian besar lainnya.

Periksa waktu handler Anda

Anda tidak perlu menebak di sisi mana garis batas handler Anda berada. Pemeriksa timeout webhook mengirim permintaan uji ke endpoint Anda, mengukur waktunya, dan menempatkan hasilnya di samping batas waktu setiap pengirim. Alat ini berjalan dua kali, jadi permintaan pertama yang lambat setelah lama sepi akan terlihat apa adanya: cold start.

FAQ

Mengapa Slack bilang bot saya timeout, atau mengapa bot membalas dua kali? Slack menunggu 3 detik untuk respons 2xx. Jika handler Anda memanggil model sebelum menjawab, prosesnya lebih lama, Slack menghitungnya sebagai kegagalan dan mengirim event itu lagi, lalu kedua eksekusi mengirim balasan. Jawab dulu, kirim balasan sesudahnya dengan chat.postMessage, dan buang event yang sudah pernah Anda lihat.

Bisakah saya mengembalikan 200 saja lalu terus bekerja di Vercel? Bisa, dengan after() dari next/server (atau waitUntil), dan pekerjaan itu berjalan selama durasi maksimum route. Cara ini tidak persisten: jika pekerjaan gagal atau instance didaur ulang, event hilang dan pengirim tidak akan mengirimnya ulang. Cukup untuk notifikasi, salah untuk apa pun yang hilangnya akan disadari pengguna.

Berapa lama LINE menunggu respons webhook? Dua detik. LINE mencatat webhook yang tidak dijawab server bot Anda dalam 2 detik sebagai request_timeout di statistik error-nya, dan menyarankan agar pekerjaan dilakukan secara asinkron. Pengiriman ulang webhook nonaktif secara default; aktifkan di pengaturan Messaging API channel Anda.

Apakah memasang antrean di depan membuat balasan bot saya lebih lambat? Tidak. Relay meneruskan event begitu event itu tersimpan, jadi handler Anda mulai berjalan kira-kira pada saat yang sama seperti tanpa relay. Yang berubah adalah pengirim tidak lagi menahan koneksi selama model berpikir. Percobaan ulang hanya menambah waktu ketika sebuah pengiriman gagal, dan tanpa percobaan ulang, event itu akan hilang.

Bagaimana jika handler saya butuh lebih dari 60 detik? Di Free, AnyHook menunggu jawaban handler Anda selama 60 detik; Pro menunggu 120 detik dan Scale 5 menit, dalam batas jatah komputasi paket Anda. Untuk pekerjaan yang lebih lama dari itu, jawab 202 begitu job sudah masuk antrean, lalu selesaikan di sisi Anda. Relay menghitung 202 sebagai terkirim.

Apakah ini berlaku untuk bot Discord dan slash command Slack? Tidak lewat relay. Keduanya membutuhkan body respons tertentu dalam 3 detik, dan hanya kode Anda sendiri yang bisa memberikannya. Gunakan deferred response Discord dan response_url Slack seperti dijelaskan di atas. Events API Slack, yang hanya membutuhkan 2xx, bisa berjalan di balik relay.

Apakah handler saya bisa menerima event yang sama dua kali? Bisa, dengan atau tanpa relay: pengirim mencoba ulang, begitu pula AnyHook saat handler Anda gagal. Rancang setiap handler untuk pengiriman setidaknya sekali dan lewati ID yang sudah pernah Anda tangani.

Semua artikel14 Oktober 2026 · 9 mnt

Berhenti kehilangan webhook.

Ganti satu URL. Dapatkan percobaan ulang, log event, dan putar ulang sekali klik. Atau lihat dulu: daftar dan jalankan latihan gangguan 30 detik, lalu saksikan event tetap selamat saat server Anda mati. Tidak perlu server.