Webhook18 April 20268 mnt baca

Percobaan Ulang (Retry) Webhook Stripe: Panduan Production

Stripe mencoba ulang webhook yang gagal hingga 3 hari. Begini cara kerja jadwalnya, apa yang rusak saat endpoint Anda mati, dan cara merancang untuk itu.

JARAK ANTARUPAYA · EKSPONENSIALsekarang5 mnt30 mnt3 j18 jberhentistatus=failed · payload tetap ada di log · tetap dapat diputar ulang
Tiga hari percobaan ulang, lalu hening. Log Anda sendiri yang menentukan apakah keheningan itu berarti.

Saat server Anda mati dan Stripe mengirim payment_intent.succeeded, apa yang terjadi pada event itu? Apakah Stripe mencoba ulang (retry) selamanya? Berapa lama? Dan bagaimana Anda merekonsiliasi event yang terlewat?

Ini pertanyaan paling umum yang saya dengar dari developer yang mengintegrasikan Stripe, dan pertanyaan ini penting karena webhook yang gagal Anda proses adalah pendapatan yang tidak bisa ditindaklanjuti produk Anda. Seorang pelanggan baru saja membayar. Job pemenuhan pesanan Anda tidak pernah berjalan. Dashboard pelanggan itu tidak pernah diperbarui.

TL;DR

  • Stripe mencoba ulang selama 3 hari dengan backoff eksponensial, lalu berhenti untuk selamanya
  • Kembalikan 200 dengan cepat, kerjakan secara async, catat setiap event sebelum diproses, buat handler idempoten
  • Jika Anda tidak punya waktu untuk membangun semua ini, relay webhook memberi Anda percobaan ulang, log, dan putar ulang (replay) cukup dengan mengganti satu URL

Cara Stripe mencoba ulang

Stripe mengharapkan endpoint webhook Anda mengembalikan kode status 2xx dengan cepat. Stripe tidak memublikasikan angka pastinya, dan dokumentasinya hanya meminta Anda mengembalikan 2xx “sebelum logika kompleks apa pun yang dapat menyebabkan timeout.” Angka yang dijadikan patokan komunitas adalah 20 detik. Pengiriman yang mengalami timeout dihitung gagal, begitu juga respons 5xx dan kegagalan jaringan.

Saat gagal, Stripe mencoba ulang event itu dengan backoff eksponensial hingga 3 hari. Jarak antar-percobaan ulang makin lama makin renggang, dan jendela terakhirnya sekitar 72 jam sejak event asli.

Setelah 3 hari, Stripe menandai event itu gagal dan berhenti mencoba ulang. Stripe tidak membangunkan Anda. Stripe tidak mengirim email cadangan. Event itu begitu saja hilang dari antrean pengiriman Anda, meskipun Anda masih bisa melihatnya di Dashboard Stripe pada menu Developers → Events.

Kurva Stripe juga termasuk yang lembut. Platform merchant-of-record bisa mencoba ulang jauh lebih agresif: Paddle melakukan hingga 60 upaya dalam sekitar dua hari, dan handler yang sanggup mengikuti jadwal Stripe masih bisa tenggelam dalam jadwal Paddle.

Jika kegagalan berlanjut melewati itu, Stripe akhirnya menonaktifkan endpoint sepenuhnya dan mengirimi Anda email berisi tanggal penonaktifan. Memulihkannya adalah pekerjaan lain, dan event-nya masih bisa diambil dari Events API selama 30 hari: lihat Stripe Menonaktifkan Endpoint Webhook Anda untuk urutan backfill-nya.

Tiga mode kegagalan yang menyakitkan

Endpoint Anda lambat. Stripe menghitung respons yang melewati timeout-nya yang tidak dipublikasikan sebagai kegagalan. Jika handler webhook Anda secara sinkron menulis ke database yang lambat, menyinkronkan data ke Salesforce, dan mengirim email ke pelanggan, Anda bermain-main dengan timeout di setiap event.

Jendela deploy Anda. Server Anda restart saat deploy, melewatkan sekitar 10 event, lalu hidup kembali. Stripe mencoba ulang semua event itu, tetapi hanya selama 3 hari.

Bug yang terus mengembalikan 500. Anda merilis deploy yang melempar error pada jenis event tertentu. Stripe mencoba ulang, tetapi terus menemui error yang sama. Setelah 3 hari Stripe menyerah, dan Anda punya celah data yang senyap.

Dalam ketiga kasus, Stripe melakukan hal yang benar. Kegagalannya ada di sisi pipa Anda.

Empat hal yang dilakukan integrasi kelas production

1. Kembalikan 200 dengan cepat, kerjakan secara async

Jangan melakukan pekerjaan berat di handler webhook. Terima event-nya, tulis ke antrean atau log persisten, kembalikan 200, lalu proses secara asinkron. Cara ini menjauhkan Anda dari timeout dan memisahkan pengiriman dari logika bisnis.

export async function POST(req: Request) {
  const event = await verifyStripeSignature(req);
  await queue.publish(event);   // durable write
  return new Response(null, { status: 200 });
}
waitUntil · 200 ITU JANJI YANG TIDAK BISA ANDA TEPATIverifikasikembalikan 200waitUntil(work)hanya di memorididaur ulang → hilang, tidak pernah dikirim ulangSIMPAN DULU · 200 ITU BENARverifikasitulis persistenkembalikan 200prosesbisa dicoba ulang
Kembalikan 200 untuk sesuatu yang sudah Anda tulis secara persisten, dan jam milik provider tidak lagi membatasi logika bisnis Anda.

2. Catat setiap event sebelum diproses

Simpan raw payload dan header secara persisten sebelum Anda menyentuh logika bisnis. Jika logika Anda melempar error, Anda bisa memutar ulang dari log. Stripe berhenti mencoba ulang setelah 3 hari dan menyimpan event selama 30 hari, jadi setelah itu log Anda satu-satunya yang memungkinkan Anda memulihkannya.

3. Idempotensi di sisi Anda

Stripe mengirim event.id yang sama pada setiap percobaan ulang, jadi handler Anda harus idempoten: jika Anda sudah memproses evt_1H..., jangan memprosesnya lagi. Pola sederhananya:

INSERT INTO processed_events (stripe_event_id, processed_at)
VALUES ($1, NOW())
ON CONFLICT (stripe_event_id) DO NOTHING;

Jika insert mengembalikan 0 baris yang terpengaruh, Anda sudah pernah melihat event ini. Lewati.

Idempotensi lebih dalam daripada kelihatannya begitu percobaan ulang mulai tiba bersamaan, atau tidak berurutan. Anda Menerima Webhook yang Sama Dua Kali membahas batas transaksi, pola claim-then-commit untuk efek samping yang tidak bisa Anda batalkan, dan mengapa version guard, bukan deduplikasi, yang memperbaiki pengiriman yang tidak berurutan.

4. Peringatan saat terjadi kegagalan

Jika 5 event berturut-turut gagal, Anda ingin tahu dalam hitungan menit, bukan saat pelanggan mengirim email ke support. Lacak kegagalan berturut-turut dan pasang peringatan yang memanggil Anda.

Peran AnyHook

AnyHook berada di depan server Anda dan menyelesaikan masalah ini di lapisan transport. Anda mengarahkan Stripe ke in.anyhook.net/you/stripe, bukan ke origin Anda, lalu kami:

  • Mengembalikan 200 ke Stripe dalam sekitar satu detik, jadi jendela respons tidak pernah menjadi masalah
  • Mencatat setiap event lengkap dengan header dan body, dienkripsi saat disimpan (at rest)
  • Mencoba ulang pengiriman ke server Anda pada 5 menit, 30 menit, 3 jam, 18 jam, lalu setiap hari: 3 upaya di Free, 5 di Pro, 10 di Scale. Jadwal Stripe sendiri berjalan hingga tiga hari, jadi di Free dan Pro jaring pengaman jangka panjangnya adalah log yang tersimpan dan putar ulang, bukan jumlah percobaan ulang
  • Memberi Anda putar ulang sekali klik. Jika server Anda mati selama satu jam, halaman App (satu URL masuk untuk satu sumber webhook) memutar ulang event yang gagal selama jam itu dengan satu tekan (hingga 100 sekaligus), dan memutar ulang event yang gagal tidak mengurangi kuota bulanan Anda, meskipun ada batas harian dan memakai jatah komputasi (compute allowance) Anda
  • Memberi peringatan saat kegagalan beruntun. Kami mengirimi Anda email pada 1 dan 5 kegagalan berturut-turut. Pada 20, kami menjeda pengiriman ke tujuan itu (10 menit pada awalnya, lebih lama jika terpicu berulang) dan mengirim email lagi; event yang tiba selama jeda dicatat agar dapat diputar ulang, bukan dikirim

Tanpa SDK, cukup ganti satu URL. Pengiriman pertama tetap lolos pemeriksaan Stripe yang sudah Anda punya. Percobaan ulang dan putar ulang membawa stempel waktu (timestamp) asli dari Stripe, yang ditolak SDK Stripe setelah lima menit, jadi periksa header AnyHook-Signature untuk menerimanya.

Untuk melihatnya sebelum mendaftar apa pun, satu perintah mengembalikan URL masuk (inbound URL) yang aktif, tanpa akun:

curl -X POST https://anyhook.net/api/v1/quickstart

Responsnya berisi inbound_url untuk Anda berikan ke provider, api_key untuk membaca log event lewat API, dan claim_url. Endpoint ini sementara: endpoint dan event-nya dihapus setelah 7 hari kecuali Anda membuka URL klaim itu lalu masuk, dan URL-nya tetap sama.

Kesimpulan

Jika hari ini Anda kehilangan event Stripe, solusinya biasanya bukan percobaan ulang yang lebih banyak. Solusinya adalah persistensi sebelum pemrosesan, agar Anda selalu punya sesuatu untuk diputar ulang.

Semua artikel18 April 2026 · 8 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.