İçeriğe geç
Can Uğurlu
Geri dön

Webhook'u önce kabul edin, sonra işleyin

Webhook endpoint’i iş yapmaz, iş kaydeder. Gelen isteği doğrula, kuyruğa at, 200 dön. İşin kendisi worker’da çalışır.

Bu kuralı ihlal eden her entegrasyon aynı yerden patlıyor.

Neden

Provider sizin işinizi beklemiyor. Shopify webhook’unuza 5 saniye tanıyor. O süre içinde 2xx dönmezseniz isteği başarısız sayıyor ve tekrar gönderiyor. Toplamda 48 saat boyunca 19 kez deniyor.

Şimdi senaryoyu düşünün. Sipariş webhook’u geliyor, siz request içinde ürünü çekiyor, stoğu düşüyor, ERP’ye yazıyor ve müşteriye mail atıyorsunuz. Hepsi 6 saniye sürüyor.

Shopify 5. saniyede vazgeçti. Sizin kodunuz 6. saniyede işi bitirip mail attı. Shopify aynı webhook’u tekrar gönderdi. İkinci mail gitti. Stok iki kez düştü.

Kimse hata görmedi. Log temiz. Sadece stok yanlış.

Doğrusu

Endpoint üç şey yapar:

public function handle(Request $request)
{
    // 1. Doğrula
    if (! $this->verifySignature($request)) {
        return response('', 401);
    }

    // 2. Kaydet ve kuyruğa at
    $event = WebhookEvent::create([
        'provider'    => 'shopify',
        'topic'       => $request->header('X-Shopify-Topic'),
        'external_id' => $request->header('X-Shopify-Webhook-Id'),
        'payload'     => $request->getContent(),
    ]);

    ProcessWebhookEvent::dispatch($event);

    // 3. Hemen dön
    return response('', 200);
}

Bu kod 50 milisaniyede biter. Provider mutlu, kuyruk dolu, iş worker’da.

Ham gövdeyi saklayın

$request->all() değil, $request->getContent(). İki sebepten.

Birincisi imza doğrulaması ham gövde üzerinden yapılıyor. JSON’ı parse edip tekrar serialize ederseniz anahtar sırası değişir, imza tutmaz.

İkincisi hata ayıklama. Üç hafta sonra “bu sipariş neden yanlış işlendi” sorusunun tek cevabı, provider’ın o gün tam olarak ne gönderdiğidir. Parse edilmiş hâli o soruyu cevaplamaz.

Aynı olay iki kez gelecek

Gelecek. Provider’ın hatası değil, tasarımı böyle. En az bir kez teslim garantisi veriyor, tam bir kez değil.

Bu yüzden external_id alanına unique index koyun:

ALTER TABLE webhook_events
  ADD UNIQUE KEY uniq_provider_external (provider, external_id);

Aynı olay ikinci kez geldiğinde insert patlar, siz de yakalayıp 200 dönersiniz. Provider tekrar denemeyi bırakır, iş ikinci kez çalışmaz.

X-Shopify-Webhook-Id başlığı her teslim denemesinde aynı kalıyor. İdempotency anahtarınız hazır, üretmeye gerek yok.

Worker patlarsa

Kuyruğa atmanın asıl kazancı burada. Worker başarısız olursa iş failed_jobs tablosunda durur. Sebebini görürsünüz, düzeltirsiniz, queue:retry ile tekrar çalıştırırsınız.

Request içinde işlemiş olsaydınız o olay kaybolmuştu. Provider’ın tekrar göndermesini bekleyip 48 saatlik pencereyi kaçırırsanız veri gitti.

Sırayı garanti etmiyor

Bir uyarı. Kuyruk sırayı garanti etmez. orders/create ve orders/updated webhook’ları ters sırada işlenebilir.

Çözüm sırayı zorlamak değil, olayı sırasız işlenebilir yazmak. Payload’daki updated_at alanına bakın. Kayıttaki tarih gelen olaydan yeniyse işlemi atlayın.

if ($order->updated_at >= $payload['updated_at']) {
    return; // daha eski bir olay, atla
}

Özet

Endpoint doğrular, kaydeder, döner. Üç satır. Geri kalan her şey worker’ın işi.

Bunu baştan böyle kurmak on dakika sürüyor. Sonradan düzeltmek, iki ay boyunca neden çift sipariş oluştuğunu aramak demek.


Bu yazıyı paylaş:

Sonraki Yazı
Pazaryeri stok senkronunda hız limiti