<!-- Source: https://docs.infraio.xyz/tr/webhooks/overview -->
<!-- Last updated: 2026-10-04 -->

# Webhook'lar — Genel bakış

Webhook'lar **yetkili** sinyaldir. Tarayıcı callback'leri (`onSuccess`)
ve panel görünümleri kolaylıktır; webhook'lar gerçek kaynaktır.

## Teslim garantileri

- **En az bir kez.** Sunucunuz zaman aşımı içinde 2xx döndürmezse, tek
  bir event en fazla **6 kez** teslim edilebilir. Handler'ınızı
  idempotent yapın — `X-Delivery` üzerinde dedup yapın (payload'da bir
  `event_id` alanı yoktur; stabil teslim UUID'si idempotency anahtarıdır).
- **HTTP isteği başına bir event.** Batch yapma yok.
- **Uç nokta başına izolasyon.** Birden fazla kayıtlı uç noktanız varsa,
  her biri kendi teslim ve yeniden deneme izini alır. Yavaş bir uç nokta
  diğerlerini geciktirmez.
- **İmzalı.** Her payload bir `X-Signature` başlığı taşır (ve bir
  rotation'dan sonraki 24 saatlik pencerede, ayrıca bir
  `X-Signature-Prev`). Gövde ile bir şey yapmadan önce doğrulayın.
  Bkz. [İmza doğrulama](https://docs.infraio.xyz/tr/webhooks/signature-verification).

## Abone olunabilir event tipleri

| Event | Şu durumda tetiklenir… |
| --- | --- |
| `payment.settled` | Zincir üstü transfer, zincirin onay sayısını temizledi. **Siparişleri ödendi olarak işaretlemek için bunu kullanın.** |
| `payment.failed` | Bir fiat ödeme, ödeme provider'ı tarafından reddedildi. Kripto zaman aşımları için tetiklenmez — onlar `checkout.expired` olarak yüzeye çıkar, kısa kripto ödemeleri ise `payment.underpaid` olarak yüzeye çıkar. |
| `payment.underpaid` | Fonlar geldi ama sipariş toplamından eksik (tipik: stablecoin transfer ücreti tutardan alındı). |
| `payment.overpaid` | Fonlar sipariş toplamının fazlasıyla geldi. Fazlalık kaydedilir ama otomatik olarak iade edilmez. |
| `order.created` | Yeni bir sipariş açıldı — ya B2B API çağrınız ile ya da bir checkout-oturumu dönüşümüyle. |
| `order.canceled` | Bir sipariş iptal edildi durumuna geçti. Payload'un `data.reason`'ı manuel iptali `payment_timeout` (ödenmemiş bir sipariş zaman aşımına uğradı) ile ayırır. |
| `order.resolved` | Bir `PARTIAL_PAID` sipariş `PAID`'e çözümlendi — satıcı eksikliği kabul etti. |
| `order.reopened` | Önceden otomatik-iptal edilmiş bir sipariş (`canceled_reason=payment_timeout`) satıcı tarafından yeniden açıldı. |
| `checkout.created` | Bir alıcı bir sipariş için checkout'u açtı. |
| `checkout.completed` | Alıcı tarafı akış bitti (zincir üstü tahsilatı ima etmez — bunun için `payment.settled` kullanın). |
| `checkout.expired` | Alıcı vazgeçti ve oturum TTL'i doldu. |
| `payment.refund.requested` | Bir iade kaydı oluşturuldu — satıcı tarafından başlatılan bir API çağrısı veya müşteri-gönderimli bir iade-talep formundan. |
| `payment.refund.approved` | Bekleyen bir iade onay iş akışınızı geçti. |
| `payment.refund.rejected` | Bekleyen bir iade reddedildi. |
| `payment.refund.executed` | İadenin zincir üstü transferi temizlendi ve kayıt nihai `executed` durumuna geçti. |
| `refund_request.created` | Bir iade-talep token'ı üretildi. `data.source` `b2b` / `dashboard` / `renewal`. Abone olmak opsiyoneldir — hangi token'ın sipariş başına şu anda aktif olduğunu takip eden denetim pipeline'ları için kullanışlı. |
| `refund_request.renewal_requested` | Bir alıcı token süresi dolduktan sonra "Yeni bağlantı talep et" butonuna tıkladı. **Abone olmak şiddetle önerilir** — bu, satıcının yenileme widget'ında harekete geçmesi gereken yeni bir öğenin olduğuna dair işarettir. |
| `refund_request.renewed` | Bir yenileme onaylandı ve yeni bir token eskisini değiştirdi. `data.old_token` / `data.new_token` denetim zincirini oluşturur. |
| `refund_request.canceled` | Bir satıcı panelden bir token'ı `CANCELED`'a geçirdi (örn. bir yenileme talebini reddetti, aktif bir bağlantıyı sonlandırdı). Idempotent — yalnızca ilk geçiş bir event yayar. `data.reason` opsiyonel satıcı notudur. |

### Planlanan (Yakında)

> **Note:**
>
> **Yakında.** Bu event'ler henüz kullanılamayan yinelenen faturalar ve
> aboneliklere aittir. Yukarıdaki abone olunabilir tabloda **yer almazlar**
> ve bugün abone olunamazlar. Bkz.
> [Yinelenen faturalar](https://docs.infraio.xyz/tr/guides/recurring-invoices).

| Planlanan event | Şu durumda tetiklenir… |
| --- | --- |
| `subscription.created` | Bir abonelik oluşturuldu. |
| `invoice.created` | Bir faturalama döngüsü için fatura oluşturuldu. |
| `invoice.paid` | Bir fatura ödendi. |
| `subscription.past_due` | Bir fatura vade tarihini geçti ve ödenmedi. |
| `subscription.canceled` | Bir abonelik iptal edildi. |

Panelin uç nokta formu aynı event'leri listeler. Var olmayan bir event'e
abone olmak, uç noktayı kaydettiğinizde reddedilir.

> **Note:**
>
> **Test event'leri abone olunabilir değildir.** Panelin uç nokta başına
> **Send Test** butonu, o tek uç noktaya hemen, yeniden deneme olmadan bir
> `webhook.test.ping` event'i gönderir. Yukarıdaki katalogda yer almaz:
> abone olduğunuz için değil, kayıtlı bir uç noktanız olduğu için alırsınız.

> **Note:**
>
> Yalnızca işlediğiniz event'lere abone olun. Her uç noktanın kendi event
> filtresi vardır; joker `"*"` "her event, gelecekte eklenecekler dahil"
> anlamına gelir. Daha az event'e abone olmak handler'ınızı daha basit
> tutar ve uç noktanız hata verdiğinde daha az yeniden deneme anlamına gelir.

## Payload + başlıklar

**HTTP gövdesi doğrudan event'e özgü veri nesnesidir.** Stripe stili
dış zarf yok — event tipi, teslim ID'si ve emisyon timestamp'i gibi
alanlar **başlıklarda** yaşar. `payment.settled` için gövde şöyle görünür:

```json
{
  "receipt_id":        "rcp_…",
  "order_id":          "ord_…",
  "payment_intent_id": "pin_…",
  "checkout_session_id": "cst_…",
  "merchant_id":       "mer_…",
  "customer_id":       "cus_…",
  "total":             "49.00",
  "currency":          "USD",
  "payment_method":    "crypto",
  "token":             "USDC",
  "network":           "polygon",
  "tx_hash":           "0x…",
  "deposit_address":   "0x…",
  "treasury_address":  "0x…",
  "amount_received":   "49.00",
  "confirmations":     5,
  "metadata":          { /* event başına */ }
}
```

Diğer event'ler kendi alanlarını taşır. Alan adları stabildir (lower
snake_case); zincir üstü işlem hash'i her zaman `tx_hash`'tir.

`tx_hash`, ağın kendi biçimindeki işlem tanımlayıcısıdır (EVM zincirlerinde `0x…`; TRON, Solana ve TON'da yerel hash veya imza). TRON, Solana ve TON'da alıcılar doğrudan Hazine cüzdanınıza ödediği için `deposit_address` bulunmayabilir; `confirmations` ise [Zincirler ve varlıklar](https://docs.infraio.xyz/tr/concepts/chains) sayfasını izler.

### Gelen istekteki başlıklar

```http
Content-Type:      application/json
X-Event:           payment.settled
X-Delivery:        7c9e6679-7425-40de-944b-e07fc1f90ae7
Idempotency-Key:   7c9e6679-7425-40de-944b-e07fc1f90ae7
X-Timestamp:       1729536000
X-Signature:       sha256=9a8b7c…
X-Signature-Prev:  sha256=fa31b2…    (yalnızca bir rotation kayma penceresinde)
```

| Başlık | Ne olduğu |
| --- | --- |
| `X-Event` | Event tipi (örn. `payment.settled`). JSON ayrıştırmasını atlamak istiyorsanız proxy katmanında bunun üzerinde yönlendirin. |
| `X-Delivery` | Teslim satırını tanımlayan UUID. Aynı `(event, endpoint)` çiftinin tüm yeniden denemelerinde **stabil** — idempotency anahtarınız olarak kullanın. |
| `Idempotency-Key` | `X-Delivery`'i yansıtır (aynı değer). Her teslimde ayarlanır. |
| `X-Timestamp` | Denemenin gönderildiği Unix-saniye. Payload'a imzalanmıştır, böylece yakalanan bir `(body, X-Signature)` çifti süresiz olarak yeniden oynatılamaz — timestamp'i tolerans pencerenizin dışında olan teslimleri reddedin. |
| `X-Signature` | `HMAC-SHA256(secret, X-Timestamp + "." + raw_body)`'nin `sha256=<hex>`'i. Bkz. [İmza doğrulama](https://docs.infraio.xyz/tr/webhooks/signature-verification). |
| `X-Signature-Prev` | **Önceki** secret ile aynı algoritma. Yalnızca siz rotate ettikten sonraki 24 saatlik pencerede mevcuttur — geçiş sırasında her iki anahtarı da çalıştıran doğrulayıcıların teslimleri kabul etmeye devam etmesini sağlar. Pencere kapandıktan sonra başlık gönderilmeyi durdurur. |

## Yeniden deneme programı

Uç noktanız zaman aşımı içinde `2xx` döndürmezse, bu programda yeniden
deneriz (timestamp'ler ilk denemeye göre):

| Deneme | Gecikme | Birikimli |
| --- | --- | --- |
| 1 | 0s | 0s |
| 2 | +1 dk | 1dk |
| 3 | +5 dk | 6dk |
| 4 | +15 dk | 21dk |
| 5 | +1 saat | 1s 21dk |
| 6 | +6 saat | 7s 21dk |

6. deneme başarısız olduktan sonra, teslim **Başarısız** olarak işaretlenir
ve hesap e-postanız bilgilendirilir. Başarısız event'leri panelin
**Geliştiriciler → Webhook'lar → Teslim geçmişi** panelinden yeniden
oynatabilirsiniz. Her replay, kendi `X-Delivery`'sine sahip yeni bir
tesliptir.

## Bir uç nokta kaydedin

[Satıcı panelinden](https://app.infraio.xyz):

1. **Geliştiriciler → Webhook'lar** → **+ Uç nokta ekle**
2. URL'nizi yapıştırın — yalnızca `https://…` (düz HTTP reddedilir;
   oluşturma formu ayrıca `localhost`'u, özel IP aralıklarını ve
   userinfo taşıyan URL'leri engeller)
3. Abone olunacak event'leri seçin (veya hepsi için `*`)
4. Ortam seçin — **test** veya **canlı** (her biri kendi secret'ını
   alır; asla çapraz geçmezler)
5. Kaydet → panel imzalama secret'ını (`whsec_…`) **bir kez** gösterir.
   Sunucu tarafında saklayın; sonraki iki özellik için ihtiyacınız olacak.

Satıcı başına ortam başına **10 uç noktaya** kadar kaydedebilirsiniz
(örn. biri production sipariş karşılama için, biri staging yansıtması
için, biri bir Slack bildirici için). Her birinin kendi yeniden deneme
durumu ve secret'ı vardır.

## Her uç noktada yaşam döngüsü eylemleri

Her uç nokta kartındaki ⋮ menüsü şunları sunar:

- **Düzenle** — URL'yi, açıklamayı veya abonelik listesini değiştir.
  Yeni URL, oluşturma ile aynı `https://`/SSRF kurallarıyla yeniden
  doğrulanır.
- **Send Test** — mevcut secret'ınızla imzalanmış bir `webhook.test.ping`
  zarfını senkron olarak POST eder. Panel HTTP durumunu, gecikmeyi ve
  yanıtınızın 512 baytlık bir parçacığını gösterir. Test ping'leri yeniden
  denenmez, böylece cevap anlıktır.
- **Rotate Secret** — yeni bir secret oluşturur. Önceki olan **24
  saat** geçerli kalır (teslimler pencere boyunca hem `X-Signature` hem
  de `X-Signature-Prev` taşır, böylece her iki anahtarı da çalıştıran
  doğrulayıcılar siz yeniden dağıtırken event'leri kabul etmeye devam
  eder).
- **Reveal Secret** — mevcut secret'ı yeniden gösterir. Yeni 2FA
  doğrulaması ile korunur ve denetim günlüğüne kaydedilir; yalnızca
  kopyanızı kaybettiğinizde ve Rotate kabul edilemediğinde kullanın.
- **Etkinleştir / Devre Dışı Bırak** — teslim geçmişini kaybetmeden
  uç noktayı açıp kapatın. Devre dışı bırakılmış uç noktalar panelde
  kalır ama yeni teslim almaz.
- **Sil** — kalıcıdır. Daha sonra yeniden etkinleştirebilirseniz Devre
  Dışı Bırak'ı kullanın.

## Handler'lar için ipuçları

1. **Hızlıca 2xx döndürün.** Ağır iş yapmadan önce `200 OK` ile
   onaylayın — sipariş karşılamayı bir arka plan işine taşıyın.
   Deneme başına zaman aşımı **10 saniyedir**; yanıtı bundan daha uzun
   tutmak bir yeniden denemeyi tetikler. Zaman aşımı platform tarafıdır
   ve satıcı tarafından yapılandırılamaz — handler'ınız gerçekten daha
   fazla zamana ihtiyaç duyuyorsa destekle iletişime geçin.
2. **`X-Delivery`** üzerinde dedup yapın (veya `Idempotency-Key` —
   aynı değer). 2xx döndürseniz bile, yukarı yönlü bir proxy bağlantıyı
   düşürebilir ve bir yeniden denemeyi tetikleyebilir; teslim ID'si aynı
   teslim satırının her yeniden denemesinde stabildir, bu yüzden doğru
   anahtardır.
3. **Bilinmeyen event tiplerine tolerans gösterin.** Yeni event'ler
   görünebilir; 4xx yerine 200 döndürün ve hiçbir şey yapmayın, yoksa
   bu teslimler yeniden denenmeye devam eder.
4. **`X-Delivery`'yi iş mantığınızın yanında loglayın.** Bir şey ters
   gittiğinde, bu, bizim tarafımız ile sizin tarafınız arasındaki join
   anahtarıdır.

## Sırada ne var

- [İmza doğrulama](https://docs.infraio.xyz/tr/webhooks/signature-verification) — tam algoritma
  + replay-koruma desenleri.
- [Kavramlar → Oturumlar](https://docs.infraio.xyz/tr/concepts/sessions) — her event
  tetiklendiğinde bir oturumun hangi durumda olduğu.
