Webhook'lar
Ödeme olaylarını alın, imzaları doğrulayın, yeniden deneme (retry) mantığını yönetin ve idempotent kalın.
Webhook'lar; bir ödemenin tahsil edildiğini, bir iadenin tamamlandığını ya da bir ödeme çıkışının (payout) ulaştığını hiçbir şeyi yoklamadan, asenkron ve güvenilir bir şekilde öğrenmenizin yoludur. Bu rehber bir endpoint kaydetmeyi, Kvell'in size gönderdiklerini doğrulamayı ve teslimatları güvenle işlemeyi kapsar.
Teslimat nasıl çalışır
Mağazanızdaki her olay, o olay türüne abone olan her endpoint'e imzalı bir HTTP POST tetikler. Endpoint'iniz 2xx ile yanıt vermezse Kvell sabit bir programa göre yeniden dener; program tükendikten sonra endpoint degraded olarak işaretlenir ve olay incelenmek üzere bir dead-letter kuyruğuna taşınır.
Webhook endpoint yapılandırması, imzalı genel API üzerinden değil, panel API'si üzerinden yapılır — X-Api-Key/X-Signature ile değil, panel oturumunuzla (Authorization: Bearer <jwt>) doğrulanır. Aradaki farkı görmek için Kimlik Doğrulama ve İstek İmzalama rehberine bakın.
1. Bir endpoint kaydedin
En hızlı yol panel üzerinden gitmektir: Integration → Webhooks → Add endpoint, bir HTTPS URL'si yapıştırın ve istediğiniz olay türlerini seçin (ya da tümüne abone olun).
Bunu programatik olarak yapmak — ortam kurulumunu script'lemek istediğinizde kullanışlıdır — panel API'sini oturum JWT'nizle çağırmak anlamına gelir:
curl -X POST https://api.pay.kvell.group/v1/stores/store_1a2b3c4d/webhooks \
-H "Authorization: Bearer <panel-jwt-tokeniniz>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://merchant.example.com/kvell/webhook",
"eventTypes": ["payment.captured", "payment.refunded", "payout.succeeded"]
}'{
"id": "whe_9a2e6b4d4e8a",
"merchantId": "mrc_8a1b2c3d4e5f",
"url": "https://merchant.example.com/kvell/webhook",
"secret": "whsec_2b3c4d5e6f708192a3b4c5d6",
"eventTypes": ["payment.captured", "payment.refunded", "payout.succeeded"],
"active": true,
"status": "active",
"createdAt": "2026-04-13T10:00:00Z"
}
secretyalnızca bir kez döndürülür. Bunu hemen sırlar (secrets) yöneticinize kaydedin — her teslimatı doğrulamak için kullanacağınız şey budur. Kaybederseniz, endpoint'i silip yenisini oluşturun.
Endpoint'lerinizi listeleyebilir (GET /v1/stores/{storeId}/webhooks), birini silmeden devre dışı bırakabilir ({"active": false} ile PATCH) veya tamamen kaldırabilirsiniz (DELETE, 204 döner).
2. Olay kataloğu
Kvell, üye işyeri endpoint'lerine yalnızca dört önek altındaki olayları dağıtır: payment.*, invoice.*, subscription.* ve payout.*. Yalnızca üzerine işlem yapacağınız olaylara abone olun:
| Olay | Ne zaman tetiklenir |
|---|---|
payment.authorized | Bir ön yetkilendirme veya 3DS bekleyen bir tahsilat ihraççı tarafından onaylandığında |
payment.captured | Para hareket etti — kesin "ödendi" sinyaliniz |
payment.refunded | Bir iade (tam veya kısmi) tamamlandığında |
payment.failed | Bir tahsilat reddedildiğinde ya da bir 3DS doğrulaması başarısız olduğunda veya süresi dolduğunda |
payment.chargeback_received | Kart sahibinin bankası, tahsil edilmiş bir ödemeye karşı bir chargeback (ters ibraz) açtığında |
invoice.paid | Barındırılan bir fatura ödendiğinde |
subscription.renewed | Tekrarlayan bir abonelik döngüsü başarıyla tahsil edildiğinde |
payout.succeeded | Banka hesabınıza veya kartınıza bir ödeme çıkışı ulaştığında |
3. Bir teslimat neye benzer
Her teslimat, bir JSON gövdesi ve şu başlıklarla gelen bir POST'tur:
| Başlık | Açıklama |
|---|---|
X-Kvell-Event | Olay türü, örn. payment.captured |
X-Kvell-Event-Id | Mantıksal olay başına sabittir — bunu tekilleştirme (dedup) anahtarınız olarak kullanın |
X-Kvell-Delivery | Her teslimat denemesi için benzersizdir — her yeniden denemede değişir, bunun üzerinden tekilleştirme yapmayın |
X-Kvell-Signature | sha256=<ham gövdenin, endpoint sırrınızla anahtarlanmış hex HMAC-SHA256'sı> |
X-Request-Id | Bu teslimatı Kvell'in dahili loglarıyla ilişkilendirir |
{
"eventId": "evt_7c8d9e0f1a2b",
"eventType": "payment.captured",
"occurredAt": "2026-04-13T10:00:02Z",
"merchantId": "mrc_8a1b2c3d4e5f",
"requestId": "7b9e5c21-4a3f-4e8a-b9d2-1f4a8c9e5b10",
"payload": {
"paymentId": "pay_1a2b3c4d5e6f",
"amount": 15000,
"currency": "TRY"
}
}payload şekli eventType'a bağlıdır — bir payment.captured yükü, GET /v1/payments/{id}'den alacağınız alanları yansıtır.
4. İmzayı doğrulayın
Gövdeye güvenmeden önce her zaman doğrulayın ve her zaman ham baytları hash'leyin — Kvell'in imzaladığı baytlarla eşleşmeyecek olan, parse edilmiş JSON'un yeniden serileştirilmiş bir halini değil.
TypeScript (Express)
import crypto from 'node:crypto';
import express from 'express';
const app = express();
// ÖNEMLİ: ham gövdeye karşı doğrulayın — bu route'ta herhangi bir JSON gövde
// ayrıştırıcısı çalışmadan ÖNCE bunu bağlayın, aksi halde hash'lediğiniz
// baytlar ağ üzerindeki gövdeyle eşleşmez.
app.post('/kvell/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signatureHeader = req.header('X-Kvell-Signature') ?? '';
const expected =
'sha256=' +
crypto
.createHmac('sha256', process.env.KVELL_WEBHOOK_SECRET!)
.update(req.body) // parse edilmiş JSON değil, Buffer
.digest('hex');
const isValid =
signatureHeader.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
if (!isValid) {
return res.status(401).send('invalid signature');
}
const event = JSON.parse(req.body.toString('utf8'));
// ... olayı işleyin (aşağıdaki idempotency bölümüne bakın) ...
res.status(200).send('ok');
});Python (Flask)
import hmac
import hashlib
import os
from flask import Flask, request, abort
app = Flask(__name__)
@app.post('/kvell/webhook')
def kvell_webhook():
signature_header = request.headers.get('X-Kvell-Signature', '')
expected = 'sha256=' + hmac.new(
os.environ['KVELL_WEBHOOK_SECRET'].encode('utf-8'),
request.get_data(), # herhangi bir JSON ayrıştırmasından önce okunan ham baytlar
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(signature_header, expected):
abort(401, 'invalid signature')
event = request.get_json()
# ... olayı işleyin (aşağıdaki idempotency bölümüne bakın) ...
return 'ok', 200Her iki örnek de ===/== yerine sabit zamanlı bir karşılaştırma (timingSafeEqual / compare_digest) kullanır — naif bir dize karşılaştırması, bir saldırganın imzayı bayt bayt tahmin etmek için kullanabileceği zamanlama bilgisi sızdırır.
5. Idempotent işleyin
X-Kvell-Event-Id, Kvell teslimatı yeniden denemek zorunda kalsa bile belirli bir mantıksal olay için sabittir — her denemede değişen X-Kvell-Delivery değil, tekilleştirme anahtarınız budur.
async function handleWebhook(event: { eventId: string; eventType: string; payload: unknown }) {
if (await alreadyProcessed(event.eventId)) {
return; // yinelenen teslimat — zaten işlendi, onayla ve devam et
}
await markProcessed(event.eventId);
await dispatch(event.eventType, event.payload);
}Kendi veri deponuzda event_id üzerinde bir benzersizlik kısıtı (ya da bir Redis SETNX) yeterlidir — "bu id'yi daha önce gördüm mü" sorusundan daha karmaşık bir şeye ihtiyacınız yok.
6. Yeniden deneme programı
2xx olmayan bir yanıt — zaman aşımı dahil — başarısız bir deneme sayılır. Kvell şu sabit programa göre yeniden dener:
| Deneme | Bir önceki başarısızlıktan sonraki gecikme |
|---|---|
| 1 | Anında |
| 2 | 10 saniye |
| 3 | 1 dakika |
| 4 | 10 dakika |
| 5 | 1 saat |
| 6 | 6 saat |
- deneme de başarısız olduktan sonra, endpoint
degradedolarak işaretlenir ve olay operatör incelemesi için bekletilir. 10 saniye içinde yanıt verin — işleyiciniz gerçek bir iş yapıyorsa (aşağı akış bir sisteme para geçirmek, e-posta göndermek), hemen200ile onaylayın ve işi daha sonra bir kuyrukta yapın. Yavaş, senkron bir işleyici, sonunda başarılı olacak olsa bile bir zaman aşımında bir yeniden deneme hakkını tüketir.
degraded bir endpoint'i düzelttikten sonra, belirli bir teslimatı manuel olarak yeniden kuyruğa alabilirsiniz:
curl -X POST https://api.pay.kvell.group/v1/deliveries/whd_0f1a2b3c4d5e/resend \
-H "Authorization: Bearer <panel-jwt-tokeniniz>"7. Teslimatları inceleyin
"Bu webhook gerçekten tetiklendi mi" sorusunu hata ayıklamanız gerektiğinde, mağaza ve duruma göre filtrelenebilir şekilde üye işyeriniz genelindeki teslimat denemelerini listeleyin:
curl "https://api.pay.kvell.group/v1/merchants/mrc_8a1b2c3d4e5f/webhook-deliveries?status=failed&limit=20" \
-H "Authorization: Bearer <panel-jwt-tokeniniz>"{
"items": [
{
"id": "whd_0f1a2b3c4d5e",
"endpointId": "whe_9a2e6b4d4e8a",
"eventType": "payment.captured",
"status": "delivered",
"attemptCount": 1,
"lastResponseCode": 200,
"createdAt": "2026-04-13T10:00:03Z"
}
]
}Yerel test
Geliştirme sırasında yerel sunucunuzu bir tünel (ngrok, Cloudflare Tunnel veya benzeri) ile dışa açın ve bir sandbox endpoint'ini tünel URL'sine yönlendirin. Sandbox olayları, üretimle aynı şekle sahiptir — belirli olay türlerini talep üzerine tetiklemek için Sandbox Ortamında Test Etme rehberine bakın. Canlıya geçmeden önce gerçek üretim URL'nizle değiştirin.
En iyi uygulamalar
- Hızlı yanıt verin, asenkron işleyin. Hemen
200ile onaylayın, asıl işi kuyruğa alın. - Güvenmeden önce doğrulayın. İmza kontrolünden geçirmediğiniz bir webhook gövdesine göre asla işlem yapmayın.
X-Kvell-Event-Idüzerinden tekilleştirin, teslimat id'si veya geliş sırası üzerinden değil.- Yalnızca HTTPS. Kvell düz
http://bir URL'ye teslimat yapmaz. - Sıralamayı varsaymayın. Aynı ödeme için iki olay, gerçekleşme sırasının dışında gelebilir — doğruluk kaynağı olarak webhook'ların geliş sırasını değil, ödemenin güncel
status'ünü (GET /v1/payments/{id}üzerinden) esas alın. - Sızan bir sırrı hemen döndürün — endpoint'i silip yeniden oluşturun; eski sır, endpoint kaybolduğu anda doğrulamayı bırakır.
X-Request-Id'yi loglayın — işlediğiniz her teslimat için; bir destek talebini Kvell'in dahili loglarıyla ilişkilendirmenin en hızlı yolu budur.
Sonraki adımlar
| Rehber | Neyi kapsar |
|---|---|
| Kart Ödemelerini Kabul Etme | payment.captured'ın checkout akışındaki yeri |
| Kimlik Doğrulama ve İstek İmzalama | İmzalı genel API — webhook doğrulamasından farklı bir şema |
| API Referansı — Webhooks | Her endpoint yapılandırması ve teslimat inceleme rotası |