Ödeme Webhook'ları
Ödemeler durumlarını asenkron değiştirir: 3-D Secure doğrulaması yönlendirmeden dakikalar sonra tamamlanır, bir capture takasa girer, bir yetkilendirme gece süresi dolar, bir ters ibraz haftalar sonra gelir. Webhook'lar bu yaşam döngüsü değişikliklerinin her birini sunucunuza push eder — böylece GET /payments/:id endpoint'ini döngüde poll etmeniz gerekmez ve sipariş durumunuz ödeme durumundan sessizce kopamaz.
Bu sayfa hangi ödeme eventlerinin var olduğunu ve ne zaman tetiklendiğini anlatır. Endpoint yapılandırması, imza doğrulama ve genel teslimat sözleşmesi tüm ürünlerde ortaktır — bkz. Webhook kurulumu.
Ödeme eventleri
On beş event tipi ödeme yaşam döngüsünün tamamını kapsar; ödeme linklerinin dokuz yaşam döngüsü event'i aşağıda, senetle ödemenin senet event'leri ayrıca listelenir:
| Event | Ne zaman tetiklenir |
|---|---|
payment.authorized | Yalnız-authorize hold başarılı: tutar kartta bloke, para henüz çekilmedi (AUTHORIZED). Bir hold asla payment.captured üretmez — teslimat için capture'ı bekleyin. |
payment.captured | Para sizde — siparişi bunun üzerine teslim edin. Tahsilatın gerçekleştiği her rayda atar: anında satışta (kart, 3-D veya cüzdan) ya da açık bir capture kümülatif çekimi yetkilendirilen tutarın tamamına getirdiğinde (CAPTURED). Fiilen çekilen tutarı (capturedAmountMinor) taşır ve muhasebe kaydı bu event'ten doğar. Parçalı ödemede ana ödemenin sonucu da bu event'le gelir: settlementMode: "SPLIT" ve legs[] taşır. |
payment.partially_captured | Kısmi capture tamamlandı (PARTIALLY_CAPTURED). capturedThisCaptureMinor, cumulativeCapturedMinor, remainingCapturableMinor taşır. |
payment.failed | Ödeme başarısız oldu, para çekilmedi (FAILED). amountMinor ve varsa ret detayı taşır (normalizedErrorCode, retryability, failureSource). Parçalı ödemede ana ödemenin başarısız sonucu da bu event'le gelir (settlementMode: "SPLIT"). |
payment.refunded | İade ödemeyi tam-iadeye getirdi (REFUNDED). refundId, refundAmountMinor, totalRefundedMinor, fullyRefunded taşır. |
payment.partially_refunded | Kısmi iade tamamlandı (PARTIALLY_REFUNDED). Alanları payment.refunded ile aynıdır. |
payment.voided | Capture edilmemiş bir yetkilendirme serbest bırakıldı (VOIDED) |
payment.expired | Capture edilmemiş bir yetkilendirme 7 gün boyunca çekilmedi ve platform tarafında EXPIRED (terminal) olarak kapatıldı. Karttaki bloke bu event'le kalkmaz — bankaya iptal gönderilmez; bloke bankanın kendi süresiyle düşer. Hold'u erken serbest bırakmak için payment.voided'a yol açan void çağrısını kullanın. |
payment.split.completed | Parçalı ödeme: bütün parçalar çekildi (COMPLETED). splitPaymentId ve parça özetleri taşır. Aynı anda ana ödeme için payment.captured de gelir; siparişi bir kez, o event'le kapatın. Parçaların kendi event'leri yalnız payment.split.leg_events seçeneği açıksa gönderilir → Parça olayları |
payment.split.voided | Parçalı ödemenin telafisi tamamlandı: bir parça başarısızdı, provizyonlar bırakıldı ve çekilmiş parçalar iade edildi — müşteri net sıfır ödedi (VOIDED) |
payment.split.compensating | Parçalı ödeme operasyon takibine düştü (COMPENSATING + reconciliation): cause alanı CAPTURE_FAILURE veya COMPENSATION_INCOMPLETE; parçalı ödeme başına bir kez ateşlenir |
payment.split.outcome_pending | Parçalı ödemede geri alınamayan parçanın sonucu bekleniyor; kart provizyonları sonuç netleşene kadar tutulur. pendingLegId ve pendingLegSequence taşır |
payment.split.reconciliation_required | Parçalı ödemede geri alınamayan parça çekildi ama siparişin kalanı tahsil edilemedi; teslimata siz karar verirsiniz. shortfallAmountMinor, reason ve varsa unsettledLegId taşır |
payment.split.leg_refund_pending | Parçalı ödemenin çekilmiş bir parçası için telafi iadesi istendi; iade başarılı olana kadar denenir. Parça event'idir: yalnız payment.split.leg_events seçeneği açıksa gönderilir |
payment.disputed | Ters ibraz açıldı ya da faz değiştirdi — chargebackId, disputeState, disputedAmountMinor, openChargebackCount taşır; bkz. Ters İbraz |
Tek bir ödeme yaşamı boyunca genellikle birden fazla event üretir — örneğin payment.authorized → payment.captured → aylar sonra payment.disputed.
Teslimatı payment.captured'a bağlayın. Tahsilatın gerçekleştiği tek sinyal budur — kart, 3-D ve cüzdan raylarının hepsinde.
payment.succeeded KALDIRILDI ve artık üretilmiyor. Bu event payment.captured ile aynı ödemeyi aynı intentId/orderId ile anlatıyordu; ikisine birden abone olan entegrasyonlar tek satış için iki webhook alıyor ve siparişi orderId üzerinden işliyorlarsa iki kez işliyorlardı. Kodunuzda type === 'payment.succeeded' dalı varsa payment.captured'a taşıyın; tutar alanı amountMinor değil capturedAmountMinor'dır.
Ödeme linki event'leri
Linkle Ödeme linklerinin yaşam döngüsü dokuz event üretir: payment.link.created, payment.link.updated, payment.link.paused, payment.link.resumed, payment.link.deactivated, payment.link.expired, payment.link.completed, payment.link.taken_down, payment.link.takedown_reverted.
Linkle alınan ödemeler yukarıdaki ödeme event'lerini üretir; bu event'lerin data'sına source: "PAYMENT_LINK", paymentLinkId, paymentLinkCode ve paymentLinkAttemptId alanları eklenir. Siparişi yine payment.captured ile teslim edin — payment.link.completed ikinci bir para sinyali değildir. Payload ve alanlar: Linkle Ödeme Webhook'ları.
Senet event'leri
Senetle Öde ile alınan ödeme, senetler düzenlendiğinde payment.captured üretir; tahsilat ise vadede, borçludan doğrudan sizin sağlayıcı hesabınıza yapılır. Her senedin vadedeki sonucu ayrı bir event'tir:
| Event | Ne zaman tetiklenir |
|---|---|
payment.promissory_note.paid | Bir senet ödendi |
payment.promissory_note.protested | Bir senet ödenmedi ve protesto edildi |
payment.promissory_note.cancelled | Bir senet iptal edildi |
Bu event'ler senet ve durum başına bir kez gönderilir, ödemenin durumunu değiştirmez (status CAPTURED kalır) ve senedin alanlarını data.promissoryNote içinde taşır: serialNumber, sequence, amountMinor, dueDate, status, occurredAt. Senet durumları sağlayıcıdan düzenli aralıklarla okunur; event anlık değildir. Panelde bu event'ler Senetle Ödeme grubundadır. Payload ve okuma sıklığı: Senetle Öde — Webhook'lar.
Teslimat payload'u
Her teslimat, endpoint'inize şu şekilde bir JSON gövdeyle yapılan HTTP POST'tur:
{
"id": "6a1f0c9e-2b3d-5e4f-8a7b-1c2d3e4f5a6b",
"type": "payment.captured",
"sourceService": "payment-service",
"organizationId": "org_…",
"productCode": "PAYMENT_GATEWAY",
"environment": "LIVE",
"eventVersion": 1,
"schemaVersion": "v1",
"createdAt": "2026-07-06T12:00:02.000Z",
"data": {
"intentId": "9c6f2b1a-4e8d-4a3f-b5e6-2d7c1a9b8e30",
"merchantId": "m_…",
"orderId": "ORD-84722",
"currency": "TRY",
"status": "CAPTURED",
"capturedAmountMinor": "10050"
}
}data her zaman intentId, merchantId, orderId, currency ve status taşır; buna yukarıdaki tabloda listelenen event'e özgü alanlar eklenir. data içindeki tutar alanları minor-unit string'dir ve çekilen tutarı gösterir: taksitli ödemede, isteğinizdeki amount'a (sepet toplamı) sistemin eklediği vade farkı dahildir.
Parçalı ödemenin ana ödeme ve payment.split.* event'lerinde data ayrıca splitPaymentId taşır. intentId yalnız Checkout Formu parçalı ödemesinin ana ödeme event'lerinde bulunur ve siparişin ödeme kaydıdır; API ile başlatılan parçalı ödemede ana ödemenin kimliği splitPaymentId'dir → Parçalı Ödeme › Olaylar.
status, createdAt anındaki durumdur — "şu anki" durum değil.
Bir teslimat başarısız olursa 12 denemeye kadar, toplam ~2 gün boyunca yeniden denenir; event sırası da garanti edilmez. Bu yüzden status alanı event'in anlattığı andaki durumu bildirir. Teslimat kararı için bu yeterlidir (gövde imzalıdır, id ile idempotent işleyin). Ödemenin güncel durumunu görmek için GET /payments/:id çağırın.
Teslimat header'ları:
| Header | Değer |
|---|---|
x-lunixi-event-id | Event id'si (gövdedeki id ile aynı) — dedupe anahtarı |
x-lunixi-event-type | Event tipi (gövdedeki type ile aynı) |
x-lunixi-signature | Virgülle ayrılmış imza listesi: bir veya iki v2=<hex HMAC> öğesi (ikincisi yalnız 24 saatlik secret-rotasyonu grace penceresinde) — aşağıya bakın |
x-lunixi-signature-timestamp | ISO-8601 imzalama zamanı |
v2 imzası ham gövdenin HMAC'idir: HMAC-SHA256(secret, eventId + "." + timestamp + "." + rawBody) — parse yok, yeniden serileştirme yok. Başlık bir veya iki v2= öğesi taşır; ikincisi yalnız secret rotasyonunun 24 saatlik grace penceresinde bulunur. Doğrulama kodu ve detaylar: Webhook kurulumu.
Teslimat modeli
Teslimatlar platform genelindeki webhook sözleşmesine uyar (detaylar Webhook kurulumu sayfasında):
- İmzalı — her teslimat, endpoint'inizin webhook secret'ıyla hesaplanan
x-lunixi-signatureheader'ını taşır. Gövdeye güvenmeden önce imzayı doğrulayın; doğrulamayı geçemeyen teslimatları reddedin. - Retry'lı — endpoint'iniz
2xxdönmezse teslimat backoff ile tekrar denenir. Handler'ınızı hızlı tasarlayın: önce onaylayın, işlemeyi asenkron yapın. - At-least-once — retry'lar aynı eventi birden fazla kez görebileceğiniz anlamına gelir. Handler'ları idempotent yapın: işlemeyi event id'sine (ya da
(paymentId, event)çiftine) anahtarlayın ve tekrarları yok sayın. - Sırasız — eventlerin gerçekleşme sırasıyla geleceğini varsaymayın. Mantığınızı event geliş sırasından değil, ödemenin güncel
statusdeğerinden yürütün (şüphedeysenizGET /payments/:idile teyit edin).
Bir eventi işleme
İmzayı doğrulayın
Webhook secret'ınızla beklenen imzayı hesaplayın ve teslimattaki imzayla karşılaştırın. Eşleşmeyen her şeyi atın.
Hızlı onaylayın
Eventi kaydettikten hemen sonra 2xx dönün. Yavaş olan her iş (e-posta, sipariş teslimi, defter kayıtları) arka plan işine aittir — yavaş bir handler başarısızlık gibi görünür ve retry tetikler.
İdempotent işleyin
Bu event id'sini daha önce işleyip işlemediğinizi kontrol edin. İşlediyseniz 2xx dönüp hiçbir şey yapmayın. İşlemediyseniz durum değişikliğini siparişinize uygulayın.
Webhook ile 3-D Secure callback'inin farkı
3-D Secure akışını kullanıyorsanız, doğrulama bittiğinde alıcının tarayıcısı da callback URL'inize geri POST edilir — _signature ve _timestamp alanlarını taşıyan HMAC-imzalı bir form POST.
Bu iki kanalın görevleri farklıdır:
| 3-D callback (form POST) | Webhook | |
|---|---|---|
| Taşıyıcı | Alıcının tarayıcısı | Platformdan sunucudan-sunucuya |
| Amaç | Alıcının oturumunu devam ettirmek, sonuç sayfasını göstermek | İş sonucunu kesinleştirmek |
| Güvenilirlik | Best-effort — alıcı sekmeyi kapatabilir, bağlantısı kopabilir, yönlendirmeyi kurcalayabilir | İmzalı, retry'lı, at-least-once |
Callback bilgilendirir; webhook otoritedir. Callback'i (HMAC imzasını ve _timestamp değerini doğruladıktan sonra) yalnızca alıcının arayüzünü yönetmek için kullanın. Bir siparişi asla yalnızca callback'e dayanarak ödendi saymayın — ürünü gönderme, bakiye tanımlama ve sipariş kapatma işlemlerini payment.captured webhook'una ya da sunucu tarafı bir GET /payments/:id kontrolüne bağlayın.
Hatalar & operasyonel notlar
| Durum | Ne yapmalı |
|---|---|
| İmza doğrulaması başarısız | Non-2xx ile reddedin; araştırın — teslimat platformdan gelmiyor ya da secret bayat. |
| Endpoint'iniz kapalıydı | Teslimatlar otomatik retry edilir; toparlandıktan sonra boşlukları GET /payments ile mutabık edin. |
| Aynı event tekrar geldi | At-least-once teslimatta beklenen durum — idempotent handler'ınız no-op yapmalı. |
| Bilinmeyen ödeme id'sine ait event | Loglayın ve alarm kurun; genellikle sistemleriniz ile platformun ayrıştığı anlamına gelir — Ödemeleri Listeleme ile mutabık edin. |
Sonraki adımlar
- Webhook kurulumu — endpoint'ler, secret'lar, kodda imza doğrulama
- Ödemeleri Listeleme — mutabakat için pull-tabanlı tamamlayıcı
- Ters İbraz —
payment.disputedsonrasında ne yapmalı - Kart — 3D Secure — merchant callback'ini üreten akış