vellTRDocs
RehberlerWebhook'lar

Webhook'lar

Orta15 min

Ö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:

Shell
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"]
  }'
JSON
{
  "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"
}

secret yalnı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:

OlayNe zaman tetiklenir
payment.authorizedBir ön yetkilendirme veya 3DS bekleyen bir tahsilat ihraççı tarafından onaylandığında
payment.capturedPara hareket etti — kesin "ödendi" sinyaliniz
payment.refundedBir iade (tam veya kısmi) tamamlandığında
payment.failedBir tahsilat reddedildiğinde ya da bir 3DS doğrulaması başarısız olduğunda veya süresi dolduğunda
payment.chargeback_receivedKart sahibinin bankası, tahsil edilmiş bir ödemeye karşı bir chargeback (ters ibraz) açtığında
invoice.paidBarındırılan bir fatura ödendiğinde
subscription.renewedTekrarlayan bir abonelik döngüsü başarıyla tahsil edildiğinde
payout.succeededBanka 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ıkAçıklama
X-Kvell-EventOlay türü, örn. payment.captured
X-Kvell-Event-IdMantıksal olay başına sabittir — bunu tekilleştirme (dedup) anahtarınız olarak kullanın
X-Kvell-DeliveryHer teslimat denemesi için benzersizdir — her yeniden denemede değişir, bunun üzerinden tekilleştirme yapmayın
X-Kvell-Signaturesha256=<ham gövdenin, endpoint sırrınızla anahtarlanmış hex HMAC-SHA256'sı>
X-Request-IdBu teslimatı Kvell'in dahili loglarıyla ilişkilendirir
JSON
{
  "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)

TypeScript
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)

Python
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', 200

Her 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.

TypeScript
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:

DenemeBir önceki başarısızlıktan sonraki gecikme
1Anında
210 saniye
31 dakika
410 dakika
51 saat
66 saat
  1. deneme de başarısız olduktan sonra, endpoint degraded olarak 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), hemen 200 ile 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:

Shell
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:

Shell
curl "https://api.pay.kvell.group/v1/merchants/mrc_8a1b2c3d4e5f/webhook-deliveries?status=failed&limit=20" \
  -H "Authorization: Bearer <panel-jwt-tokeniniz>"
JSON
{
  "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 200 ile 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

RehberNeyi kapsar
Kart Ödemelerini Kabul Etmepayment.captured'ın checkout akışındaki yeri
Kimlik Doğrulama ve İstek İmzalamaİmzalı genel API — webhook doğrulamasından farklı bir şema
API Referansı — WebhooksHer endpoint yapılandırması ve teslimat inceleme rotası