# Ö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://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.

## Kısa cevap

Tek cümleyle: imza "bu gerçekten sağlayıcı mı"yı, idempotency "bu işi daha önce yaptım mı"yı sorar. İkincisinin genel hâlini, yani aynı isteği güvenle tekrar etmenin API tarafındaki kurallarını [idempotency yazısında](/tr/blog/api-de-idempotency-ayni-istegi-guvenle-tekrar-etmek/) anlatmıştım; burada aynı fikrin webhook üzerindeki uygulaması var.

## Neden

1. **İmza ile tekillik farklı şeyleri garanti eder.** Geçerli imzalı bir bildirim beş kez gelebilir; imza onun sahiciliğini söyler, kaçıncı kez geldiğini değil.

2. **Bir-kez garantisini uygulama mantığı veremez.** İki istek aynı anda gelirse "önce kontrol et, sonra yaz" mantığı ikisini de geçirir. Tekilliği ancak veritabanının kendi kısıtı verir.

3. **Yavaş yanıt yeni tekrarlar üretir.** Sağlayıcı timeout yerse aynı bildirimi yeniden gönderir; senkron çalışan bir handler kendi sorununu büyütür.

## Ne yapmalı

1. **İmzayı RAW body üzerinde doğrula.** HMAC imzasını, parse etmeden önce **ham gövde** üzerinde, **constant-time** karşılaştırmayla kontrol et. JSON'ı decode edip yeniden encode edersen imza bozulur.

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.

3. **Önce hızlı 200 dön, işi kuyruğa al.** İmzayı 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. Kuyruk tarafının operasyonel detayı [Laravel queue ve Supervisor yazısında](/tr/blog/laravel-queue-ve-supervisor-ile-asenkron-islemler/).

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.

5. **Sıralamayı ve replay penceresini gözet.** "Refund" bildirimi "charge"dan önce gelebilir; out-of-order durumları handle et.

**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.

## İlgili Yazılar

- [Laravel Queue ve Supervisor ile Asenkron İşlemler](/tr/blog/laravel-queue-ve-supervisor-ile-asenkron-islemler/) — Blog
- [API hata gövdelerimi RFC 7807 problem+json biçimine mi taşımalıyım?](https://muhammetsafak.com/tr/sor-bakalim/api-hata-govdelerimi-rfc-7807-problem-json-tasimaliyim/) — Sor Bakalım
- [Container'ı non-root çalıştırınca mount edilen storage dizinine yazamıyorum, izinleri nasıl çözerim?](https://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://muhammetsafak.com/tr/sor-bakalim/openapi-semasini-once-mi-yazmaliyim-yoksa-koddan-mi-uretmeliyim/) — Sor Bakalım
