Bot Anda berjalan sempurna saat pengembangan. Di production, bot itu hanya sesekali menjawab. Pengguna melaporkan bahwa bot “biasanya” membalas, atau membalas tiga kali untuk satu pesan, atau diam sepanjang sore lalu kembali dengan sendirinya.
Ketiganya bug yang sama. Slack memberi Anda tiga detik, handler Anda butuh empat, dan semua yang terjadi sesudahnya adalah kebijakan percobaan ulang (retry) Slack yang bertemu dengan handler yang tidak ditulis untuk mengantisipasinya.
TL;DR
- “Aplikasi Anda harus merespons permintaan event dengan HTTP 2xx dalam tiga detik”
- Slack mencoba ulang tiga kali: hampir segera, setelah 1 menit, lalu setelah 5 menit.
- Handler yang tidak idempoten mengubah percobaan ulang itu menjadi pesan duplikat yang bisa dilihat orang.
x-slack-no-retry: 1pada respons non-200 menghentikan percobaan ulang untuk event yang Anda tahu tidak bisa Anda tangani.- Jika lebih dari 95% pengiriman gagal dalam rentang 60 menit, Slack menonaktifkan langganan event Anda dan mengirim email ke pemilik aplikasi.
Tiga detik itu tidak cukup untuk apa pun
Lihat apa yang dilakukan handler yang realistis antara menerima message.channels dan siap membalas. Pertama, memverifikasi tanda tangan (signature). Lalu mencari tahu workspace mana yang mengirimnya. Kemudian membaca sejumlah state dari database Anda. Terakhir, jika bot itu melakukan sesuatu yang menarik di tahun 2026, memanggil model.
Tiga langkah pertama mungkin 200ms di hari yang baik. Langkah keempat 30 detik.
Ini ketidakcocokan skala waktu yang sama dengan yang muncul pada webhook pembayaran, bedanya di sini batas waktu responsnya tiga detik yang dipublikasikan (batas waktu setiap pengirim ada di referensi provider webhook kami) dan akibatnya terlihat, bukan diam-diam. Tidak ada yang menyaksikan payment_intent.succeeded gagal. Semua orang menyaksikan bot yang tidak menjawab.
Bentuk umum perbaikannya, untuk setiap pengirim dan setiap platform hosting yang mungkin Anda pakai, ada di Handler Webhook Anda Memanggil LLM, dan pemeriksa timeout akan mengukur waktu handler Anda terhadap tiga detik milik Slack. Slack juga punya jalan keluar khusus yang membuatnya jauh lebih ringan dibanding kebanyakan pengirim lain.
Trik dari Slack yang tidak ada di Stripe
Slack memisahkan konfirmasi penerimaan event dari respons ke pengguna. Anda mengembalikan 200 segera, lalu mengirim balasan yang sebenarnya belakangan dengan chat.postMessage, atau dengan memanggil response_url yang dibawa payload interaktif.
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);
// URL verification handshake, only during setup
if (body.type === "url_verification") {
return Response.json({ challenge: body.challenge });
}
await queue.publish(body); // durable, ~10ms
return new Response(null, { status: 200 }); // well inside 3s
}
Worker kemudian memakan waktu selama yang dibutuhkan dan memanggil chat.postMessage saat sudah ada yang ingin disampaikan. Dari sisi pengguna, ini terlihat seperti bot yang sedang berpikir, hal yang wajar bagi sebuah bot. Dari sisi Slack, event-nya sudah dikonfirmasi dalam 40 milidetik.
Jika pekerjaannya akan memakan lebih dari satu atau dua detik, kirim placeholder segera lalu perbarui. Pesan yang bertuliskan “sedang dikerjakan” lalu mengedit dirinya sendiri jauh lebih baik daripada tiga detik tanpa apa-apa yang diakhiri timeout.
Apa yang sebenarnya dilakukan percobaan ulang terhadap Anda
Jadwal Slack singkat dan spesifik: “Percobaan ulang pertama akan dikirim hampir segera. Percobaan ulang kedua akan dilakukan setelah 1 menit. Percobaan ulang ketiga dan terakhir akan dikirim setelah 5 menit.”
Sekarang ambil contoh handler yang mengerjakan tugasnya lebih dulu dan baru mengembalikan 200 di akhir, dengan waktu empat detik. Slack mengalami timeout pada detik ketiga. Handler Anda selesai pada detik keempat dan mengirim pesan. Slack mencoba ulang hampir segera, handler Anda berjalan lagi, mengirim pesan lagi. Semenit kemudian, pesan ketiga. Lima menit kemudian, pesan keempat.
Pengguna mengajukan satu pertanyaan dan mendapat empat jawaban, tersebar selama enam menit, yang lebih mirip bot rusak daripada bot lambat.
Perbaikannya sama dengan pola unique constraint di tempat lain, dengan event_id dari Slack sebagai key-nya:
const { rowCount } = await db.query(
`INSERT INTO processed_events (event_id, provider)
VALUES ($1, 'slack') ON CONFLICT (event_id) DO NOTHING`,
[body.event_id],
);
if (rowCount === 0) return new Response(null, { status: 200 });
Perhatikan 200 di jalur duplikat. Mengembalikan error di sana menyuruh Slack mencoba ulang sesuatu yang baru saja berhasil Anda abaikan. Kasus konkurensi, ketika dua percobaan ulang mendarat di dua instance pada milidetik yang sama, dibahas lebih lanjut di Anda Menerima Webhook yang Sama Dua Kali.
Header yang tidak dipakai siapa pun
Sebagian event tidak akan pernah berhasil. Workspace sudah menghapus aplikasi Anda, channel sudah diarsipkan, payload merujuk ke objek yang sudah Anda hapus. Mencoba ulang event seperti itu membuang tiga pengiriman dan, yang lebih penting, mendorong tingkat kegagalan Anda ke ambang yang membuat langganan Anda dimatikan.
Slack memungkinkan Anda menolak percobaan ulang per respons: “Sertakan header HTTP dan nilai ini sebagai bagian dari respons non-200 OK Anda: x-slack-no-retry: 1”.
if (!workspace) {
return new Response("unknown workspace", {
status: 410,
headers: { "x-slack-no-retry": "1" },
});
}
Gunakan hanya untuk kondisi permanen. Timeout database bukan kondisi permanen, dan Anda justru ingin percobaan ulang itu terjadi.
Ambang kegagalan
Inilah bagian yang mengubah sore yang lambat menjadi gangguan.
“Jika aplikasi Anda mengalami kombinasi apa pun dari kondisi kegagalan ini pada lebih dari 95% upaya pengiriman dalam 60 menit, langganan event aplikasi Anda akan dinonaktifkan sementara.”
Saat ini terjadi, Slack mengirim email ke pembuat dan pemilik aplikasi. Itu hanya berguna jika alamat tersebut milik seseorang yang masih bekerja di perusahaan dan masih membacanya. Ini jebakan yang sama dengan Emergency developer email (email developer untuk keadaan darurat) di Shopify, yang dijelaskan di Langganan Webhook Shopify Anda Dihapus: satu-satunya peringatan yang Anda dapat dikirim ke alamat yang dipilih saat penyiapan dan tidak pernah ditinjau lagi.
Yang perlu benar-benar dipahami: 95% dalam satu jam bukan penurunan perlahan yang akan Anda sadari. Deploy yang merusak verifikasi tanda tangan seketika mencapai 100%, dan satu jam kemudian langganannya mati. Bot tidak menampilkan error. Bot hanya berhenti menerima apa pun, yang dari dalam terlihat persis seperti channel yang sedang sepi.
Men-debug bot yang kadang menjawab
Periksa yang berikut ini, sesuai urutan.
Waktu respons, bukan tingkat error. Handler dengan waktu 2,8 detik lolos di setiap tes yang Anda tulis dan gagal setiap kali database sedikit sibuk. Ukur p99, bukan rata-rata, karena yang membuat Slack timeout adalah p99.
Cold start, jika Anda memakai serverless. Dua detik cold start dari batas waktu tiga detik menyisakan satu detik untuk semua hal lain. Menerima Webhook di Vercel tanpa Ada yang Hilang membahas cara menjaga graf import route tetap kecil, yang mengembalikan lebih banyak waktu daripada apa pun yang bisa Anda lakukan di sini.
Apakah duplikatnya berasal dari percobaan ulang Slack. Pesan duplikat enam menit setelah pesan pertama adalah percobaan ulang ketiga dari Slack, bukan bug di logika pengiriman pesan Anda. Waktunya memberi tahu mana yang terjadi.
Apakah langganannya masih aktif. Jika semuanya mendadak sepi dan tidak ada yang berubah di log Anda, periksa halaman Event Subscriptions aplikasi sebelum men-debug hal lain.
Peran AnyHook
Tiga detik adalah batasan di lapisan transport, dan transport itulah yang digantikan relay.
Arahkan Request URL Slack ke in.anyhook.net/you/slack. AnyHook memverifikasi dan menyimpan di edge (Cloudflare Worker AnyHook di in.anyhook.net) lalu mengembalikan 200 dalam sekitar satu detik, entah handler Anda sedang hangat, dingin, atau terbakar. Edge worker yang hanya melakukan dua hal itu sudah memenuhi batas tiga detik Slack, jadi rangkaian percobaan ulang hampir tidak pernah dimulai dan ambang 95% tidak pernah terdekati.
- Handler Anda mendapat 60 sampai 300 detik, bukan tiga, tergantung paket dan dalam batas jatah komputasi (compute allowance) paket tersebut.
- Setiap event disimpan secara persisten sebelum dikirim, jadi jeda saat deploy cukup diatasi dengan putar ulang (replay), bukan dengan serentetan pertanyaan dari pengguna Anda.
- Pengiriman ke endpoint Anda dicoba ulang dengan backoff eksponensial dalam rentang yang jauh lebih panjang daripada rangkaian enam menit milik Slack.
- Peringatan kegagalan dikirim pada 1, 5, dan 20 kegagalan berturut-turut, jauh sebelum apa pun mencapai ambang yang membuat Slack bertindak.
AnyHook menjawab handshake url_verification di edge, setelah memeriksa tanda tangan Slack jika Anda sudah memberinya signing secret, dan tidak meneruskan apa pun ke handler Anda.
Kesimpulan
Tiga detik dari Slack adalah batas waktu respons paling ketat yang diterapkan pengirim besar mana pun, dan salah satu dari sedikit batas yang kegagalannya terlihat oleh pengguna produk Anda. Konfirmasi dulu, balas kemudian, selalu. Lakukan deduplikasi berdasarkan event_id sebelum Anda melakukan apa pun yang akan dilihat manusia dua kali. Kirim x-slack-no-retry: 1 saat Anda tahu event-nya sudah mati. Dan periksa alamat mana yang tercantum sebagai pemilik aplikasi, karena ke sanalah pemberitahuan dikirim saat langganan Anda dimatikan.