Kimlik Doğrulama ve İstek İmzalama
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 API | Panel 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ıklar | X-Api-Key + X-Signature + X-Timestamp | Authorization: Bearer <jwt> |
| Kimlik bilgisi | Mağazanızın pk_/sk_ anahtar çifti | Panelde oturum açarak alınan bir oturum token'ı |
| Anlatıldığı yer | Bu rehber | Webhook'lar |
Bu rehber, para hareketi yapan işlemler için sunucunuzun kullandığı imzalı genel API ile ilgilidir.
Zorunlu başlıklar
| Başlık | Zorunlu | Örnek | Açıklama |
|---|---|---|---|
X-Api-Key | Evet | pk_live_a1b2c3d4e5f6g7h8 | Mağazanızın genel anahtarı |
X-Signature | Evet | 9f8e7d6c5b4a… | Hex ile kodlanmış HMAC-SHA256 imzası (aşağıya bakın) |
X-Timestamp | Evet | 2026-04-13T10:00:00Z | ISO 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-1f4a8c9e5b10 | UUID 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-d701748f0851 | UUID 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:
{X-Timestamp}
{HTTP-METODU}
{YOL}
{sha256-hex(istek-gövdesi)}| Satır | Değer | Notlar |
|---|---|---|
| 1 | Gönderdiğiniz X-Timestamp değerinin birebir aynısı | Yeniden biçimlendirilmiş bir hali değil, baytı baytına aynı dize |
| 2 | HTTP metodu | Büyük harf — GET, POST, PUT, PATCH, DELETE |
| 3 | İstek yolu | Yalnızca rota yolu — domain yok, sorgu dizesi yok ve {id} gibi her yer tutucu gerçek değerle değiştirilmiş olmalı |
| 4 | Gövde hash'i | Ham 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:
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:
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:
printf '' | shasum -a 256Adım adım
X-Timestamp'i, sonundaZolacak şekilde ISO 8601 formatında geçerli UTC zamanı olarak üretin.- HTTP metodunu büyük harfe çevirin.
- Rota yolunu tam olarak istendiği gibi alın — gerçek kaynak id'leri yerine konmuş, sorgu dizesi yok, sonda eğik çizgi yok.
- Göndermek üzere olduğunuz ham gövde baytlarının (gövde yoksa boş dizenin) SHA-256 hash'ini hex olarak kodlayın.
- Dört değeri
\nile birleştirerek kurallı dizeyi oluşturun. - Kurallı dizeyi gizli anahtarınızla HMAC-SHA256 ile imzalayın; sonucu hex olarak kodlayın.
X-Api-Key,X-TimestampveX-Signaturebaş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
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
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
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 zamanZile 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-Idile 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-2001ile409. 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
| Kod | Anlamı | Çözüm |
|---|---|---|
KVELL-GW-1001 | X-Timestamp eksik, hatalı biçimlendirilmiş veya ±5 dakikalık pencerenin dışında | Sunucu 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şmiyor | Kurallı 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-1003 | X-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:
{
"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 olanGETisteklerinde 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 (
postdeğil,POST). - Boş gövde — boş dizeyi (
"") hash'leyin;"{}",nullveya atlanmış bir satır değil.
Sonraki adımlar
| Rehber | Neyi kapsar |
|---|---|
| Kart Ödemelerini Kabul Etme | İmzalı isteklerinizi işe koşun: oturumlar, doğrudan tahsilatlar, 3DS |
| Webhook'lar | Diğer imza şeması — Kvell'in size gönderdiklerini doğrulama |
| API Referansı | Her endpoint'in tam imza deseni, istek/yanıt şekliyle yan yana |