İçeriğe atla
ÖzelliklerFiyatlandırmaİş OrtaklığıBlogYardımHakkımızdaİletişim
BaşlaGiriş Yap
Bloga Dön
guides2026-08-297 dk okuma

Webhook Idempotency: Stripe Aynı Komisyon Event'ini 3 Kez Gönderdiğinde Ne Olur?

Stripe at-least-once delivery garantisi ile %0.3 duplicate oranı production gerçeği. thMenu'nun stripe_webhook_events tablosu ve INSERT claim pateni nasıl çözüyor — race condition dahil.

th

thMenu Team

thmenu.com

Production'da Stripe webhook'larını 14 ay boyunca izledik: 100.000 event'ten yaklaşık 312'si aynı event_id ile 2-3 kez geldi. Bu, Stripe'ın "at-least-once delivery" garantisinin somut karşılığı — ve idempotency olmadan komisyon tablosunda üçer kayıt demek.

Neden Aynı Event 3 Kez Gelir?

Stripe webhook handler'ınızdan 2xx response almazsa retry yapar. TCP timeout, lambda cold start, NS lookup geçici hatası, hatta sizin başarılı işlediğiniz ama response gönderirken bağlantının düşmesi — hepsi retry tetikler. Stripe 3 gün boyunca exponential backoff ile dener: 5sn, 5dk, 30dk, 2sa, 5sa.

thMenu'da en sık görülen üç senaryo: (1) Cloudflare Workers cold-start 10sn üzeri sürdü, Stripe timeout sayıp retry yaptı ama işlem aslında commit oldu. (2) D1 batch transaction commit edildi ama response write esnasında connection drop. (3) Stripe dashboard'dan manuel "resend" tıklandı.

INSERT-Claim Pattern

thMenu'nun çözümü stripe_webhook_events tablosu — Supabase Postgres'te event_id PRIMARY KEY ve type kolonu. Handler'ın ilk işi INSERT INTO stripe_webhook_events (event_id, type) VALUES (?, ?). Postgres unique constraint 23505 error code döndürürse duplicate — 200 OK no-op ile cevaplayıp Stripe'ı sustur.

Önemli: claim adımı business logic'ten önce gelir. Önce işlem yapıp sonra "kaydettim mi" diye bakarsanız race condition var: iki worker aynı anda webhook alır, ikisi de "henüz kaydedilmemiş" der, ikisi de işlem yapar. INSERT-first atomic.

Race Condition: Out-of-Order Events

Stripe event'leri sıralı göndermez. Üretimde gerçek bir vaka: customer.subscription.updated event'i checkout.session.completed'den 800ms önce geldi. Handler subscription'ı güncellemek istedi ama user_profiles'da henüz customer_id mapping yoktu — UPDATE 0 row affected döndü.

thMenu fix'i: 0 row affected ise idempotency claim'i geri al (DELETE FROM stripe_webhook_events WHERE event_id=?) ve 503 dön. Stripe 5 saniye sonra retry yapacak — bu sefer checkout.session.completed önce işlenmiş olacak. Tabloya "tried but not committed" işareti bırakmak yerine temizlemek kritik, yoksa retry duplicate sanıp atlanır.

FAQ

Signature verify'ı claim'den önce mi sonra mı yapmalıyım? Önce. Verify ucuz (HMAC-SHA256), DB yazımı pahalı. Geçersiz signature'lı event tabloyu kirletmesin.

Idempotency tablosunu ne zaman temizlemeliyim? Stripe 30 gün retry'ı garanti ediyor; biz 90 gün tutuyoruz audit için. Cron pruner günlük 04:00 UTC'de çalışır.

subscription.updated 3 saat sonra hâlâ gelirse 503 retry sonsuza mı sürer? Hayır. Stripe maksimum 3 gün retry yapar. Bu süre içinde checkout.session.completed işlenmezse manuel intervention gerekir — alarm kur.

Faydalı buldunuz mu? Paylaşın.