# Ödeme webhook'u aynı bildirimi tekrar gönderiyor; idempotency'i nasıl kurarım?

> HMAC'i ham gövdede constant-time doğrulayın, hızlı 200 dönüp işi kuyruğa alın ve tekilliği event_id üzerindeki UNIQUE constraint'e yaptırın.

- Soruldu: 2026-05-11
- Yanıtlandı: 2026-05-14
- Soran: Sıla
- Etiketler: mimari, guvenlik, api
- Kaynak: https://www.muhammetsafak.com.tr/sor-bakalim/webhook-mukerrer-bildirim-idempotency-ve-hmac/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**Soru:** Üçüncü parti bir ödeme sağlayıcısından webhook alıyorum. Sağlayıcı ağ kesintileri nedeniyle aynı başarılı ödeme bildirimini birden çok kez (retries) gönderebiliyor.

Sistemim mükerrer işlem (double-spending) yapmasın diye webhook endpoint'inde nasıl bir mimari kurmalıyım? İstekleri imzalama (HMAC) ve DB seviyesinde idempotency key takibi nasıl olmalı?


Kısa cevap: Bunlar **iki ayrı mesele** — kimlik doğrulama (HMAC) ve mükerrerliği önleme (idempotency). İkisini karıştırma, ikisini de ayrı çöz.

Tek cümleyle: imza "bu gerçekten sağlayıcı mı"yı, idempotency "bu işi daha önce yaptım mı"yı sorar. Mimarini bu ayrım üzerine kur:

1. **İmzayı RAW body üzerinde doğrula.** Sağlayıcının HMAC imzasını, parse etmeden önce **ham gövde** üzerinde, **constant-time** karşılaştırmayla kontrol et; tutmuyorsa hiçbir şeye güvenme, reddet. JSON'ı decode edip yeniden encode edersen imza bozulur — body'ye dokunmadan doğrula.
2. **Idempotency'i DB'ye yaptır.** Provider'ın event id'sini (ya da hash'ini) **UNIQUE constraint**'li bir tabloda sakla; bir transaction içinde **check-or-insert** yap. Bir-kez garantisini uygulama mantığı değil, veritabanı verir — aynı id ikinci kez gelirse insert zaten patlar.
3. **Önce hızlı 200 dön, işi kuyruğa al.** Webhook handler'ında ödeme yan etkilerini **senkron çalıştırma**; imzayı doğrula, kaydı al, hızlıca `200` dön ve asıl işi kuyruğa it. Worker aynı key üzerinde idempotent kalsın. Sağlayıcı timeout yiyip tekrar denemesin diye yanıt hızlı olmalı.
4. **Her katmanda iki-kez-çağrılmaya dayanıklı ol.** Handler da, worker da, yan etki de aynı bildirimle iki kez çağrılınca tek bir net sonuç üretmeli. "Bir kere gelir" varsayımını hiçbir yere koyma.
5. **Sıralamayı ve replay penceresini gözet.** "Refund" bildirimi "charge"dan önce gelebilir; out-of-order durumları handle et. Provider'ın replay/retry penceresini de bil ki eski bir bildirimi yeni sanıp tekrar işlemeyesin.

**Sonuç:** Ben olsam akışı **imza doğrula → hızlı 200 → işi kuyruğa al** diye kurar, idempotency'i `event_id` üzerindeki UNIQUE constraint + transaction'lı check-or-insert ile DB'ye yaptırırdım. HMAC'i ham body'de constant-time karşılaştır. Double-spending'i app kodu değil, veritabanının tekillik garantisi durdurur. Kuyruk tarafının operasyonel detayını hub yazısında bulursun.

## İlgili Yazılar

- [Laravel Queue ve Supervisor ile Asenkron İşlemler](/blog/laravel-queue-ve-supervisor-ile-asenkron-islemler/) — Blog
- [Container'ı non-root çalıştırınca mount edilen storage dizinine yazamıyorum, izinleri nasıl çözerim?](https://www.muhammetsafak.com.tr/sor-bakalim/containeri-non-root-calistirinca-mount-edilen-storage-dizinine-yazamiyorum-izinleri-nasil/) — Sor Bakalım
- [OpenAPI şemasını önce mi yazmalıyım yoksa koddan mı üretmeliyim?](https://www.muhammetsafak.com.tr/sor-bakalim/openapi-semasini-once-mi-yazmaliyim-yoksa-koddan-mi-uretmeliyim/) — Sor Bakalım
- [Public API'mde bir endpoint'i kullanımdan kaldırırken sunset sürecini nasıl yönetmeliyim?](https://www.muhammetsafak.com.tr/sor-bakalim/public-apimde-bir-endpointi-kullanimdan-kaldirirken-sunset-surecini-nasil-yonetmeliyim/) — Sor Bakalım
