Ana Sayfa / Blog / Webhook’ların %90’ı yanlış kurgulanıyor, doğru retry/ack mantığı

Webhook’ların %90’ı yanlış kurgulanıyor, doğru retry/ack mantığı

Ödeme sağlayıcıdan, e-posta servisten, CRM'den webhook alıyorsunuz. Handling mantığınızı 5 dakikalık bir downtime'da test edin, büyük ihtimalle kırılıyor.

Webhook demek kolay: “bir event olduğunda size HTTP POST göndereceğim.” Ama production’da webhook handling’ini doğru yapmak sandığınızdan daha zor, ve çoğu ekip bunu yanlış yapıyor.

Standart yanlış implementasyon

Tipik webhook endpoint’i şöyle görünür: JSON payload’ı parse ediyorsun, event type’a göre işlem yapıyorsun, 200 dönüyorsun. Bu kod ilk hafta çalışır. Sonra şunları kaybedersiniz:

  • Duplicate event’ler (provider retry yaptığında)
  • Out-of-order event’ler (payment.succeeded, sonra payment.pending)
  • Signature doğrulama yok, sahte webhook kabul ediyorsun
  • Handler içinde slow operation (email send, DB write) varsa timeout
  • Handler hata atarsa ne olur, provider yeniden dener mi, denediğinde hazır mısın?

Her birini tek tek açalım.

1. Signature doğrulama şart

Eğer webhook URL’ni biri öğrenirse (ki log’larda, error report’larda sızabilir), sahte payload gönderebilir. Her major provider signed webhook sunar.

Stripe için HMAC-SHA256 ile signature header’ı doğrulanıyor. Secret ile payload’ı hash’leyip header’daki hash ile karşılaştırıyorsun. Timing-safe karşılaştırma kullan (hash_equals), normal string karşılaştırma timing attack’e açık.

2. Idempotency, aynı event birden fazla gelebilir

Webhook provider’lar “at least once” delivery yapar. Yani aynı event tekrar tekrar gelebilir:

  • Senin endpoint’in timeout verdi, provider retry etti
  • Provider’ın internal sistemi duplicate gönderiyor
  • Sen response gönderdin ama network kesildi

Her event’in unique bir ID’si vardır (Stripe’ta event.id, bir UUID). Bu ID’yi gördükten sonra işlenmiş olarak işaretle. Processed event store’u Redis olabilir (7-30 gün TTL), veya DB tablosu (event_id UNIQUE index ile).

3. Slow handler, async queue’ya at

Webhook handler 5 saniyede cevap vermeli. Bunu aşarsa provider timeout verir, retry eder, sen de duplicate işlem görürsün (idempotency olmadan felaket).

Solution: handler’da sadece event’i validate et ve bir queue’ya at. Actual work background worker’da olsun. Handler <200ms'de 200 dönsün, arkada Laravel Horizon / RabbitMQ / AWS SQS işi işlesin.

4. Out-of-order event’ler

Event’ler her zaman doğru sırada gelmez. Stripe örneği:

  1. Kullanıcı abonelik iptal ediyor → subscription.updated
  2. Sistem iptali işliyor → subscription.deleted

Ama networking gereği subscription.deleted önce gelebilir. Sen “silindi” diye işaretlersin, sonra subscription.updated gelir ve aktif gibi işaretlersin. Hatalı state.

Fix: event’lerde timestamp vardır. State değişikliği yapmadan önce son işlediğin timestamp’tan daha yeni mi diye kontrol et. Subscription modelinin updated_at’i event’in created’inden sonra ise, bu event eski bir event, skip et.

5. Failed handler retry stratejisi

Background job’un iş sırasında hata aldı, mesela DB down, external API bozuk. Ne yapacaksın?

  • Panic retry: anında yeniden dene → sistemi daha da yorar, muhtemelen yine fail olur
  • Exponential backoff: 1s, 2s, 4s, 8s, 16s…, best practice
  • Dead letter queue: N kez fail olduysa bir “hatalar” queue’sine koy, manual review

Ayrıca: log’la! Her failed job’un sebebi bir yerlere yazılmalı. Datadog, Sentry, custom dashboard, önemli olan görünebilir olması.

6. Webhook provider’ının dashboard’unu takip et

Stripe, Paddle, SendGrid gibi provider’lar webhook delivery log’u tutuyor. Failed deliveries varsa dashboard’ta gözüküyor. Haftada bir bakın:

  • Response time ortalamanız ne?
  • Failed delivery oranı?
  • Hangi event tipi en çok fail oluyor?

Bu dashboard sizin production health monitoring’iniz.

7. Test etmek için ngrok + provider’s webhook tester

Local development’ta webhook test etmek için ngrok kullanın. Provider genelde bir “webhook tester” sunuyor, Stripe CLI en iyilerinden. stripe listen --forward-to localhost:8080/webhooks ile local’e forward, stripe trigger payment_intent.succeeded ile test event gönderebiliyorsun.

Bu bana gerçekçi event payload’ları gönderiyor, handler’ı gerçek veriyle test edebiliyorum.

Sonuç

Webhook handling production’da ciddi bir iş. Eğer bu 7 konuyu şu anda kod tabanında kontrol edersen, muhtemelen en az 2-3 tanesi düzeltilmesi gereken durumda. Özellikle idempotency ve signature, bu ikisi %80 etki sağlıyor.

Webhook’unuzun sağlığı gelirinizin sağlığıdır. Ciddiye alın.

Bu konuda bir projeniz mi var?

Kısa bir özet bırakın, 24 saat içinde size dönüş yapayım.

İletişime Geç