Webhook'lar
Cüzdanın asenkron sonuçları webhook ile bildirilir. Para hareketlerinin sonucunu polling ile değil webhook ile öğrenin; GET uçları teyit içindir.
Webhook kaydı, para hareketiyle aynı veritabanı işleminde yazılır. Yani "para tamamlandı ama olay kayboldu" durumu yapısal olarak mümkün değildir — teslimat gecikebilir, ama olay üretilmemiş olamaz.
Olay kataloğu
Bakiye yükleme
| Olay | Ne zaman |
|---|---|
wallet.topup.completed | Yükleme bakiyeye yansıdı |
wallet.topup.failed | Yükleme başarısız |
wallet.topup.refund_pending | Yükleme iadesi başlatıldı |
wallet.topup.refunded | Yükleme iadesi tamamlandı |
wallet.topup.reversed | Tamamlanmış yükleme ters kaydedildi |
Transfer
| Olay | Ne zaman |
|---|---|
wallet.transfer.completed | Transfer tamamlandı |
wallet.transfer.failed | Transfer başarısız, rezervasyon çözüldü |
wallet.transfer.reversed | Tamamlanmış transfer ters kaydedildi |
Para çekme
| Olay | Ne zaman |
|---|---|
wallet.withdrawal.processing | Talimat bankada işleniyor |
wallet.withdrawal.completed | Çekim tamamlandı |
wallet.withdrawal.failed | Çekim başarısız |
wallet.withdrawal.awaiting_recon | Sonuç belirsiz, mutabakata alındı |
wallet.withdrawal.reversed | Tamamlanmış çekim ters kaydedildi |
Ödeme ve iade
| Olay | Ne zaman |
|---|---|
wallet.payment.completed | Cüzdanla ödeme tamamlandı |
wallet.payment.failed | Ödeme başarısız |
wallet.payment.reversed | Tamamlanmış ödeme ters kaydedildi |
wallet.refund.credited | Cüzdana iade kredilendi |
wallet.refund.reversed | İade ters kaydedildi |
Sadakat
| Olay | Ne zaman |
|---|---|
wallet.loyalty.earned | Puan kazanıldı |
wallet.loyalty.redeemed | Puan harcandı |
wallet.loyalty.expired | Puan lotu süresi doldu |
wallet.loyalty.tier_changed | Kullanıcının seviyesi değişti |
wallet.badge.granted | Rozet verildi |
wallet.badge.reward_credited | Rozet ödülü yatırıldı |
wallet.campaign.paused_insufficient_funds | Kampanya bütçesi bitti, duraklatıldı |
Fatura ve fatura ödeme
| Olay | Ne zaman |
|---|---|
wallet.invoice.issued · paid · cancelled | Fatura yaşam döngüsü |
wallet.billpay.dispatched | Talimat kuruma iletildi |
wallet.billpay.confirmed | Kurum onayladı |
wallet.billpay.failed | Kurum reddetti |
wallet.billpay.awaiting_recon | Sonuç belirsiz |
wallet.billpay.reversed | Ters kaydedildi |
Operasyon
| Olay | Ne zaman |
|---|---|
wallet.suspense.created | Eşleşmeyen para askıya alındı |
Sağlayıcısı henüz bağlanmamış yeteneklerin olayları (çekim, fatura ödeme) katalogda yer alır ama üretimde akmaz. Tüketicinizi baştan bunlara hazırlayın; entegrasyon bağlandığında kod değişikliği gerekmez. Bkz. Yol Haritası.
Zarf
Her teslimat aynı zarfla gelir; cüzdan olayının kendi alanları data altındadır:
{
"id": "0b6d3f2e-9a41-4c7e-8d25-6f1a3b9c4e07",
"type": "wallet.transfer.completed",
"sourceService": "wallet-service",
"organizationId": "3c9a1e5b-7d2f-4b8e-a6c4-1f0e9d2b7a35",
"productCode": "WALLET_SERVICE",
"environment": "LIVE",
"eventVersion": 1,
"schemaVersion": "v1",
"createdAt": "2026-08-07T12:00:04.512Z",
"data": {
"schemaVersion": "wallet.webhook.v1",
"domain": "WALLET",
"eventName": "wallet.transfer.completed",
"operationId": "7a3e9c1d-2b4f-4e8a-9d6c-0f5b8e2a1c93",
"sourceWalletAccountId": "c2a5e8f1-9d3b-4c7a-8e6f-5b1d2a9c0e47",
"destinationWalletAccountId": "a4d7b0e3-5f8c-4a1d-9b2e-6c3f8a1d0e59",
"amount": "10050",
"currency": "TRY",
"feeTotal": "250",
"taxTotal": "125"
}
}| Alan | Anlamı |
|---|---|
id | Olay kimliği — x-lunixi-event-id header'ıyla aynı değer |
type | Olay adı (yukarıdaki katalog) |
organizationId | Organizasyonunuz |
environment | TEST veya LIVE |
createdAt | Olayın oluşturulma zamanı |
data | Cüzdan olayının alanları |
data içinde:
| Alan | Anlamı |
|---|---|
schemaVersion | Cüzdan payload sürümü — geriye dönük uyumsuz değişiklikte (breaking change) artar |
domain | Her zaman WALLET |
eventName | Olay adı (yukarıdaki katalog) |
data içinde bunların yanında olaya özgü alanlar taşınır (operasyon kimliği, tutarlar, durum).
Tutarlar payload'da minor-unit taşınır ve PII maskelidir. Ham telefon numarası, IBAN veya isim webhook gövdesinde gönderilmez — bunlara ihtiyacınız varsa ilgili GET ucundan okuyun.
İmza doğrulama
Her teslimat şu header'ları taşır:
| Header | İçerik |
|---|---|
x-lunixi-event-id | Olayın benzersiz kimliği |
x-lunixi-event-type | Olay adı |
x-lunixi-signature | Virgülle ayrılmış imza listesi: bir veya iki v2=<hex> öğesi (ikincisi yalnız 24 saatlik secret-rotasyonu grace penceresinde) |
x-lunixi-signature-timestamp | İmza zaman damgası (ISO 8601) |
v2 imzası ham gövdenin HMAC'idir — gövdeyi parse etmeden doğrularsınız:
signature = HMAC-SHA256( secret, "{eventId}.{timestamp}.{rawBody}" )Başlıktaki her v2= öğesini bu değerle sabit zamanlı karşılaştırın; herhangi biri eşleşirse imza geçerlidir (secret rotasyonunun 24 saatlik grace penceresinde iki v2= öğesi bulunur).
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyWalletWebhook(rawBody, headers, secret, toleranceSeconds = 300) {
const eventId = headers['x-lunixi-event-id'];
const timestamp = headers['x-lunixi-signature-timestamp'];
if (Math.abs(Date.now() - Date.parse(timestamp)) > toleranceSeconds * 1000) return false;
const expected = createHmac('sha256', secret)
.update(`${eventId}.${timestamp}.${rawBody}`)
.digest('hex');
return String(headers['x-lunixi-signature'] || '')
.split(',')
.map((item) => item.trim())
.filter((item) => item.startsWith('v2='))
.some((item) => {
const received = Buffer.from(item.slice(3), 'hex');
const wanted = Buffer.from(expected, 'hex');
return received.length === wanted.length && timingSafeEqual(received, wanted);
});
}İmzayı doğrulamadan hiçbir webhook'u işlemeyin. Endpoint'iniz internete açıktır; imzasız gövde saldırgan tarafından üretilmiş olabilir.
Teslim garantisi
Cüzdan webhook'ları en az bir kez (at-least-once) teslim edilir:
- Bir operasyonun durumu değiştiğinde (ör.
completed,failed,reversed) ilgili olay, durum değişikliğiyle aynı işlemde kaydedilir. Operasyon bir duruma ulaştıysa o durumun olayı da üretilmiştir; altyapı kesintisinde olay kaybolmaz, kesinti bittiğinde gecikmeli olarak teslim edilir. - Aynı olay birden fazla kez teslim edilebilir. Her tekrar aynı
x-lunixi-event-iddeğerini taşır; bu değer olay başına sabittir ve tekilleştirme anahtarı olarak kullanılmalıdır. - Olaylar gecikmeli gelebilir. Operasyonun güncel durumunu her zaman
GET /wallet/operations/{id}ile teyit edebilirsiniz.
Yeniden deneme
Endpoint'iniz 2xx dönmezse teslimat yeniden denenir. Bekleme süreleri sabit bir cetvele göredir ve her değere ±%20 jitter uygulanır; deneme hakkı tükendiğinde teslimat dead letter olarak işaretlenir (DEAD_LETTER).
| Davranış | Değer |
|---|---|
| Yanıt zaman aşımı | ~10 saniye |
| Bekleme | Sabit cetvel: 10 sn, 30 sn, 2 dk, 5 dk, 15 dk, 30 dk, 1 sa, 2 sa, 4 sa, 8 sa, 12 sa, 24 sa (±%20 jitter) |
| Deneme sayısı | 12 (~52 saatlik ufuk; aşılırsa DEAD_LETTER) — yeniden oynatma bütçeyi sıfırlar |
| Yönlendirme | Takip edilmez — 3xx başarısızlıktır |
Uzun süre hata veren bir endpoint otomatik olarak devre dışı bırakılabilir. Endpoint'inizi düzelttikten sonra panelden durumunu kontrol edin.
Doğru tüketim
- Hızlı
2xxdönün. İşi kuyruğa alın; webhook isteği içinde uzun iş yapmayın — zaman aşımı yeniden denemeye yol açar. - Idempotent olun. Teslimat en az bir kezdir; aynı olay birden fazla kez gelebilir.
x-lunixi-event-id'yi saklayın ve aynı kimlikle gelen tekrarı yok sayın. - Sıra garantisi yoktur.
completedolayıprocessing'den önce gelebilir. Durumu olayın sırasına değil içeriğine göre yazın; şüphedeGET /wallet/operations/{id}ile teyit edin. - Bilinmeyen olayı yok sayın. Yeni olay tipleri eklenebilir; tanımadığınız
typeiçin2xxdönüp geçin. 3xxdöndürmeyin. Yönlendirme takip edilmez.
Kurulum
Endpoint URL'nizi ve dinleyeceğiniz olayları merchant panelden tanımlarsınız; imza secret'ınız orada üretilir. Endpoint'iniz herkese açık ve HTTPS olmalıdır.
Yaygın hatalar
| Durum | Sebep | Çözüm |
|---|---|---|
| İmza hiç tutmuyor | Gövde parse edilip yeniden serileştirildi | v2 imzasını ham gövde byte'ları üzerinden doğrulayın — parse etmeyin |
| Aynı işlem iki kez kaydedildi | Idempotency yok | x-lunixi-event-id ile tekilleştirin |
| Olaylar gelmiyor | Endpoint art arda hata verdiği için pasifleşmiş | Panelden durumu kontrol edin |
| Bazı olaylar hiç gelmiyor | O yeteneğin entegrasyonu bağlı değil | Yol Haritası |