vellTRDocs
RehberlerKimlik Doğrulama ve İstek İmzalama

Kimlik Doğrulama ve İstek İmzalama

Başlangıç15 min

Kvell'in HMAC-SHA256 istek imzalama yönteminin nasıl çalıştığını, TypeScript, Python ve PHP'de çalışan imzalama örnekleriyle öğrenin.


Kvell'in genel (public) API'sine yapılan her üye işyeri sunucu çağrısı, gizli anahtarınızdan hesaplanan bir HMAC-SHA256 imzasıyla doğrulanır — takıp unutabileceğiniz bir bearer token yoktur. Bu rehber tam olarak hangi baytların imzalandığını anlatır, üç dilde çalışan yardımcı fonksiyonlar verir ve bir şeyler ters gittiğinde karşınıza çıkacak hataları listeler.

İki tür API erişimi

Kvell dokümantasyonu iki farklı kimlik doğrulama şemasını kapsar — bunları birbirine karıştırmayın:

İmzalı genel APIPanel API'si
Kullanım alanı/v1/payments, /v1/customers, /v1/installments ve benzeri üye işyeri sunucu çağrılarıWebhook endpoint yapılandırması (/v1/stores/{storeId}/webhooks) ve panele ait diğer kaynaklar
BaşlıklarX-Api-Key + X-Signature + X-TimestampAuthorization: Bearer <jwt>
Kimlik bilgisiMağazanızın pk_/sk_ anahtar çiftiPanelde oturum açarak alınan bir oturum token'ı
Anlatıldığı yerBu rehberWebhook'lar

Bu rehber, para hareketi yapan işlemler için sunucunuzun kullandığı imzalı genel API ile ilgilidir.

Zorunlu başlıklar

BaşlıkZorunluÖrnekAçıklama
X-Api-KeyEvetpk_live_a1b2c3d4e5f6g7h8Mağazanızın genel anahtarı
X-SignatureEvet9f8e7d6c5b4a…Hex ile kodlanmış HMAC-SHA256 imzası (aşağıya bakın)
X-TimestampEvet2026-04-13T10:00:00ZISO 8601 UTC istek zamanı, sunucu saatine göre ±5 dakika içinde doğrulanır
X-Request-Idİsteğe bağlı7b9e5c21-4a3f-4e8a-b9d2-1f4a8c9e5b10UUID v4. Siz göndermezseniz Kvell bir tane üretir ve yanıtta geri döner — kendi değerinizi eklerseniz loglarınız boyunca izlenebilir olur
Idempotency-Keyİsteğe bağlıd290f1ee-6c54-4b01-90e6-d701748f0851UUID v4. Aynı anahtarın tekrarı, 24 saat boyunca birebir aynı yanıtı döndürür

Kurallı (canonical) dize

İmza, \n ile birleştirilmiş şu tam dört satırlık dize üzerinden hesaplanır:

Shell
{X-Timestamp}
{HTTP-METODU}
{YOL}
{sha256-hex(istek-gövdesi)}
SatırDeğerNotlar
1Gönderdiğiniz X-Timestamp değerinin birebir aynısıYeniden biçimlendirilmiş bir hali değil, baytı baytına aynı dize
2HTTP metoduBüyük harf — GET, POST, PUT, PATCH, DELETE
3İstek yoluYalnızca rota yolu — domain yok, sorgu dizesi yok ve {id} gibi her yer tutucu gerçek değerle değiştirilmiş olmalı
4Gövde hash'iHam istek gövdesi baytlarının küçük harf hex SHA-256'sı. Gövdesiz bir istek için (GET, DELETE), bu boş dizenin SHA-256'sıdır

Bu dizenin tamamı ardından gizli anahtarınızla (sk_…) HMAC-SHA256 ile imzalanır, hex olarak kodlanır ve X-Signature olarak gönderilir.

POST /v1/payments/card için, gerçek bir gövdeyle birlikte işleyen bir örnek:

Shell
2026-04-13T10:00:00Z
POST
/v1/payments/card
{tam JSON gövde baytlarının sha256-hex'i}

Ve GET /v1/payments/pay_1a2b3c4d5e6f için — yolun gerçek id'yi içerdiğine, literal {id} dizesini değil, ve gerçek istek URL'sinde bir sorgu dizesi bulunsa bile yolun sorgu dizesi taşımadığına dikkat edin:

Shell
2026-04-13T10:00:00Z
GET
/v1/payments/pay_1a2b3c4d5e6f
{boş dizenin sha256-hex'i}

Son satır, endpoint'ten bağımsız olarak her gövdesiz istekte (GET, DELETE) aynı sabit değerdir — bunu tercih ettiğiniz dilde bir kez hesaplayın, sonra hep tanıyacaksınız:

Shell
printf '' | shasum -a 256

Adım adım

  1. X-Timestamp'i, sonunda Z olacak şekilde ISO 8601 formatında geçerli UTC zamanı olarak üretin.
  2. HTTP metodunu büyük harfe çevirin.
  3. Rota yolunu tam olarak istendiği gibi alın — gerçek kaynak id'leri yerine konmuş, sorgu dizesi yok, sonda eğik çizgi yok.
  4. Göndermek üzere olduğunuz ham gövde baytlarının (gövde yoksa boş dizenin) SHA-256 hash'ini hex olarak kodlayın.
  5. Dört değeri \n ile birleştirerek kurallı dizeyi oluşturun.
  6. Kurallı dizeyi gizli anahtarınızla HMAC-SHA256 ile imzalayın; sonucu hex olarak kodlayın.
  7. X-Api-Key, X-Timestamp ve X-Signature başlıklarını istekle birlikte gönderin.

İmzalama yardımcı fonksiyonları

Aşağıdakilerden birini sunucunuza ekleyin — her biri metodu, yolu ve ham gövde dizesini alır, imzayı ve hesaplandığı zaman damgasını döndürür.

TypeScript / Node.js

TypeScript
import crypto from 'node:crypto';

export function signRequest(
  secretKey: string,
  method: string,
  path: string,
  body: string,
  timestamp: string = new Date().toISOString(),
): { signature: string; timestamp: string } {
  const bodyHash = crypto.createHash('sha256').update(body, 'utf8').digest('hex');
  const canonical = `${timestamp}\n${method.toUpperCase()}\n${path}\n${bodyHash}`;
  const signature = crypto.createHmac('sha256', secretKey).update(canonical, 'utf8').digest('hex');
  return { signature, timestamp };
}

// Kullanım: bir ödeme oturumu oluşturma
const body = JSON.stringify({
  transactionId: 'ORDER-2026-001',
  amount: 15000,
  currency: 'TRY',
  description: 'Order #1234',
  returnUrl: 'https://merchant.example.com/return',
});

const { signature, timestamp } = signRequest(
  process.env.KVELL_SECRET_KEY!,
  'POST',
  '/v1/payments/sessions',
  body,
);

await fetch('https://api.pay.kvell.group/v1/payments/sessions', {
  method: 'POST',
  headers: {
    'X-Api-Key': process.env.KVELL_PUBLIC_KEY!,
    'X-Timestamp': timestamp,
    'X-Signature': signature,
    'Content-Type': 'application/json',
  },
  body, // hash'lediğiniz dizenin birebir aynısını gönderin
});

Python

Python
import hashlib
import hmac
from datetime import datetime, timezone

def sign_request(secret_key: str, method: str, path: str, body: str, timestamp: str | None = None) -> tuple[str, str]:
    timestamp = timestamp or datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
    body_hash = hashlib.sha256(body.encode('utf-8')).hexdigest()
    canonical = f"{timestamp}\n{method.upper()}\n{path}\n{body_hash}"
    signature = hmac.new(secret_key.encode('utf-8'), canonical.encode('utf-8'), hashlib.sha256).hexdigest()
    return signature, timestamp

# Kullanım: bir ödeme oturumu oluşturma
import json
import os
import requests

body = json.dumps({
    "transactionId": "ORDER-2026-001",
    "amount": 15000,
    "currency": "TRY",
    "description": "Order #1234",
    "returnUrl": "https://merchant.example.com/return",
})

signature, timestamp = sign_request(os.environ["KVELL_SECRET_KEY"], "POST", "/v1/payments/sessions", body)

requests.post(
    "https://api.pay.kvell.group/v1/payments/sessions",
    data=body,  # hash'lediğiniz dizenin birebir aynısını gönderin
    headers={
        "X-Api-Key": os.environ["KVELL_PUBLIC_KEY"],
        "X-Timestamp": timestamp,
        "X-Signature": signature,
        "Content-Type": "application/json",
    },
)

PHP

PHP
<?php

function sign_request(string $secretKey, string $method, string $path, string $body, ?string $timestamp = null): array {
    $timestamp = $timestamp ?? gmdate('Y-m-d\TH:i:s\Z');
    $bodyHash = hash('sha256', $body);
    $canonical = "{$timestamp}\n" . strtoupper($method) . "\n{$path}\n{$bodyHash}";
    $signature = hash_hmac('sha256', $canonical, $secretKey);
    return [$signature, $timestamp];
}

// Kullanım: bir ödeme oturumu oluşturma
$body = json_encode([
    'transactionId' => 'ORDER-2026-001',
    'amount' => 15000,
    'currency' => 'TRY',
    'description' => 'Order #1234',
    'returnUrl' => 'https://merchant.example.com/return',
]);

[$signature, $timestamp] = sign_request(getenv('KVELL_SECRET_KEY'), 'POST', '/v1/payments/sessions', $body);

$ch = curl_init('https://api.pay.kvell.group/v1/payments/sessions');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $body, // hash'lediğiniz dizenin birebir aynısını gönderin
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'X-Api-Key: ' . getenv('KVELL_PUBLIC_KEY'),
        "X-Timestamp: {$timestamp}",
        "X-Signature: {$signature}",
        'Content-Type: application/json',
    ],
]);
$response = curl_exec($ch);

Resmi SDK'lar (Node.js, Python, PHP, Go, Java, .NET — bkz. SDK'lar) bunların tamamını sizin için otomatik olarak hesaplar; ham bir imzalama yardımcı fonksiyonuna yalnızca HTTP API'ye doğrudan entegre olduğunuzda ihtiyaç duyarsınız.

Zaman damgası penceresi

X-Timestamp, Kvell'in sunucu saatine göre ±5 dakika içinde olmalıdır. Bu, ele geçirilmiş bir isteğin yeniden oynatılabileceği pencereyi sınırlar. Pratikte:

  • Zaman damgasını her zaman imzalamadan hemen önce üretin — önceden hesaplayıp isteği daha sonra göndermeyin.
  • UTC kullanın. Asla bir saat dilimi ofseti (+03:00) göndermeyin; her zaman Z ile bitirin.
  • Sunucunuzun saati kayıyorsa NTP ile senkronize edin — birkaç dakikalık bir kayma bile her isteği pencerenin dışına itmeye yeter.

Idempotency anahtarları

Durum değiştiren her çağrı — bir oturum oluşturmak, bir kartı tahsil etmek, bir iade göndermek — bir Idempotency-Key başlığı kabul eder (sizin ürettiğiniz bir UUID v4). Kvell, yanıtı (anahtarınız, endpoint, idempotency anahtarı) ile anahtarlanmış şekilde 24 saat boyunca önbelleğe alır:

  • Aynı anahtar, aynı gövde → orijinal yanıtı, orijinal HTTP durum kodu ve X-Request-Id ile birlikte geri alırsınız. Bir zaman aşımından sonra kimseyi iki kez tahsil etmeden yeniden denemek güvenlidir.
  • Aynı anahtar, farklı bir gövde → KVELL-GW-2001 ile 409. Bu, bir anahtarı yanlışlıkla farklı bir istek için tekrar kullandığınız bir hatayı yakalar.

POST /v1/payments/card'ın ek bir inceliği vardır: Idempotency-Key başlığı olmasa bile, transactionId gövde alanı zaten bir oturumdan geldiği için tek başına bir idempotency güvencesi görevi görür. Tahsilat akışının kendisi için Kart Ödemelerini Kabul Etme rehberine bakın.

Yaygın hatalar (1xxx) ve nasıl düzeltilir

KodAnlamıÇözüm
KVELL-GW-1001X-Timestamp eksik, hatalı biçimlendirilmiş veya ±5 dakikalık pencerenin dışındaSunucu saatinizin senkronize (NTP) olduğunu ve zaman damgasının sonunda Z bulunan UTC ISO 8601 olduğunu doğrulayın
KVELL-GW-1002İmza uyuşmazlığı — hesaplanan imza sunucunun beklediğiyle eşleşmiyorKurallı dizeyi tam olarak yeniden hesaplayın: metod büyük/küçük harfini, yolu (sorgu dizesi yok, domain yok) ve gönderdiğiniz tam baytları hash'lediğinizi kontrol edin
KVELL-GW-1003X-Api-Key eksik, tanınmıyor veya iptal edilmişBaşlığın mevcut olduğunu ve anahtarın çağırdığınız ortamla eşleştiğini doğrulayın (pk_demo_… anahtarı ile canlı domaine çağrı yapmak başarısız olur)

Her hata yanıtı aynı zarfı (envelope) izler:

JSON
{
  "code": "KVELL-GW-1002",
  "message": "Signature verification failed.",
  "requestId": "7b9e5c21-4a3f-4e8a-b9d2-1f4a8c9e5b10"
}

Destek ekibiyle iletişime geçtiğinizde requestId'yi ekleyin — isteğinizi loglarda bulmanın en hızlı yoludur.

Sorun giderme kontrol listesi

  • Saat kayması — sunucunuzun saatini NTP ile senkronize edin; X-Timestamp'i her zaman imzalamadan hemen önce UTC olarak üretin.
  • Gövde hash uyuşmazlığı — ağ üzerinden gönderdiğiniz tam bayt dizesini hash'leyin. İstek gövdesini hash'i hesaplamak için bir kez, göndermek için tekrar serileştirirseniz (farklı anahtar sırası, farklı boşluk kullanımı) hash'ler eşleşmez. Bir kez serileştirin, o dizeyi hash'leyin, aynı dizeyi gönderin.
  • Yol uyuşmazlığı — rotanın gerçek yolunu, gerçek kaynak id'leri yerine konmuş şekilde imzalayın (/v1/payments/{id} değil, /v1/payments/pay_1a2b3c4d5e6f); sorgu parametreleri olan GET isteklerinde bile asla sorgu dizesini eklemeyin; sonda eğik çizgi yok; https:// veya domain yok.
  • Metod büyük/küçük harfi — kurallı dizede her zaman büyük harf kullanın (post değil, POST).
  • Boş gövde — boş dizeyi ("") hash'leyin; "{}", null veya atlanmış bir satır değil.

Sonraki adımlar

RehberNeyi kapsar
Kart Ödemelerini Kabul Etmeİmzalı isteklerinizi işe koşun: oturumlar, doğrudan tahsilatlar, 3DS
Webhook'larDiğer imza şeması — Kvell'in size gönderdiklerini doğrulama
API ReferansıHer endpoint'in tam imza deseni, istek/yanıt şekliyle yan yana