Subjek email-nya kira-kira berbunyi “Your webhook endpoint is failing”, dan jika Anda mengabaikan email pertama, email susulannya mengabarkan bahwa endpoint sudah dinonaktifkan. Sekarang tidak ada yang masuk. Langganan tidak teraktivasi, invoice tidak terekonsiliasi, dan Anda tidak tahu sejak kapan kerusakannya dimulai.
Kabar baiknya: Stripe menyimpan salinan setiap event selama 30 hari, entah pengirimannya berhasil atau tidak. Hampir semuanya bisa dipulihkan jika Anda bergerak di dalam rentang itu. Kabar buruknya: kebanyakan orang mengaktifkan kembali endpoint, melihat centang hijau pada trafik baru, lalu tidak pernah mem-backfill celahnya. Celah itulah yang merugikan secara finansial.
TL;DR
- Aktifkan kembali endpoint lebih dulu, tetapi itu hanya melanjutkan event baru. Tidak ada yang diputar ulang (replay).
- Lakukan backfill dengan
GET /v1/events?delivery_success=false, yang menjangkau 30 hari ke belakang. - Pastikan handler idempoten sebelum backfill, atau Anda akan memenuhi pesanan dua kali.
- Akar masalahnya hampir selalu handler yang lambat, bukan handler yang rusak.
- Simpan payload sebelum memprosesnya, dan ini tidak lagi menjadi keadaan darurat.
Mengapa Stripe sampai menonaktifkan endpoint
Dalam live mode, Stripe mencoba ulang pengiriman yang gagal hingga tiga hari dengan backoff eksponensial. Itu rentang yang longgar, dan rentang itu ada agar deploy atau gangguan singkat tidak merugikan Anda sama sekali. Percobaan Ulang (Retry) Webhook Stripe: Panduan Production membahas jadwal itu dan cara merancang sistem untuknya sebelum Anda sampai pada keadaan yang dibahas tulisan ini.
Namun, tiga hari bukanlah standar, dan menganggapnya standar akan merugikan Anda di integrasi berikutnya. Paddle mengirim event gagal yang sama hingga enam puluh kali dalam sekitar dua hari. Paddle Mencoba 60 Kali dalam Dua Hari. Stripe Menjarangkannya. menyandingkan kedua jadwal itu, dan ini paling penting jika Anda menagih lewat keduanya.
Endpoint yang gagal terus-menerus adalah sinyal yang berbeda. Alamatnya sudah tidak berfungsi, dan terus menggempurnya hanya membuang kapasitas pengiriman Stripe serta mengubur log event Anda dalam noise. Jadi Stripe memperingatkan Anda lewat email, memberi tahu tanggal endpoint akan dimatikan jika tidak ada perubahan, lalu akhirnya mematikannya.
Yang perlu benar-benar dipahami: penonaktifan adalah notifikasi kedua, bukan yang pertama. Saat itu terjadi, kegagalannya sudah terlihat di Dashboard selama berhari-hari. Jika email peringatan itu masuk ke kotak masuk founder yang tidak pernah dibaca siapa pun, atau ke alias yang bounce, itulah bug yang sebenarnya.
Langkah 1: Cari tahu apa yang rusak sebelum mengaktifkan kembali
Tahan dorongan untuk segera mengeklik Enable (aktifkan). Jika penyebabnya masih ada, Anda akan menghabiskan rentang percobaan ulang untuk kedua kalinya.
Mencari penyebab itu adalah pekerjaan tersendiri, dan makin lama dibiarkan makin sulit, karena pengiriman yang gagal kedaluwarsa dari dashboard selagi Anda masih memeriksanya. Men-debug Webhook yang Gagal di Production tanpa Kehilangan Akhir Pekan adalah urutan yang layak diikuti.
Di Dashboard, masuk ke Developers → Webhooks (atau tab Webhook di Workbench), buka endpoint-nya, lalu lihat pengiriman terbaru. Stripe mencatat respons yang diterimanya untuk setiap upaya. Cari tahu mana di antara berikut ini yang Anda alami:
| Yang Anda lihat | Artinya |
|---|---|
Timed out | Handler Anda terlalu lambat. Sejauh ini yang paling umum |
500 / 502 | Kode Anda melempar error, atau platform di depannya yang melakukannya |
503 | Aplikasi Anda mati, atau di-scale ke nol dan tidak pernah bangun lagi |
401 / 403 | Sesuatu di depan aplikasi Anda memblokir Stripe. WAF, rule Cloudflare, middleware auth |
404 | Route-nya pindah saat refactor dan tidak ada yang memperbarui URL endpoint |
Kasus 401/403 sering menjebak orang setelah menambahkan proteksi bot atau lapisan auth baru, karena permintaan webhook tidak membawa cookie sesi dan diperlakukan sebagai trafik anonim. Kasus 404 biasanya muncul setelah migrasi framework.
Perbaiki itu dulu. Baru aktifkan.
Langkah 2: Aktifkan kembali, dan pahami apa yang tidak dilakukannya
Mengaktifkan kembali melanjutkan pengiriman event yang dibuat sejak saat itu. Event yang sudah ditinggalkan tidak dikirim lagi secara otomatis. Ada satu pengecualian sempit, yang didokumentasikan Stripe: jika Anda menonaktifkan lalu mengaktifkan kembali sebelum percobaan ulang Stripe berikutnya, percobaan ulang yang sedang berjalan tetap sampai. Jika endpoint sudah nonaktif selama seminggu, tidak ada yang akan kembali dengan sendirinya.
Jadi, posisi Anda sekarang: event baru mengalir, celah historis belum tersentuh.
Langkah 3: Backfill dari Events API
Inilah bagian yang sering dilewati. List Events API milik Stripe menyediakan setiap event dari 30 hari terakhir, dan menerima filter delivery_success:
curl -G https://api.stripe.com/v1/events \
-u "$STRIPE_SECRET_KEY:" \
-d delivery_success=false \
-d "types[]=payment_intent.succeeded" \
-d "types[]=customer.subscription.created" \
-d "types[]=invoice.paid" \
-d limit=100
Beberapa hal yang penting di sini:
Filter berdasarkan jenis, jangan ambil semuanya. Akun yang sibuk menghasilkan banyak jenis event yang tidak punya handler di sisi Anda. Ambil yang benar-benar Anda proses.
Gunakan ending_before dengan auto-pagination. Berikan ID event dari sesaat sebelum gangguan dimulai, dan auto-pagination akan mengembalikan hasil dalam urutan kronologis. Ini penting: customer.subscription.created harus diproses sebelum customer.subscription.updated, sedangkan urutan default-nya dimulai dari yang terbaru.
const missed = stripe.events.list({
delivery_success: false,
ending_before: "evt_lastKnownGood",
types: ["payment_intent.succeeded", "invoice.paid"],
limit: 100,
});
for await (const event of missed) {
await handleEvent(event); // must be idempotent (see below)
}
Batas 30 hari itu mutlak. Event yang lebih lama dari itu sudah hilang dari API. Jika endpoint Anda dinonaktifkan lima minggu lalu, minggu pertamanya tidak bisa dipulihkan lewat Stripe, dan Anda terpaksa merekonsiliasi dari objek charges dan subscriptions, yang berarti sore yang jauh lebih berat.
Langkah 4: Pastikan idempoten, atau jangan jalankan backfill
Backfill Anda bisa mencakup event yang sudah diselesaikan handler setelah Stripe mencatat timeout, dan Stripe mungkin masih mencoba ulang sebagian dari yang kini Anda proses secara manual. Jika handleEvent tidak idempoten, Anda akan mengirim dua kuitansi, memberikan dua license key, atau mengirim dua pesanan.
Versi murahnya, dan ini benar-benar cukup:
INSERT INTO processed_events (stripe_event_id, processed_at)
VALUES ($1, NOW())
ON CONFLICT (stripe_event_id) DO NOTHING;
Nol baris terpengaruh berarti Anda sudah pernah melihat event ini dan harus berhenti. Jika memungkinkan, lakukan insert ini dalam transaksi yang sama dengan efek sampingnya. Jika efek sampingnya berupa panggilan API eksternal yang tidak bisa ikut dalam transaksi Anda, tandai baris itu processing lebih dulu, lalu processed sesudahnya, sehingga crash di tengah proses terlihat, alih-alih dicoba ulang diam-diam selamanya.
Kami membahas versi yang lebih luas dari masalah ini, termasuk pengiriman yang tidak berurutan dan mengapa setidaknya sekali (at-least-once) adalah pilihan desain yang disengaja, di Anda Menerima Webhook yang Sama Dua Kali.
Langkah 5: Perbaiki penyebab sebenarnya
Dalam pengalaman kami, penonaktifan hampir tidak pernah disebabkan handler yang salah. Penyebabnya handler yang lambat. Polanya seperti ini:
// The shape that eventually gets your endpoint disabled
export async function POST(req: Request) {
const event = await verifyStripeSignature(req);
await db.insertOrder(event); // 40ms
await salesforce.sync(event); // 900ms, sometimes 8s
await resend.sendReceipt(event); // 300ms, sometimes times out
await slack.notify(event); // 200ms
return Response.json({ ok: true });
}
Pada hari Selasa yang sepi, itu 1,5 detik dan tidak ada yang sadar. Saat Salesforce sedang bermasalah di suatu sore, handler ini menggantung, setiap pengiriman mengalami timeout sekaligus, dan Anda tinggal beberapa hari lagi dari endpoint yang dinonaktifkan. Dependensi yang gagal itu bahkan bukan milik Anda.
Perbaikannya struktural, bukan timeout yang lebih panjang:
export async function POST(req: Request) {
const event = await verifyStripeSignature(req);
await queue.publish(event); // durable write, ~10ms
return new Response(null, { status: 200 });
}
Terima, simpan, konfirmasi. Jalankan sinkronisasi Salesforce di worker yang bisa mencoba ulang dengan jadwalnya sendiri tanpa Stripe menghitung waktu. Kini pihak ketiga yang lambat hanyalah job yang lambat, bukan kegagalan pengiriman.
Separuh lainnya adalah peringatan. Anda perlu tahu pada kegagalan kelima berturut-turut, bukan beberapa hari kemudian saat Stripe mengirim email berisi tanggal penonaktifan. Jika satu-satunya pemantauan yang Anda punya adalah email peringatan dari Stripe, pastikan benar-benar email itu sampai ke tempat yang dibaca manusia.
Peran AnyHook
AnyHook berada di antara Stripe dan server Anda, sehingga origin yang lambat tidak bisa berujung pada endpoint yang dinonaktifkan. Stripe berkomunikasi dengan in.anyhook.net/you/stripe, bukan dengan aplikasi Anda, dan kami mengembalikan 200 dalam sekitar satu detik, apa pun yang sedang dilakukan server Anda.
- Stripe tidak pernah melihat timeout, jadi jalur penonaktifan tidak pernah tersentuh.
- Setiap event disimpan secara persisten lengkap dengan header dan body-nya, dengan enkripsi saat disimpan (at rest), sebelum upaya pengiriman apa pun dilakukan.
- Putar ulang tinggal satu tombol. Jika server Anda mati dari pukul 14.00 sampai 16.00, putar ulang rentang waktu itu. Putar ulang diambil dari log Anda, bukan dari log Stripe, dan event tetap dapat diputar ulang selama 3 hari di Free, 30 di Pro, 90 di Scale. Memutar ulang event yang gagal tidak mengurangi kuota bulanan Anda, meskipun ada batas harian dan memakai jatah komputasi (compute allowance) Anda.
- Peringatan kegagalan beruntun dikirim pada 1, 5, dan 20 kegagalan berturut-turut, dengan jeda otomatis pada kegagalan ke-20 agar deploy yang rusak tidak berubah menjadi banjir percobaan ulang.
Bedanya dalam praktik: dengan relay di depan, playbook pemulihan di atas menyusut menjadi memilih rentang waktu lalu mengeklik tombol Replay (putar ulang). Lihat Cara Memutar Ulang Webhook Saat Server Anda Mati.
Kesimpulan
Mengaktifkan kembali endpoint hanyalah 10% dari pemulihan. Sementara itu, 90% sisanya adalah mem-backfill celah sebelum rentang 30 hari itu berakhir, dan memastikan handler cukup cepat sehingga Stripe tidak pernah punya alasan untuk menonaktifkannya lagi. Jika handler webhook Anda memanggil API pihak ketiga secara inline, Anda sudah berada di jalur itu. Anda hanya belum mengalami sore yang buruk itu.