İçeriğe geç
Dokümantasyon

Başlarken

Payniko, kartlı ödemeleri imzalı tek bir REST API üzerinden kabul eder. Müşteriyi Payniko ödeme sayfasına yönlendirebilir (Hosted API) veya kart bilgilerini kendi ödeme formunuzda alabilirsiniz (Direct API).

Temel URL ve veri biçimleri

Temel URL
https://payniko.com/api. Yol, sürüm bilgisi içermez.
Biçim
UTF-8 kodlamalı JSON. Hatalar application/problem+json biçiminde döner.
Tutarlar
İki ondalık basamaklı dize, örneğin "100.00". Tutarları hiçbir zaman JSON sayısı olarak göndermeyin.
Para birimi
USD, EUR, AED veya TRY. currency gönderilmezse sistemin ana para birimi uygulanır.
Zaman
UTC saat diliminde, milisaniye içeren ISO 8601 biçimi; örneğin 2026-11-01T12:00:00.000Z.
ID değerleri
Ödeme, iade, para çekme ve itiraz kimlikleri LUVEI-ABCD-123456 biçimindedir. Kimliği tam olarak saklayın.
Dil
Mesajlar Accept-Language başlığında belirtilen dilde döner (en, tr, de, ar, es, el); varsayılan değer en olarak uygulanır. Kodlar ve alan adları hiçbir zaman çevrilmez.

API anahtarlarını edinme

Yeni hesapta Sandbox ve canlı anahtarlar otomatik hazırlanır. Hesabınız varsa giriş yapın ve Erişim anahtarları bölümünü açın. Size hesap dosyası verildiyse dosyadaki kullanıcı adı ve şifreyle giriş yapın; yeniden kayıt olmanız gerekmez.

1. Hesabınızı oluşturun

  1. Hesap oluşturun. Kullandığınız e-posta adresini yazın ve Kod gönder düğmesine basın. Gelen e-postadaki altı haneli kodu kayıt ekranına girip Doğrula düğmesine basın. Kod gelmezse adresi ve gereksiz/spam klasörünü kontrol edin; ekrandaki bekleme süresi dolduğunda yeniden kod isteyin.

  2. E-posta doğrulanınca ülke kodunu seçin ve kendi Telegram hesabınıza bağlı telefon numaranızı yazın. Yanındaki Doğrula düğmesine basın. SMS beklemeyin: bu adım Telegram üzerinden tamamlanır.

  3. Ekrandaki Telegram bağlantısını açın veya bilgisayardaysanız QR kodunu telefonunuzla okutun. Telegram botunda Başlat / Start deyin ve botun numara paylaşma düğmesine basarak kendi numaranızı paylaşın. Numarayı mesaj olarak yazmak veya başka bir kişinin numarasını göndermek doğrulama yerine geçmez. Kayıt sayfasına dönüp Telegram doğrulandı bilgisini bekleyin.

  4. Hesap bilgilerine geç düğmesine basın. Kullanıcı adınızı ve şifrenizi oluşturun, şifreyi tekrar yazın ve ekrandaki onayları tamamlayıp Hesabı oluştur deyin. Kullanıcı adı 4–32 karakterdir; küçük harf, rakam, nokta, alt çizgi ve kısa çizgi kullanılabilir. Şifre en az 12 karakter olmalıdır.

  5. Hesabınız oluşturulduğunda kurtarma kodunu güvenli bir yere kaydedin. Bu kod yalnızca bu ekranda gösterilir; API gizli anahtarıyla aynı şey değildir. Kaydettiğinizi işaretleyin ve Kodu kaydettim, panele geç düğmesine basın.

2. Sandbox anahtarlarını ve üye işyeri kodunu alın

  1. Erişim anahtarları bölümünü açın ve Sandbox kartını bulun. Yeni hesapta anahtarlar hazırdır. Eski hesabınızda aktif anahtar yoksa bir etiket girip Anahtar üret düğmesine basın. IP adresi eklemeniz gerekmez.

  2. Sandbox kartından aşağıdaki üç bilgiyi alın. Gizli anahtar satırındaki göz simgesiyle değeri görüntüleyebilir, kopyalama simgesiyle kopyalayabilirsiniz. Anahtar aktif olduğu sürece hesabınızdan tekrar görüntülenebilir; yalnızca görmek için anahtarı yenilemeniz gerekmez.

  3. Bu bilgileri entegrasyonu yapacak geliştiricinize güvenli biçimde iletin veya kendi sunucunuzun gizli ayarlarında saklayın. API anahtarı ve gizli anahtar aynı Sandbox kartından alınmalıdır. Gizli anahtarı web sayfanızın JavaScript koduna, herkese açık dosyalara veya URL adresine koymayın.

BilgiNereden alınır?Ne için kullanılır?
Üye işyeri kodu / Merchant koduKarttaki altı haneli kodX-Merchant-Code başlığı. Aynı hesabın Sandbox ve canlı kartlarında aynı kod kullanılır.
API anahtarıSandbox kartındaki API anahtarı satırıX-Api-Key başlığı. Kullanılacak ortamı bu anahtar belirler.
Gizli anahtarAynı Sandbox kartındaki göz/kopyala simgeleriİstek imzasını oluşturmak ve webhook imzasını doğrulamak için kullanılır; istekte düz olarak gönderilmez.

Test ödemesi oluşturma

Sandbox ile entegrasyonunuzu gerçek para hareketi olmadan test edin. Başarılı test, canlı anahtar almanın koşulu değildir; imza, yönlendirme ve webhook akışınızı doğrulamak için önerilir.

  1. İstek yardımcı fonksiyonları bölümündeki sunucu örneklerinden birini kullanın. Örneği kendi Sandbox API anahtarınız, Sandbox gizli anahtarınız ve üye işyeri kodunuzla yapılandırın. Sandbox için ayrı bir API alan adı yoktur; dokümandaki temel URL kullanılır ve ortamı anahtar belirler.

  2. Ödeme deneyimi bölümünde Sandbox için gösterilen tutar sınırlarını ve kullanılabilir para birimlerini kontrol edin. Bu sınırlar içinde bir test tutarı seçin. İlk başarılı testte sonu .77 ile biten tutar kullanmayın; bu tutar testte bekleyen sonuç senaryosudur.

  3. POST /api/payments/hosted örneğindeki alanları doldurun: benzersiz bir merchant_order_id, tutar, para birimi, customer.email / customer.phone ve üç dönüş/bildirim adresi. E-posta ve telefon ulaşılabilir olmalı; telefon ülke koduyla yazılmalıdır. webhook_url kendi sunucunuzda herkese açık bir HTTPS adresi olmalıdır; Sandbox için de localhost veya özel ağ adresi kabul edilmez. success_url / fail_url ise ödeme sonrası tarayıcı dönüş adreslerinizdir.

  4. İmzalı POST /api/payments/hosted isteğini sunucunuzdan gönderin. Yeni test için yeni sipariş referansı ve Idempotency-Key kullanın. Aynı isteği bağlantı hatası nedeniyle tekrar gönderiyorsanız aynı Idempotency-Key değerini koruyun. 201 yanıtı ödemenin oluşturulduğunu gösterir; henüz başarılı ödeme değildir.

  5. Yanıttaki next_action.url adresini tam olarak alıp tarayıcıda açın. Açılan Sandbox ödeme formunda 4000000000000002 test kartını, gelecekteki bir son kullanma tarihini ve üç haneli bir CVV değerini kullanın. Örneğin tarih 12/30, CVV 123 olabilir. Test 3D Secure ekranı açıldığında başarılı/onaylı sonucu tamamlayın. Bu adımda gerçek kart kullanmayın. Diğer senaryolar Test kartları bölümündedir.

  6. Tarayıcıdaki dönüş ekranından sonra GET /api/payments/{payment_id} ile ödemeyi sorgulayın veya imzasını doğruladığınız webhook bildirimini işleyin. API sonucunda succeeded görmelisiniz. requires_action / processing ara durumdur; failed başarılı test sayılmaz. Yalnızca başarılı dönüş sayfasının açılmasına bakarak karar vermeyin.

  7. İşlem kayıtları bölümünde test sonucunu kontrol edin. Test kaydı başarılıysa entegrasyonun bu senaryosunu tamamladınız. Canlı erişim ayrı anahtar çiftiyle çalışır.

Canlı anahtarı alma ve kullanıma hazırlama

Sandbox ve canlı anahtarlar ayrı hazırlanır. Canlı anahtar için IP kaydı veya başarılı Sandbox işlemi şartı yoktur. Panelde ortam değiştirmek, sunucunuzdaki anahtarı değiştirmez.

  1. Canlı erişim sayfasını açıp Canlı kartını bulun. Yeni hesabın canlı anahtarı hazırdır; eski hesapta yoksa Anahtar üret düğmesiyle oluşturun.

  2. Aynı canlı karttan API anahtarını ve gizli anahtarını birlikte alın. Üye işyeri kodunuz Sandbox ile aynıdır. Gizli anahtarı yalnız sunucunuzun güvenli yapılandırmasına kaydedin.

  3. Sunucunuzun canlı yapılandırmasında Sandbox anahtar çifti yerine bu canlı anahtar çiftini kullanın. API adresi değişmez. Sandbox anahtarınız test için geçerli kalır; panelde bir kartı görüntülemek tek başına sunucunuzdaki anahtarı değiştirmez.

  4. Ödeme deneyimi bölümünde canlı ortamın limitlerini ve para birimlerini kontrol edin. Artık canlı anahtarınız hazırdır. Canlı anahtarla gönderilen ödeme istekleri gerçek tahsilat akışına girer; test kartlarıyla denemeye Sandbox anahtarıyla devam edin.

Bir adımda takılırsanız

Gördüğünüz durumYapmanız gereken
E-posta veya Telegram doğrulanmıyorE-postadaki kodu kayıt sayfasına girin. Telegram için ekrandaki bot bağlantısını kullanıp kayıt sırasında yazdığınız numarayı botun düğmesiyle paylaşın. Süresi dolan bağlantıyı kayıt ekranından yenileyin.
Anahtar üretilemiyorHesabınızın aktif ve API tahsilat izninin açık olduğunu kontrol edin. IP eklemek veya Sandbox ödeme tamamlamak gerekmez.
Canlı anahtar görünmüyorCanlı kartını açın. Yeni hesapta anahtar hazırdır; eski hesapta aktif anahtar yoksa anahtar oluşturun. Sorun sürerse destekle iletişime geçin.
Gizli anahtarı kaydetmeyi unuttunuzAktif anahtarın kartındaki göz veya kopyalama simgesini kullanın. Kurtarma kodu API gizli anahtarı değildir.
API tahsilat sayfası hesabınızda görünmüyorHesabınızın API tahsilat erişimini kontrol ettirmek için destekle iletişime geçin. Link tahsilat erişimi ile API tahsilat erişimi ayrı olabilir.

Uç noktalara genel bakış

Uç noktaAmaç
POST /api/payments/hostedHosted API ile ödeme oluşturma · Hosted API
GET /api/payments/{payment_id}Ödeme sorgulama · Hosted API · Direct API
GET /api/payments/bin/{bin6}BIN sorgulama (kart numarasının ilk altı hanesi) · Direct API
POST /api/payments/directDirect API ile ödeme oluşturma · Direct API
GET /api/account/balanceHesap bakiyesi · Bakiyeler
GET /api/account/available-balanceÇekilebilir bakiye · Bakiyeler
POST|GET /api/account/refundsİadeler ve iptaller · İadeler
POST|GET /api/account/withdrawalsUSDT ile para çekme · Para çekme
GET /api/account/disputesİtirazlar ve itiraz yanıtları · İtirazlar
GET|POST /api/webhooks/deliveriesWebhook gönderim geçmişi ve yeniden gönderim · Gönderimler
POST /api/sandbox/disputesTest itirazı oluşturma (yalnızca Sandbox anahtarı) · Sandbox

Tüm uç noktalar aynı kimlik doğrulama yöntemini kullanır; her POST isteği ayrıca bir Idempotency-Key gerektirir.

Kimlik doğrulama

Her istek, API anahtarınızı ve API gizli anahtarınızla hesaplanan bir HMAC-SHA256 imzasını içerir.

API anahtarı ve gizli anahtar

DeğerBiçimAçıklama
API anahtarıapi_ + 32 hexX-Api-Key başlığında gönderilir. Ortamı da anahtar belirler.
API gizli anahtarı64 hexİstekleri imzalamak ve webhook imzalarını doğrulamak için kullanılır; istekte gönderilmez. Hesap sahibi, aktif anahtarı panelde göz simgesiyle tekrar görüntüleyebilir veya kopyalayabilir.
Üye işyeri kodu[0-9]{6}X-Merchant-Code başlığında gönderilen hesap numaranız.

Her API anahtarı yalnızca bir ortama (Sandbox veya canlı ortam) aittir ve isteğin hangi ortamda çalışacağını anahtar belirler: test için Sandbox anahtarını, gerçek ödemeler için canlı ortam anahtarını kullanın. X-Environment başlığı isteğe bağlıdır; gönderilmezse istek, anahtarın ait olduğu ortamda çalışır. Gönderilirse değeri sandbox veya live olmalı (büyük/küçük harf duyarsız) ve anahtarın ortamıyla eşleşmelidir; aksi takdirde istek HTTP 401 ve 0055502 koduyla reddedilir ve hiçbir işlem yapılmaz. Zorunlu kimlik doğrulama başlıkları şunlardır: X-Api-Key, X-Merchant-Code, X-Timestamp ve X-Signature; her yazma isteğinde bunlara ek olarak Idempotency-Key gerekir. Ortam bilgisi imzalanacak dizeye dahil değildir. environment alanını içeren her yanıt gövdesi (ödemeler, bakiyeler, iadeler ve diğer kaynaklar) kullanılan ortamı bildirir.

Anahtar yenileme

  • Gizli anahtarın ifşa olmuş olabileceği durumlarda veya gizli anahtara erişimi olan bir kişi ayrıldığında yeni bir anahtar oluşturun. Önceki anahtar hemen geçersiz olur.
  • Bir ödemeye ait webhook’lar ve dönüş bağlantıları, ödemeyi oluşturan anahtarın gizli anahtarıyla imzalanır; bu kural, ödemeden uzun süre sonra gönderilen iade ve itiraz webhook’ları için de geçerlidir. Bu tür olaylar gelmeye devam edebileceği sürece önceki gizli anahtarı saklayın.

İstek başlıkları

BaşlıkZorunluDeğer
X-Api-KeyZorunluAPI anahtarınız.
X-Merchant-CodeZorunluAltı haneli üye işyeri kodunuz.
X-TimestampZorunluSaniye cinsinden güncel Unix zamanı (10 hane).
X-SignatureZorunluİstek imzası: 64 karakterlik, küçük harfli onaltılık (hex) değer.
Idempotency-KeyKoşulluHer POST isteğinde zorunludur. Bkz. Idempotency-Key.
Content-TypeKoşulluİstek gövdesi içeren isteklerde application/json.
Accept-Languageİsteğe bağlıHata mesajlarının dili: en (varsayılan), tr, de, ar, es veya el.

Her X- başlığını yalnızca bir kez gönderin. Başlık değerleri virgül, satır sonu veya boşluk içermemelidir.

İstek imzalama

İmzalanacak dize, satır sonu karakteriyle (line feed) birleştirilen dört değerden oluşur: zaman damgası, HTTP yöntemi, yol ve istek gövdesinin SHA-256 özeti.

İmzalanacak dize

canonical   = X-Timestamp + "\n" + METHOD + "\n" + path + "\n" + hex(sha256(raw body))
X-Signature = hex(HMAC-SHA256(key = API secret, message = canonical))   // 64 lowercase hex characters
  1. İstek gövdesini bir kez JSON olarak kodlayın ve tam olarak bu baytların SHA-256 özetini (küçük harfli onaltılık) hesaplayın. İstek gövdesi olmayan isteklerde boş dizenin özetini kullanın (e3b0c442…b855).

  2. Yolu URL’de göründüğü biçimiyle, /api/ ile başlayacak şekilde ve sorgu dizesi olmadan kullanın. Örneğin GET /api/payments/bin/540061?amount=100.00 isteğinde imzalanan yol /api/payments/bin/540061 olur.

  3. HMAC-SHA256 değerini, API gizli anahtarını anahtar olarak kullanarak hesaplayın. Gizli anahtar dizesini olduğu gibi kullanın; kod çözme (decode) işlemi uygulamayın.

  4. Sonucu X-Signature başlığında gönderin ve istek gövdesi olarak tam olarak özeti alınan baytları gönderin.

Zaman damgası toleransı

X-Timestamp, Payniko sunucu saatinden en fazla 300 saniye ileri veya geri olmalıdır. Sunucu saatinizi NTP ile senkronize tutun. Sapma bu değeri aşarsa istek 401 0055504 ile reddedilir ve detail alanında Payniko sunucu saati yer alır.

İmzalama örneği

Uygulamanızı test etmek için aşağıdaki örnek değerleri kullanın. Bu gizli anahtar, zaman damgası ve istek gövdesiyle kodunuzun birebir aynı imzayı üretmesi gerekir.

API gizli anahtarı (örnek)

e3c4a5b6d7e8f90112233445566778899aabbccddeeff0011223344556677889

İstek gövdesi (birebir baytlar)

{"merchant_order_id":"ORDER-2026-001","amount":"100.00","currency":"USD","webhook_url":"https://merchant.example/webhooks/payments","success_url":"https://merchant.example/payment/success","fail_url":"https://merchant.example/payment/failure","customer":{"email":"[email protected]","phone":"+37061234567"}}

sha256(body)

dca2bae6e50c13c26b22b57e3c31161ba3174147060fcbf22b2ba99cc13560ae

İmzalanacak dize

1793534400
POST
/api/payments/hosted
dca2bae6e50c13c26b22b57e3c31161ba3174147060fcbf22b2ba99cc13560ae

X-Signature

5522c27ff0b49ffa4c44712ed5db350c1c5a2cbfc0c568edbce39cff59ca620c

İstek başlıkları

POST /api/payments/hosted
X-Api-Key: api_0123456789abcdef0123456789abcdef
X-Merchant-Code: 123456
X-Timestamp: 1793534400
X-Signature: 5522c27ff0b49ffa4c44712ed5db350c1c5a2cbfc0c568edbce39cff59ca620c
Idempotency-Key: 6f1c2a9e-1d2b-4c1a-9f0e-3b7d5a2c8e41
Content-Type: application/json
Accept: application/json

İstek yardımcı fonksiyonları

Bu yardımcı fonksiyonlardan birini sunucunuza ekleyin. Fonksiyon API_KEY, API_SECRET ve MERCHANT_CODE değerlerini ortam değişkenlerinden okur, isteği imzalar ve gönderir. Diğer kod örnekleri bu yardımcı fonksiyonu kullanır.

#!/usr/bin/env bash
# payniko.sh - signed request helper (bash, curl, openssl).
# Usage: ./payniko.sh METHOD PATH [JSON_BODY] [IDEMPOTENCY_KEY]
# Reads API_KEY, API_SECRET and MERCHANT_CODE. The key decides sandbox or live.
# Optional: SANDBOX_SCENARIO adds the X-Sandbox-Scenario header (sandbox keys only).
set -euo pipefail
: "${API_KEY:?set API_KEY}" "${API_SECRET:?set API_SECRET}" "${MERCHANT_CODE:?set MERCHANT_CODE}"
BASE_URL="https://payniko.com"
method="$(printf '%s' "${1:?METHOD}" | tr '[:lower:]' '[:upper:]')"
target="${2:?PATH}"            # may include a query string; only the path is signed
body="${3-}"                   # sent byte-for-byte as hashed
idempotency_key="${4-}"
path="${target%%\?*}"
timestamp="$(date +%s)"
body_hash="$(printf '%s' "$body" | openssl dgst -sha256 | sed 's/^.* //')"
signature="$(printf '%s\n%s\n%s\n%s' "$timestamp" "$method" "$path" "$body_hash" \
  | openssl dgst -sha256 -hmac "$API_SECRET" | sed 's/^.* //')"
args=(-sS --fail-with-body -X "$method" "$BASE_URL$target"
  -H "X-Api-Key: $API_KEY" -H "X-Merchant-Code: $MERCHANT_CODE" -H "X-Timestamp: $timestamp"
  -H "X-Signature: $signature" -H "Accept: application/json" -H "Accept-Language: en")
if [ -n "$body" ]; then args+=(-H "Content-Type: application/json" --data-binary "$body"); fi
if [ -n "$idempotency_key" ]; then args+=(-H "Idempotency-Key: $idempotency_key"); fi
if [ -n "${SANDBOX_SCENARIO-}" ]; then args+=(-H "X-Sandbox-Scenario: $SANDBOX_SCENARIO"); fi
curl "${args[@]}"
echo
// payniko.mjs - signed request helper (Node.js 20+, no dependencies).
import {createHash, createHmac, randomUUID} from 'node:crypto';

const base = 'https://payniko.com';
const key = process.env.API_KEY ?? '';
const secret = process.env.API_SECRET ?? '';
const merchant = process.env.MERCHANT_CODE ?? '';
if (!/^api_[0-9a-f]{32}$/.test(key) || !/^[0-9a-f]{64}$/.test(secret) || !/^[0-9]{6}$/.test(merchant)) {
  throw new Error('Set API_KEY, API_SECRET and MERCHANT_CODE.');
}

export async function api(method, path, body, idempotencyKey, extraHeaders = {}) {
  const url = new URL(path, base);
  if (url.origin !== base || !url.pathname.startsWith('/api/')) throw new Error('Invalid API URL');
  method = method.toUpperCase();
  const raw = body === undefined ? '' : JSON.stringify(body);   // hash and send the same bytes
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const canonical = [timestamp, method, url.pathname,
    createHash('sha256').update(raw, 'utf8').digest('hex')].join('\n');
  const signature = createHmac('sha256', secret).update(canonical, 'utf8').digest('hex');
  const response = await fetch(url, {
    method,
    redirect: 'error',
    headers: {
      ...extraHeaders,
      'X-Api-Key': key, 'X-Merchant-Code': merchant,
      'X-Timestamp': timestamp, 'X-Signature': signature,
      'Accept': 'application/json', 'Accept-Language': 'en',
      ...(raw ? {'Content-Type': 'application/json'} : {}),
      ...(idempotencyKey ? {'Idempotency-Key': idempotencyKey} : {}),
    },
    ...(raw ? {body: raw} : {}),
  });
  const text = await response.text();
  const data = text ? JSON.parse(text) : null;
  if (!response.ok) {
    throw Object.assign(new Error(`${response.status} ${data?.code ?? ''} ${data?.detail ?? ''}`.trim()),
      {status: response.status, problem: data, correlationId: response.headers.get('x-correlation-id')});
  }
  return data;
}
export {randomUUID};
<?php
// payniko.php - signed request helper (PHP 8.1+ with the curl extension).
function payniko_api(string $method, string $path, ?array $body = null, ?string $idempotencyKey = null, array $extraHeaders = []): ?array
{
    $base = 'https://payniko.com';
    $key = getenv('API_KEY') ?: '';
    $secret = getenv('API_SECRET') ?: '';
    $merchant = getenv('MERCHANT_CODE') ?: '';
    if (!preg_match('/^api_[0-9a-f]{32}$/', $key) || !preg_match('/^[0-9a-f]{64}$/', $secret)
        || !preg_match('/^[0-9]{6}$/', $merchant)) {
        throw new RuntimeException('Set API_KEY, API_SECRET and MERCHANT_CODE.');
    }
    $method = strtoupper($method);
    // Hash and send the same bytes.
    $raw = $body === null ? '' : json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
    $timestamp = (string) time();
    $canonical = implode("\n", [$timestamp, $method, explode('?', $path, 2)[0], hash('sha256', $raw)]);
    $headers = array_merge($extraHeaders, [
        'X-Api-Key: ' . $key,
        'X-Merchant-Code: ' . $merchant,
        'X-Timestamp: ' . $timestamp,
        'X-Signature: ' . hash_hmac('sha256', $canonical, $secret),
        'Accept: application/json',
        'Accept-Language: en',
    ]);
    if ($raw !== '') $headers[] = 'Content-Type: application/json';
    if ($idempotencyKey !== null) $headers[] = 'Idempotency-Key: ' . $idempotencyKey;
    $ch = curl_init($base . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => $headers,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_TIMEOUT => 60,
    ]);
    if ($raw !== '') curl_setopt($ch, CURLOPT_POSTFIELDS, $raw);
    $response = curl_exec($ch);
    if ($response === false) throw new RuntimeException('Network error: ' . curl_error($ch));
    $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    $data = $response === '' ? null : json_decode($response, true);
    if ($status >= 400) {
        throw new RuntimeException($status . ' ' . ($data['code'] ?? '') . ' ' . ($data['detail'] ?? ''), $status);
    }
    return $data;
}
# payniko.py - signed request helper (Python 3.9+, standard library only).
import hashlib, hmac, json, os, re, time, urllib.error, urllib.request

BASE_URL = "https://payniko.com"
KEY = os.environ.get("API_KEY", "")
SECRET = os.environ.get("API_SECRET", "")
MERCHANT = os.environ.get("MERCHANT_CODE", "")
if not (re.fullmatch(r"api_[0-9a-f]{32}", KEY) and re.fullmatch(r"[0-9a-f]{64}", SECRET)
        and re.fullmatch(r"[0-9]{6}", MERCHANT)):
    raise SystemExit("Set API_KEY, API_SECRET and MERCHANT_CODE.")


def api(method, path, body=None, idempotency_key=None, extra_headers=None):
    method = method.upper()
    # Hash and send the same bytes.
    raw = b"" if body is None else json.dumps(body, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
    timestamp = str(int(time.time()))
    canonical = "\n".join([timestamp, method, path.split("?", 1)[0], hashlib.sha256(raw).hexdigest()])
    signature = hmac.new(SECRET.encode(), canonical.encode(), hashlib.sha256).hexdigest()
    headers = dict(extra_headers or {})
    headers.update({
        "X-Api-Key": KEY, "X-Merchant-Code": MERCHANT,
        "X-Timestamp": timestamp, "X-Signature": signature,
        "Accept": "application/json", "Accept-Language": "en",
    })
    if raw:
        headers["Content-Type"] = "application/json"
    if idempotency_key:
        headers["Idempotency-Key"] = idempotency_key
    request = urllib.request.Request(BASE_URL + path, data=raw or None, method=method, headers=headers)
    try:
        with urllib.request.urlopen(request, timeout=60) as response:
            text = response.read()
            return json.loads(text) if text else None
    except urllib.error.HTTPError as error:
        problem = json.loads(error.read() or b"{}")
        raise RuntimeError(f"{error.code} {problem.get('code')} {problem.get('detail')}") from None

Kurulumunuzu doğrulama

Bakiye sorgulaması herhangi bir kayıt oluşturmadığından ilk çağrı için güvenli bir seçenektir.

./payniko.sh GET '/api/account/balance'
import {api} from './payniko.mjs';

const result = await api('GET', '/api/account/balance');
console.log(result);
<?php
require __DIR__ . '/payniko.php';

$result = payniko_api('GET', '/api/account/balance');
print_r($result);
from payniko import api

result = api("GET", "/api/account/balance")
print(result)

Tekrarlanan geçersiz imzalar anahtarı geçici olarak kilitler: aynı adresten 15 dakika içinde 20 hata anahtarı 15 dakika, bir saat içinde 300 hata ise bir saat kilitler (Retry-After ile 429 1000005). Yeniden denemeden önce imzalama mantığını düzeltin.

Idempotency-Key

Her POST isteği bir Idempotency-Key gerektirir. Aynı anahtarla yeniden gönderilen bir istek yalnızca bir kez yürütülür ve ilk sonucu döndürür; bu sayede zaman aşımından sonra yapılan yeniden denemeler güvenli olur.

  • Her yeni işlem için UUID gibi rastgele yeni bir anahtar üretin ve isteği göndermeden önce bu anahtarı siparişinizle birlikte saklayın.
  • Zaman aşımı veya ağ hatasından sonra aynı istek gövdesini aynı anahtarla yeniden gönderin.
  • Bir anahtarı hiçbir zaman farklı bir istek gövdesiyle yeniden kullanmayın; böyle bir istek 409 ile reddedilir (9000010, Direct API’de 0055503).
  • İlk istek hâlâ işleniyorsa yanıt 409 olur: Hosted API'de 9000039 (integration.retryable: true ile), Direct API'de metadata.retry_class: retry_same_request ile 0055503. Kısa bir bekleme sonrası aynı isteği yeniden gönderin.
  • Biçim: A-Z a-z 0-9 _ - karakterlerinden oluşan 8–128 karakter. Eksik veya geçersiz bir anahtar 422 1000009 yanıtını döndürür.
  • İmzalı bir istek (X-Timestamp ve X-Signature) 10 dakika boyunca birlikte gönderildiği ilk Idempotency-Key değerine bağlanır; aynı imza farklı bir anahtarla 409 9000010 ile reddedilir. Her yeni işlem için yeni bir zaman damgası ve imza üretin. Bu denetim geçici olarak kullanılamıyorsa yanıt 503 1000011 olur.

İstek gereksinimleri

  • Yalnızca dokümantasyonda tanımlanan alanları gönderin. Bilinmeyen alanlar 422 1000001 ile reddedilir.
  • POST istekleri sorgu dizesi içermemelidir. GET istekleri istek gövdesi içermemelidir ve yalnızca dokümantasyonda tanımlanan sorgu parametrelerini kabul eder.
  • İstek gövdesi tek bir JSON nesnesi olmalı ve en dıştaki nesne dahil en fazla 7 düzey iç içe yapı içermelidir.
  • Hız sınırları IP adresi başına (kimlik doğrulamadan önce dakikada 600 istek) ve anahtar başına (dakikada 300 istek) uygulanır. 429 (1000005) durumunda Retry-After süresi kadar bekleyin, ardından artan aralıklarla yeniden deneyin. Her yanıt X-RateLimit-Limit ve X-RateLimit-Remaining başlıklarını taşır.
BaşlıkAnlam
X-Correlation-Idİsteğin benzersiz ID’si. Bu değeri log kayıtlarınıza yazın ve destek taleplerinize ekleyin.
Retry-AfterBeklenmesi gereken süre (saniye). 429 yanıtlarında her zaman, 503 yanıtlarında ise biliniyorsa yer alır.
X-RateLimit-LimitGeçerli pencerenin istek sınırı. X-RateLimit-Reset başlığı yoktur.
X-RateLimit-RemainingGeçerli zaman aralığında kalan istek sayısı.
Content-LanguageYanıt mesajlarının dili.

Kimlik doğrulama hataları

KodHTTPAnlamÖnerilen işlem
0055502401 / 403Erişim reddedildi. 401: X-Api-Key veya X-Merchant-Code eksik, bir başlık yinelenmiş veya hatalı biçimlendirilmiş, anahtar tanınmıyor veya iptal edilmiş, üye işyeri kodu eşleşmiyor ya da X-Environment gönderilmiş ancak anahtarla uyuşmuyor. 403: hesap veya Direct API erişimi etkin değil.Başlıkları ve API anahtarını doğrulayın. İsteği otomatik olarak yeniden denemeyin.
0055504401X-Timestamp veya X-Signature eksik ya da hatalı biçimlendirilmiş veya sunucu saatinizle Payniko sunucu saati arasındaki fark 300 saniyeyi aşıyor.Sunucu saatinizi (NTP) düzeltin ve isteği güncel bir zaman damgasıyla yeniden imzalayın.
0055505401İmza istekle eşleşmiyor.Özeti, gönderdiğiniz istek gövdesinin baytları üzerinden birebir hesaplayın, yolu sorgu dizesi olmadan imzalayın ve doğru API gizli anahtarını kullanın.
9000002401İstek işleme alındığında geçerlilik süresi dolmuştu.İsteği güncel bir zaman damgasıyla yeniden imzalayın ve tekrar gönderin.
1000009422Idempotency-Key eksik veya geçersiz.Her POST isteğinde A-Z a-z 0-9 _ - karakterlerinden oluşan, 8–128 karakter uzunluğunda bir anahtar gönderin.
9000010409Bu Idempotency-Key daha önce farklı bir istek için kullanılmış ya da aynı imzalı istek farklı bir anahtarla yeniden gönderilmiş.İsteği aynı anahtar ve aynı istek gövdesiyle yeniden deneyin; yeni anahtarı yalnızca yeni bir işlem için kullanın.
1000005429Çok fazla istek gönderildi ya da art arda gelen imza hatalarının ardından geçici bir kilitleme uygulandı.Retry-After başlığında belirtilen süre kadar bekleyin, ardından artan bekleme aralıklarıyla yeniden deneyin.

Hosted API

Ödemeyi sunucunuz oluşturur ve müşteriyi Payniko ödeme sayfasına yönlendirir. Kart bilgilerini Payniko alır ve 3-D Secure doğrulamasını gerçekleştirir; böylece kart verileri sistemlerinize hiçbir zaman ulaşmaz. Hosted API, en basit entegrasyon seçeneğidir.

Entegrasyon adımları

  1. Ödemeyi oluşturma

    Sunucunuz POST /api/payments/hosted çağrısını yapar. Yanıtta dönen payment_id değerini siparişinizle birlikte saklayın.

  2. Müşteriyi yönlendirme

    Tarayıcıyı next_action.url adresine yönlendirin veya bu adresi yeni bir sekmede açın; adresi hiçbir zaman iframe içinde yüklemeyin.

  3. Müşterinin ödemeyi tamamlaması

    Müşteri kart bilgilerini girer. Banka 3-D Secure doğrulaması isterse sayfa bu adımı gösterir. Bu aşamadaki ara bir ret nihai değildir; ödeme, sizin herhangi bir işlem yapmanıza gerek kalmadan nihai bir duruma ulaşır.

  4. Müşterinin geri dönmesi

    Sayfa sonucu kısa bir geri sayımla gösterir, ardından tarayıcıyı imzalı parametrelerle success_url adresine (yalnızca ödeme succeeded durumundaysa) veya fail_url adresine yönlendirir. Bkz. Dönüş URL’si.

  5. Sonucu doğrulama

    Webhook’u bekleyin veya GET /api/payments/{payment_id} çağrısını yapın. Siparişi yalnızca durum succeeded olduğunda teslim edin.

Ödeme akışı

Your server                          Payniko                        Payer's browser
 1  POST /api/payments/hosted  ─────►  201  status: requires_action
                               ◄─────  next_action.url
 2  redirect (303) ───────────────────────────────────────────────►  payment page
 3                                     card + 3-D Secure  ◄────────  payer pays
 4                                     ───────────────────────────►  success_url / fail_url
                                                                     (signed parameters)
 5  webhook  ◄───────────────────────  payment.succeeded / payment.failed
    GET /api/payments/{payment_id} ──►  current status
POST/api/payments/hostedIdempotency-Key zorunlu

Ödeme oluşturma

Yeni ödemeyle birlikte 201 döndürür. Aynı istek aynı Idempotency-Key ile yeniden gönderilirse aynı ödemeyle birlikte 200 döner.

İstek gövdesi

AlanTürZorunluAçıklamaÖrnek
merchant_order_idstringZorunluSipariş referansınız: A-Z a-z 0-9 . _ - karakterlerinden oluşan 1–64 karakter. Her referans, her ortamda yalnızca bir ödeme için kullanılabilir; bu kural ödeme başarısız olsa veya süresi dolsa da geçerlidir. Benzersizlik denetiminde büyük/küçük harf ayrımı yapılmaz: ORDER-1 ve order-1 aynı referans olarak değerlendirilir.ORDER-2026-001
amountstringZorunluBaşında sıfır olmayan, iki ondalık basamaklı pozitif tutar; örneğin "100.00". Hesabınızın tutar sınırları içinde olmalıdır."100.00"
currencystringİsteğe bağlıGönderilmezse sistemin ana para birimi uygulanır.
USDEURAEDTRY
USD
webhook_urlstring · uriZorunluBu ödemenin webhook'larını alan uç nokta. 443 portunda herkese açık HTTPS, en fazla 500 karakter, kullanıcı bilgisi ve parça (fragment) olmadan. Özel ve yerel adresler sandbox dahil kabul edilmez; alan adı istek anında herkese açık bir IP adresine çözülmelidir, aksi halde 422 9000008.https://merchant.example/webhooks/payments
success_urlstring · uriZorunluBaşarılı bir ödemeden sonra tarayıcının yönlendirileceği adres. HTTPS, en fazla 2048 karakter; kullanıcı bilgisi (user info) ve parça (fragment) içermemelidir. Sandbox ortamı http://localhost, 127.0.0.1 ve [::1] adreslerini de kabul eder.https://merchant.example/payment/success
fail_urlstring · uriZorunluÖdeme başarısız olduğunda, süresi dolduğunda veya iptal edildiğinde tarayıcının yönlendirileceği adres. success_url için geçerli kurallar burada da uygulanır.https://merchant.example/payment/failure
customerobjectZorunluÖdeyenin iletişim bilgileri; ödeme sonucu, makbuz ve bankanın doğrulama adımları için kullanılır. Yalnızca email ve phone kabul edilir; customer içindeki başka bir alan 422 1000001 ile reddedilir.{ … }
customer.emailstringZorunluÖdeyenin gerçek e-posta adresi: RFC 5322, en fazla 254 karakter, noktalı alan adı. Küçük harfe çevrilerek saklanır.[email protected]
customer.phonestringZorunluÖdeyenin gerçek telefon numarası, uluslararası biçimde (+ ülke kodu, E.164), örneğin +37061234567. Türkiye'ye özgü kural: +90 ve 10 haneli ulusal numara (5xx cep, 2xx, 3xx, 4xx sabit, 8xx özel). Ülke kodu verilmeyen numara Türkiye numarası (+90) sayılır.+37061234567

Bu üç URL yalnızca bu ödeme için geçerlidir ve panel ayarlarınızdan alınmaz.

Ödeme sayfası ödeyenden e-posta adresini ve telefon numarasını yeniden istemez. Gizlilik gereği customer ödeme nesnesinde ve webhook'larda döndürülmez.

AlanNedenerrors içindeki mesaj
customer.emailBiçim geçersiz veya 254 karakterden uzun.customer.email must be a valid e-mail address (max 254 characters).
customer.emailYer tutucu, örnek veya tek kullanımlık adres.customer.email must be the payer's real address; placeholder, example or disposable addresses (example.com, test@…, mailinator…) are not accepted.
customer.phoneUluslararası biçimde geçerli bir numara değil.customer.phone must be a valid phone number in international format (e.g. +37061234567).
customer.phoneYinelenen veya ardışık rakam deseni.customer.phone must be the payer's real number; repeated or sequential digit patterns (5555555555, 1234567890) are not accepted.
customer.phoneTürkiye numarası +90 ve 2, 3, 4, 5 veya 8 ile başlayan 10 hane biçiminde değil.customer.phone for Türkiye must be +90 followed by a 10-digit number starting with 2, 3, 4, 5 or 8 (e.g. +905321234567).

HTTP 422 · 1000001

{
  "type": "https://payniko.com/problems/1000001",
  "title": "Validation failed",
  "status": 422,
  "detail": "The submitted fields are not valid.",
  "code": "1000001",
  "correlation_id": "4c7a1e9d-2b3f-4d85-9a6e-0f1b2c3d4e5f",
  "errors": {
    "customer.email": [
      "customer.email must be the payer's real address; placeholder, example or disposable addresses (example.com, test@…, mailinator…) are not accepted."
    ],
    "customer.phone": [
      "customer.phone must be the payer's real number; repeated or sequential digit patterns (5555555555, 1234567890) are not accepted."
    ]
  }
}

İstek customer yüzünden reddedilirse nedeni ödeyene açıkça gösterin (örneğin: kendi e-posta adresinizi veya telefon numaranızı girin) ve ödemeyi yeniden oluşturmadan önce alanı düzeltmesine izin verin. errors içindeki mesajlar İngilizcedir; günlükleriniz ve destek için tasarlanmıştır.

Payniko ödeme linki sayfaları, ödeyenin orada girdiği e-posta adresi ve telefon numarasına aynı kuralları uygular ve ret nedenini ödeyene gösterir.

İstek gövdesi · Örnek

{
  "merchant_order_id": "ORDER-2026-001",
  "amount": "100.00",
  "currency": "USD",
  "webhook_url": "https://merchant.example/webhooks/payments",
  "success_url": "https://merchant.example/payment/success",
  "fail_url": "https://merchant.example/payment/failure",
  "customer": {
    "email": "[email protected]",
    "phone": "+37061234567"
  }
}
KEY="$(openssl rand -hex 16)"  # store with the order before sending; reuse it after network errors
./payniko.sh POST '/api/payments/hosted' '{
  "merchant_order_id": "ORDER-2026-001",
  "amount": "100.00",
  "currency": "USD",
  "webhook_url": "https://merchant.example/webhooks/payments",
  "success_url": "https://merchant.example/payment/success",
  "fail_url": "https://merchant.example/payment/failure",
  "customer": {
    "email": "[email protected]",
    "phone": "+37061234567"
  }
}' "$KEY"
import {api, randomUUID} from './payniko.mjs';

const key = randomUUID(); // Store this key with the order before sending; reuse it after network errors.
const payment = await api('POST', '/api/payments/hosted', {
  "merchant_order_id": "ORDER-2026-001",
  "amount": "100.00",
  "currency": "USD",
  "webhook_url": "https://merchant.example/webhooks/payments",
  "success_url": "https://merchant.example/payment/success",
  "fail_url": "https://merchant.example/payment/failure",
  "customer": {
    "email": "[email protected]",
    "phone": "+37061234567"
  }
}, key);
console.log(payment);
<?php
require __DIR__ . '/payniko.php';

$key = bin2hex(random_bytes(16)); // store with the order; reuse it after network errors
$payment = payniko_api('POST', '/api/payments/hosted', [
    "merchant_order_id" => 'ORDER-2026-001',
    "amount" => '100.00',
    "currency" => 'USD',
    "webhook_url" => 'https://merchant.example/webhooks/payments',
    "success_url" => 'https://merchant.example/payment/success',
    "fail_url" => 'https://merchant.example/payment/failure',
    "customer" => [
        "email" => '[email protected]',
        "phone" => '+37061234567',
    ],
], $key);
print_r($payment);
from payniko import api
import uuid

key = str(uuid.uuid4())  # store with the order; reuse it after network errors
payment = api("POST", "/api/payments/hosted", {
    "merchant_order_id": "ORDER-2026-001",
    "amount": "100.00",
    "currency": "USD",
    "webhook_url": "https://merchant.example/webhooks/payments",
    "success_url": "https://merchant.example/payment/success",
    "fail_url": "https://merchant.example/payment/failure",
    "customer": {
        "email": "[email protected]",
        "phone": "+37061234567"
    }
}, key)
print(payment)

Yanıt · 201

status: requires_action ve next_action.url içeren { "data": Payment } nesnesini döndürür.

Yanıt · Örnek

{
  "data": {
    "payment_id": "LUVEI-PAYN-123456",
    "merchant_order_id": "ORDER-2026-001",
    "flow": "hosted",
    "status": "requires_action",
    "amount": "100.00",
    "currency": "USD",
    "environment": "sandbox",
    "next_action": {
      "type": "redirect",
      "url": "https://payniko.com/l/example-checkout",
      "expires_at": "2026-11-02T12:00:00.000Z"
    },
    "card": null,
    "failure": null,
    "created_at": "2026-11-01T12:00:00.000Z",
    "expires_at": "2026-11-02T12:00:00.000Z",
    "completed_at": null
  }
}

Diğer yanıtlar

  • 200: aynı anahtar ve istek gövdesi yeniden gönderildi. İlk ödeme döndürülür; yeni ödeme oluşturulmaz.
  • 409 9000010: bu anahtar daha önce farklı bir istek gövdesiyle kullanılmış.
  • 409 9000039: bu anahtarla gönderilen ilk istek hâlâ işleniyor. Kısa bir bekleme sonrası aynı isteği yeniden gönderin.
  • 409 9000007: bu merchant_order_id için zaten bir ödeme mevcut.
  • 422 9000005: tutar, hesabınızın tutar sınırlarının dışında; izin verilen aralık limits alanında yer alır.
  • 422 1000001 ve errors içinde customer, customer.email veya customer.phone: iletişim bilgileri eksik veya geçersiz.
  • 422 1000001, 1000020, 1000021, 9000008, 9000038: bir alan geçersiz; ayrıntılar errors ve integration.violations alanlarında yer alır.

HTTP 422 · 9000005

{
  "type": "https://payniko.com/problems/9000005",
  "title": "Invalid amount",
  "status": 422,
  "detail": "The amount is outside the account limits.",
  "code": "9000005",
  "correlation_id": "6d1f0c2e-8a4b-4c7d-9e21-3f5a7b9c1d20",
  "limits": {
    "currency_code": "USD",
    "min_amount": "25.00",
    "max_amount": null
  }
}

Tutar sınırları, tutarınız sistemin ana para birimine (limits.currency_code) çevrildikten sonra bu para biriminde değerlendirilir. max_amount: null değeri üst sınır olmadığını belirtir. Örnekteki sınır değerleri yalnızca açıklama amaçlıdır.

Ödeme nesnesi

Oluşturma ve sorgulama çağrıları bu nesneyi data içinde döndürür; her ödeme webhook’u da bu nesneyi içerir. Nesnenin yapısı Hosted API ve Direct API için aynıdır.

AlanTürAçıklama
payment_idstring · LUVEI-ABCD-123456Ödemenin Payniko ID’si. Bu değeri siparişinizle birlikte saklayın.
merchant_order_idstringGönderdiğiniz şekliyle sipariş referansınız.
flowstringHosted API için hosted, Direct API için direct.
hosteddirect
statusstringÖdemenin güncel durumu; aşağıdaki durum tablosuna bakın.
requires_actionprocessingsucceededfailedexpiredcancelled
amountstringİki ondalık basamaklı tutar.
currencystringTutarın para birimi; para birimi belirtilmediyse sistemin ana para birimi.
environmentstringÖdemeyi oluşturan anahtarın ortamı.
sandboxlive
next_actionobject | nullMüşteriden beklenen bir sonraki işlem. Yalnızca status değeri requires_action iken doldurulur.
next_action.typestringHer zaman redirect.
next_action.urlstring · uriBu URL’yi müşterinin tarayıcısında, döndüğü şekliyle açın. URL’yi hiçbir zaman kendiniz oluşturmayın veya değiştirmeyin.
next_action.expires_atstring · date-timeURL’nin kullanılabileceği son zaman. Direct API’de bu değer 3-D Secure adımının bitişidir ve ödemenin kendi expires_at değerinden önce olabilir; Hosted API’de ise ödemenin son geçerlilik zamanıdır. Bu süre dolduğunda ödeme bir sonraki durumuna geçer ve next_action alanı null olur.
cardobject | nullMaskelenmiş kart bilgileri; belirlendiğinde doldurulur.
card.bin6string | nullİlk altı hane.
card.last_fourstring | nullSon dört hane.
card.networkstring | nullKart markası.
visamastercardamextroyunionpaydiscoverjcbdinersmaestro
card.bankstring | nullKartı çıkaran banka (biliniyorsa).
failureobject | nullBaşarısızlık nedeni. Yalnızca status değeri failed olduğunda doldurulur.
failure.codestringİş mantığınızda kullanılacak neden kodu; aşağıdaki listeye bakın.
failure.messagestringMüşteriye düz metin olarak gösterilmeye uygun kısa mesaj.
created_atstring · date-timeÖdemenin oluşturulduğu zaman.
expires_atstring · date-timeÖdemenin tamamlanması için son zaman.
completed_atstring · date-time | nullÖdemenin nihai bir duruma ulaştığı zaman.

next_action, requires_action durumunda bile kısa bir süre null olabilir; bu durumda ödemeyi yeniden sorgulayın. Kart numarasının tamamı, CVV, son kullanma tarihi ve kart sahibinin adı hiçbir zaman döndürülmez. card.network ve card.bank bilinmediğinde null olabilir.

Ödeme oluşturulurken gönderilen customer bilgileri bu nesnenin parçası değildir ve webhook'larda yer almaz.

Ödeme · Örnek

{
  "data": {
    "payment_id": "LUVEI-PAYN-123456",
    "merchant_order_id": "ORDER-2026-001",
    "flow": "hosted",
    "status": "requires_action",
    "amount": "100.00",
    "currency": "USD",
    "environment": "sandbox",
    "next_action": {
      "type": "redirect",
      "url": "https://payniko.com/l/example-checkout",
      "expires_at": "2026-11-02T12:00:00.000Z"
    },
    "card": null,
    "failure": null,
    "created_at": "2026-11-01T12:00:00.000Z",
    "expires_at": "2026-11-02T12:00:00.000Z",
    "completed_at": null
  }
}

Hosted ödeme durumları

DurumAnlamÖnerilen işlem
requires_actionÖdeme oluşturuldu; müşteri ödemeyi henüz tamamlamadı.Müşteriyi next_action.url adresine yönlendirin (next_action.expires_at zamanına kadar).
processingMüşteri ödemeyi yaptı; bankanın yanıtı bekleniyor.Webhook'u bekleyin ve yeni bir ödeme oluşturmayın. Banka hiç yanıt vermezse ödeme mutabakata kadar processing durumunda kalabilir ve sonra failed ya da expired olarak biter.
succeededTutar tahsil edildi.Siparişi teslim edin.
failedÖdeme tamamlanmadı. Nedeni failure alanında yer alır.failure.message değerini gösterin. Yeniden denemek için yeni bir merchant_order_id ile yeni bir ödeme oluşturun.
expiredMüşteri ödemeyi 24 saat içinde (expires_at) tamamlamadı. Müşteri ödemeyi yarıda bırakırsa ödeme bunun yerine failed durumuyla sonuçlanabilir.Müşteri hâlâ ödeme yapmak istiyorsa yeni bir ödeme oluşturun.
cancelledÖdeme iptal edildi.Siparişi teslim etmeyin.
  • Ödeme sayfasındaki ara bir ret nihai değildir: ödeme requires_action durumunda kalabilir ve payment.updated olayını alabilirsiniz. Sonuçta tek bir nihai sonuç alırsınız.
  • succeeded, failed, expired ve cancelled nihai durumlardır. processing durumundaki bir ödeme, expires_at zamanı geçtikten sonra da başarıyla sonuçlanabilir.
  • Burada listelenmeyen her durumu processing olarak değerlendirin ve ödemeyi yeniden sorgulayın.

Başarısızlık kodları

failed durumundaki bir ödeme failure.code ve failure.message alanlarını içerir. Kodu iş mantığınızda kullanın, mesajı ise müşteriye düz metin olarak gösterin.

KodAnlam
payment_declinedBanka ödemeyi reddetti.
do_not_honorBanka ödemeyi gerekçe belirtmeden reddetti.
insufficient_fundsKart bakiyesi yetersiz.
card_limit_reachedKart limitlerinden birine ulaşıldı.
card_details_invalidKart bilgileri geçersiz.
card_expiry_invalidSon kullanma tarihi geçersiz veya kartın süresi dolmuş.
card_security_code_invalidCVV geçersiz.
card_not_activeKart bloke edilmiş veya etkin değil.
card_brand_not_supportedBu kart markası desteklenmiyor.
three_ds_failed3-D Secure doğrulaması başarısız oldu veya tamamlanmadı.
payment_not_permittedKartın bu tür ödemelerde kullanılmasına izin verilmiyor.
amount_not_allowedBu kart için bu tutara izin verilmiyor.
currency_not_supportedBu para birimi bu kart için desteklenmiyor.
payment_details_rejectedBanka ödeme bilgilerini reddetti.
duplicate_paymentBanka ödemeyi mükerrer işlem olarak tespit etti.
payment_timed_outÖdeme öngörülen süre içinde tamamlanmadı.
bank_unavailableBankaya ulaşılamadı.
bank_requested_retryBanka, işlemin daha sonra yeniden denenmesini talep etti.
service_unavailableÖdeme şu anda işlenemedi.

Yeni kodlar eklenebilir; bilinmeyen kodları payment_declined olarak değerlendirin. failure.message bankanın kendi metnini içerebilir; aksi takdirde webhook’larda İngilizcedir, GET çağrılarında ise Accept-Language başlığına göre belirlenir.

Yönlendirme gereksinimleri

  • Sayfanın tamamını next_action.url adresine yönlendirin (303 veya window.location) ya da adresi yeni bir sekmede açın. Adresi iframe içine yerleştirmeyin; sayfa, çerçeve içinde gösterimi engeller.
  • URL’yi next_action.expires_at zamanından önce kullanın. Süre dolduktan sonra ödemeyi sorgulayın.
  • Sayfa dili müşterinin tarayıcı ayarlarına göre belirlenir; herhangi bir parametre gönderilmesi gerekmez.

Dönüş URL’si

Müşteri geri döndüğünde tarayıcı, yalnızca ödeme succeeded durumundaysa success_url adresine, diğer tüm durumlarda fail_url adresine yönlendirilir. URL’ye imzalı parametreler eklenir. Doğru sayfayı göstermek için bu parametreleri doğrulayın; siparişi ise yalnızca webhook veya bir GET çağrısı succeeded durumunu teyit ettikten sonra teslim edin.

AlanAçıklama
payment_idÖdeme ID’si .
merchant_order_idSipariş referansınız. İmzalı değildir: yalnızca siparişi bulmak için kullanın; eşleştirmeyi payment_id üzerinden yapın.
statusYönlendirme anındaki ödeme durumu, GET ile aynı değerlerle. success_url üzerinde her zaman succeeded. fail_url üzerinde genellikle failed, expired veya cancelled; Hosted API'de ödeyen ödeme nihai duruma ulaşmadan ayrıldıysa (örneğin "Mağazaya dön" ile) requires_action veya processing olabilir.
failure_codeYalnızca status değeri failed olduğunda yer alır; failure.code ile aynıdır.
tsDönüş bağlantısının oluşturulduğu Unix zamanı. Müşteri bağlantıyı daha sonra açabileceğinden eski bir ts değeri tek başına sahtecilik göstergesi değildir.
key_versionBağlantıyı imzalayan anahtarı belirtir: ilgili ortamda oluşturulan ilk anahtar için 1, sonraki anahtar için 2 ve bu şekilde devam eder. Bir ödeme için bu değer hiçbir zaman değişmez.
sig64 karakterlik, küçük harfli onaltılık (hex) HMAC-SHA256 değeri.

İmzalanacak dize

ts + "." + payment_id + "." + status + "." + failure_code   // failure_code empty: keep the last dot
  1. key_version ile belirtilen anahtarın gizli anahtarını seçin. Anahtarlar sonradan değiştirilmiş olsa bile bu, ödemeyi oluşturan anahtardır.

  2. Dizeyi, URL kod çözümü (URL-decode) uygulanmış değerlerden oluşturun. failure_code yoksa sondaki noktayı koruyun.

  3. HMAC-SHA256 değerini hesaplayın ve sig ile sabit zamanlı olarak karşılaştırın. payment_id değerinin size ait ödemelerden birine karşılık geldiğini doğrulayın.

  4. Siparişi teslim etmeden önce ödemeyi GET /api/payments/{payment_id} ile sorgulayın (veya webhook’u bekleyin).

  • Kendi sorgu parametreleriniz korunur. URL’de yukarıdaki adlardan birine sahip bir parametre zaten varsa bu parametre değiştirilir.
  • Bağlantı geçici bir hata nedeniyle imzalanamadıysa URL bu parametrelerin hiçbirini içermez. Bu durumda sonucu bilinmiyor olarak kabul edin ve ödemeyi sorgulayın.
// Node.js 20+: check the signed parameters on success_url / fail_url.
import {createHmac, timingSafeEqual} from 'node:crypto';

// query: URLSearchParams of the return URL.
// secretFor(keyVersion): the API secret of that key, or undefined if you do not know it.
export function verifyReturn(query, secretFor) {
  const sig = query.get('sig') ?? '';
  const secret = secretFor(query.get('key_version'));
  if (!secret || !/^[0-9a-f]{64}$/.test(sig)) return false;
  const signed = [query.get('ts') ?? '', query.get('payment_id') ?? '',
    query.get('status') ?? '', query.get('failure_code') ?? ''].join('.');
  const expected = createHmac('sha256', secret).update(signed, 'utf8').digest();
  return timingSafeEqual(expected, Buffer.from(sig, 'hex'));
}
// Even a valid return is not proof of payment: read GET /api/payments/{payment_id} before you deliver.
<?php
// PHP 8.1+: check the signed parameters on success_url / fail_url.
// $secretFor(string $keyVersion): the API secret of that key, or null if you do not know it.
function payniko_verify_return(array $query, callable $secretFor): bool
{
    $sig = (string) ($query['sig'] ?? '');
    $secret = $secretFor((string) ($query['key_version'] ?? ''));
    if (!$secret || !preg_match('/^[0-9a-f]{64}$/', $sig)) return false;
    $signed = implode('.', [(string) ($query['ts'] ?? ''), (string) ($query['payment_id'] ?? ''),
        (string) ($query['status'] ?? ''), (string) ($query['failure_code'] ?? '')]);
    return hash_equals(hash_hmac('sha256', $signed, $secret), $sig);
}

// $ok = payniko_verify_return($_GET, fn (string $v) => $secretsByKeyVersion[$v] ?? null);
// Even a valid return is not proof of payment: read GET /api/payments/{payment_id} before you deliver.
# Python 3.9+: check the signed parameters on success_url / fail_url.
import hashlib, hmac, re


def verify_return(query, secret_for) -> bool:
    # query: dict-like (for example Flask request.args); secret_for(key_version) -> secret or None
    sig = query.get("sig", "")
    secret = secret_for(query.get("key_version"))
    if not secret or not re.fullmatch(r"[0-9a-f]{64}", sig):
        return False
    signed = ".".join([query.get("ts", ""), query.get("payment_id", ""),
                       query.get("status", ""), query.get("failure_code", "")])
    expected = hmac.new(secret.encode(), signed.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

# Even a valid return is not proof of payment: read GET /api/payments/{payment_id} before you deliver.

Dönüş URL’leri · Örnek

https://merchant.example/payment/success?payment_id=LUVEI-PAYN-654321&merchant_order_id=ORDER-2026-001&status=succeeded&ts=1793001234&key_version=1&sig=200e8a5d4a7a03ead362a2c076408f9d8764f8f636f58bd4c17d78a50d59eb8a
string to sign: 1793001234.LUVEI-PAYN-654321.succeeded.

https://merchant.example/payment/failure?payment_id=LUVEI-PAYN-654321&merchant_order_id=ORDER-2026-001&status=failed&failure_code=insufficient_funds&ts=1793001234&key_version=1&sig=1cd0a104ec4ec4f5b2933d48d14c0eb33d949dd7f27936a067ab8f68b413b70d
string to sign: 1793001234.LUVEI-PAYN-654321.failed.insufficient_funds

secret used in this example (fake): example_secret_not_real
GET/api/payments/{payment_id}

Ödeme sorgulama

Güncel durumu içeren { "data": Payment } nesnesini döndürür. Sorgulama ödemede hiçbir değişiklik yapmadığından bu uç noktayı gerektiği sıklıkta çağırabilirsiniz: müşteri geri döndükten sonra, bir webhook tutarsız göründüğünde veya zaman aşımından sonra.

Parametreler

AlanTürZorunluAçıklama
payment_idstring · pathZorunluÖdeme oluşturulurken döndürülen payment_id. Aynı ortama ait her anahtarla bu ödeme sorgulanabilir.
./payniko.sh GET '/api/payments/LUVEI-PAYN-123456'
import {api} from './payniko.mjs';

const payment = await api('GET', '/api/payments/LUVEI-PAYN-123456');
console.log(payment);
<?php
require __DIR__ . '/payniko.php';

$payment = payniko_api('GET', '/api/payments/LUVEI-PAYN-123456');
print_r($payment);
from payniko import api

payment = api("GET", "/api/payments/LUVEI-PAYN-123456")
print(payment)

Olası hatalar

Ödeme webhook’ları

Aşağıdaki olaylar ödemenin webhook_url adresine gönderilir. Her ödeme olayı, bir ödeme için en fazla bir kez gönderilir.

OlayAnlam
payment.succeededÖdemenin durumu succeeded. Siparişi teslim edin.
payment.failedÖdeme başarısız sonuçlandı: failed, expired veya cancelled. Bkz. data.status.
payment.updatedMüşterinin işlem yapması hâlâ gerekiyor (requires_action); örneğin ara bir ret sonrasında. Bu nihai bir sonuç değildir.
refund.* · payment.voided · dispute.*Bu ödemeye ilişkin sonraki olaylar (iadeler, iptaller, itirazlar). Bkz. Webhook’lar.

payment.succeeded

{
  "event_id": "e0743f4c-a297-4bf0-8085-ceb7d9d5e181",
  "event_type": "payment.succeeded",
  "environment": "sandbox",
  "occurred_at": "2026-11-01T12:02:00.000Z",
  "data": {
    "payment_id": "LUVEI-PAYN-123456",
    "merchant_order_id": "ORDER-2026-001",
    "flow": "hosted",
    "status": "succeeded",
    "amount": "100.00",
    "currency": "USD",
    "environment": "sandbox",
    "next_action": null,
    "card": {
      "bin6": "400000",
      "last_four": "0002",
      "network": "visa",
      "bank": null
    },
    "failure": null,
    "created_at": "2026-11-01T12:00:00.000Z",
    "expires_at": "2026-11-02T12:00:00.000Z",
    "completed_at": "2026-11-01T12:02:00.000Z"
  }
}

Her webhook’u doğrulayın: ödemeyi oluşturan anahtarın gizli anahtarıyla hesaplanan X-Timestamp + "." + raw body dizesinin HMAC-SHA256 değeri X-Signature ile aynı olmalı ve X-Timestamp 300 saniye içinde olmalıdır. Her event_id değerini bir kez kaydedin ve vakit kaybetmeden 2xx koduyla yanıt verin. Kod örnekleri ve yeniden deneme davranışı için bkz. Webhook’lar.

Hatalar

KodHTTPAnlamÖnerilen işlem
1000001422İstek geçersiz: bilinmeyen bir alan, geçersiz bir biçim, POST isteğinde sorgu dizesi, desteklenmeyen bir sorgu parametresi veya JSON nesnesi olmayan bir istek gövdesi. İlgili alanlar errors içinde listelenir.Listelenen alanları düzeltin.
1000009422Idempotency-Key eksik veya geçersiz.Her POST isteğinde A-Z a-z 0-9 _ - karakterlerinden oluşan, 8–128 karakter uzunluğunda bir anahtar gönderin.
9000010409Bu Idempotency-Key daha önce farklı bir istek için kullanılmış ya da aynı imzalı istek farklı bir anahtarla yeniden gönderilmiş.İsteği aynı anahtar ve aynı istek gövdesiyle yeniden deneyin; yeni anahtarı yalnızca yeni bir işlem için kullanın.
9000039409Bu Idempotency-Key ile gönderilen ilk istek hâlâ işleniyor (Hosted API).Kısa bir bekleme sonrası aynı isteği yeniden gönderin.
9000007409Bu merchant_order_id için zaten bir ödeme mevcut (Hosted API).Mevcut ödemeyi sorgulayın veya yeni bir sipariş referansı kullanın.
9000005422Tutar, hesabınızın tutar sınırlarının dışında (Hosted API). İzin verilen aralık limits alanında yer alır.Tutarı güncelleyin.
1000020422Tutar biçimi geçersiz.Tutarı, başında sıfır olmayan, iki ondalık basamaklı pozitif bir dize olarak gönderin; örneğin "100.00".
1000021422Para birimi desteklenmiyor.USD, EUR, AED veya TRY kullanın ya da currency alanını göndermeyin.
9000008422webhook_url geçersiz.443 portu üzerinden erişilebilen, en fazla 500 karakterlik, herkese açık bir HTTPS URL’si kullanın.
9000038422success_url veya fail_url geçersiz.En fazla 2048 karakterlik bir HTTPS URL’si kullanın. Sandbox ortamı http://localhost adresini de kabul eder.
9000040403Hosted API hesabınızda etkin değil.Destek ekibiyle iletişime geçin.
9000013409Sandbox işlemi kullanılamıyor; örneğin yalnızca Sandbox’a özel bir uç nokta canlı ortam anahtarıyla çağrıldığında.Sandbox anahtarı kullanın veya daha sonra yeniden deneyin.
9000004403Hesabınız bu işlem için kısıtlanmış.Destek ekibiyle iletişime geçin.
3000001503Döviz kuru geçici olarak alınamıyor.Daha sonra yeniden deneyin.
1000011503Hizmet geçici olarak kullanılamıyor.Aynı isteği yeniden deneyin. Yeni kaynak oluşturmadan önce mevcut kaynakları sorgulayın.
1000013503Bu özellik henüz kullanıma sunulmadı.retry_after alanında belirtilen süre (saniye) dolduktan sonra yeniden deneyin.
1000014503Planlı bakım çalışması sürüyor. Ayrıntılar until ve note alanlarında yer alır.Retry-After başlığında belirtilen süre dolduktan sonra yeniden deneyin.

Kartın reddedilmesi bir HTTP hatası değildir; ödeme failure.code ile birlikte failed durumuyla sonuçlanır. Kodların tam listesi için bkz. Hata kodları.

Sandbox kartlarıyla test

Ödemeyi Sandbox anahtarınızla oluşturun, next_action.url adresini açın ve ileri tarihli herhangi bir son kullanma tarihi ile 3 haneli herhangi bir CVV kullanarak aşağıdaki kartlardan biriyle ödeme yapın. Ayrıntılar için bkz. Sandbox ve test kartları.

Kart numarasıSonuç
40000000000000103-D Secure olmadan onaylanır. Direct API’de oluşturma yanıtında durum doğrudan succeeded olarak döner.
4000000000000002 · 4355090000044242Test 3-D Secure adımının ardından succeeded durumu.
4000000000000051 · 43550900000400003-D Secure öncesinde reddedilir (do_not_honor). Hosted API: ödeme yaklaşık 5 dakika requires_action durumunda kalabilir ve failed olmadan önce payment.updated gönderebilir. Direct API: oluşturma yanıtı doğrudan failed döner.
4000000000000093 · 4355090000005151Test 3-D Secure adımının ardından insufficient_funds nedeniyle failed durumu.
4000000000000044Bankadan yanıt alınmaz ve 3-D Secure uygulanmaz; ödeme processing durumunda kalır.
4355090000089999Hosted API: 3-D Secure adımı olmadan processing. Direct API: test 3-D Secure adımı, ardından processing.

Uçtan uca örnek

Ödeme oluşturan, müşteriyi yönlendiren, dönüşü ve webhook’u işleyen asgari bir Node.js sunucusu. Örnek, istek yardımcı fonksiyonunu ve Webhook’lar ile Dönüş URL’si bölümlerindeki doğrulama fonksiyonlarını kullanır.

server.mjs

// server.mjs: Hosted API from start to end (Node.js 20+, no dependencies).
import http from 'node:http';
import {randomUUID} from 'node:crypto';
import {api} from './payniko.mjs';                  // request helper (Authentication)
import {verifyWebhook} from './verify-webhook.mjs'; // webhook check (Webhooks)
import {verifyReturn} from './verify-return.mjs';   // return URL check (Return URL)

const site = 'https://merchant.example';
const orders = new Map();      // use your database
const seenEvents = new Set();  // use your database
const readBody = async (req) => { const parts = []; for await (const p of req) parts.push(p); return Buffer.concat(parts); };

http.createServer(async (req, res) => {
  const url = new URL(req.url, site);
  // 1. Create the payment, 2. redirect the payer.
  if (req.method === 'POST' && url.pathname === '/checkout') {
    const form = JSON.parse((await readBody(req)).toString('utf8')); // your checkout form: the payer's e-mail and phone
    const orderId = 'ORDER-' + Date.now();
    const key = randomUUID();
    orders.set(orderId, {key, status: 'new'});      // save the key before you call the API
    const {data} = await api('POST', '/api/payments/hosted', {
      merchant_order_id: orderId, amount: '100.00', currency: 'USD',
      customer: {email: form.email, phone: form.phone},
      webhook_url: `${site}/webhooks/payniko`,
      success_url: `${site}/payment/success`, fail_url: `${site}/payment/failure`,
    }, key);
    Object.assign(orders.get(orderId), {paymentId: data.payment_id, status: data.status});
    // next_action can be null for a moment: read the payment again before you redirect.
    res.writeHead(303, {Location: data.next_action ? data.next_action.url : `/orders/${orderId}`}).end();
    return;
  }
  // Return URL: show a page, but trust only the webhook or a GET.
  if (req.method === 'GET' && (url.pathname === '/payment/success' || url.pathname === '/payment/failure')) {
    const signed = verifyReturn(url.searchParams, () => process.env.API_SECRET);
    let status = 'unknown';
    if (signed) status = (await api('GET', `/api/payments/${url.searchParams.get('payment_id')}`)).data.status;
    res.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8'});
    res.end(status === 'succeeded' ? 'Thank you, payment received.' : 'Payment not completed.');
    return;
  }
  // Webhook: verify, store event_id once, answer 2xx fast.
  if (req.method === 'POST' && url.pathname === '/webhooks/payniko') {
    const raw = await readBody(req);
    if (!verifyWebhook(raw, req.headers, process.env.API_SECRET)) { res.writeHead(401).end(); return; }
    const event = JSON.parse(raw.toString('utf8'));
    if (!seenEvents.has(event.event_id)) {          // use a unique index in your database
      seenEvents.add(event.event_id);
      const order = orders.get(event.data.merchant_order_id);
      if (order && event.event_type.startsWith('payment.')) order.status = event.data.status;
    }
    res.writeHead(204).end();
    return;
  }
  res.writeHead(404).end();
}).listen(8080);

Webhook’lar

Payniko, bir ödemenin, iadenin, itirazın veya çekimin durumu değiştiğinde webhook_url adresinize imzalı bir POST gönderir; ilk deneme genellikle olaydan sonraki bir dakika içinde yapılır. İmzayı doğrulayın, olayı kaydedin ve işlemeden önce 2xx ile yanıt verin.

Olaylar

OlayAnlam
payment.succeededÖdemenin durumu succeeded. Siparişi teslim edin.
payment.failedÖdeme başarısız sonuçlandı: failed, expired veya cancelled. Bkz. data.status.
payment.updatedMüşterinin işlem yapması hâlâ gerekiyor (requires_action); örneğin ara bir ret sonrasında. Bu nihai bir sonuç değildir.
refund.completedBir iade tamamlandı.
payment.voidedBir iptal işlemi tamamlandı.
refund.rejectedBir iade veya iptal talebi reddedildi.
refund.failedBir iade veya iptal işlemi başarısız oldu.
refund.cancelledBir iade veya iptal talebi iptal edildi.
dispute.openedBir itiraz açıldı.
dispute.in_defenseİtiraz inceleniyor.
dispute.wonİtiraz lehinize sonuçlandı.
dispute.lostİtiraz aleyhinize sonuçlandı.
withdrawal.sentBir para çekme işlemi gönderildi.
withdrawal.rejectedBir para çekme talebi reddedildi.

Ödeme, iade ve itiraz olayları ödemenin webhook_url adresine; para çekme olayları ise para çekme talebinin webhook_url adresine gönderilir. Her ödeme olayı bir ödeme için en fazla bir kez; her iade, itiraz veya para çekme olayı ise ilgili iade, itiraz veya para çekme talebi ve durum başına en fazla bir kez gönderilir.

İçerik

AlanTürAçıklama
event_idstring · uuidOlayın benzersiz ID’si; X-Event-Id başlığıyla aynıdır.
event_typestringYukarıdaki tabloda listelenen olay adı.
environmentstringsandbox veya live.
occurred_atstring · date-timeOlayın gerçekleştiği zaman.
subjectobjectYalnızca iade, itiraz ve para çekme olaylarında yer alır: ilgili iadenin, itirazın veya para çekme talebinin type ve id değerleri.
dataobjectÖdeme olaylarında ödeme nesnesi (bkz. Hosted API); diğer olaylarda aşağıda listelenen alanlar.

payment.succeeded

{
  "event_id": "e0743f4c-a297-4bf0-8085-ceb7d9d5e181",
  "event_type": "payment.succeeded",
  "environment": "sandbox",
  "occurred_at": "2026-11-01T12:02:00.000Z",
  "data": {
    "payment_id": "LUVEI-PAYN-123456",
    "merchant_order_id": "ORDER-2026-001",
    "flow": "hosted",
    "status": "succeeded",
    "amount": "100.00",
    "currency": "USD",
    "environment": "sandbox",
    "next_action": null,
    "card": {
      "bin6": "400000",
      "last_four": "0002",
      "network": "visa",
      "bank": null
    },
    "failure": null,
    "created_at": "2026-11-01T12:00:00.000Z",
    "expires_at": "2026-11-02T12:00:00.000Z",
    "completed_at": "2026-11-01T12:02:00.000Z"
  }
}

İade, itiraz ve para çekme olayları

NesneAlan
refundid, status, operation_type, amount, currency, payment_id veya payment_code, merchant_order_id
disputeid, status, amount, currency, payment_id veya payment_code, merchant_order_id, reason, evidence_due_at (tanımlanmışsa)
withdrawalid, status, amount, currency, usdt_amount, asset, network, tx_hash
  • İade ve itiraz olayları tek bir ödeme referansı içerir: payment_id veya bazı olaylarda payment_code. refund.completed her zaman payment_id içerir. Olay payment_code içeriyorsa siparişi merchant_order_id ile eşleştirin.
  • Bir olaya göre işlem yapmadan önce ilgili iadeyi, itirazı veya para çekme talebini GET ile sorgulayarak güncel durumunu teyit edin.

refund.completed

{
  "event_id": "c4b2e7d1-8a3f-4e65-b0d9-5f1a2c3e4d70",
  "event_type": "refund.completed",
  "environment": "sandbox",
  "occurred_at": "2026-11-02T09:20:00.000Z",
  "subject": {
    "type": "refund",
    "id": "LUVEI-RFND-100001"
  },
  "data": {
    "id": "LUVEI-RFND-100001",
    "status": "completed",
    "operation_type": "refund",
    "amount": "100.00",
    "currency": "USD",
    "payment_id": "LUVEI-PAYN-123456",
    "merchant_order_id": "ORDER-2026-001"
  }
}

withdrawal.sent

{
  "event_id": "b3e8f0a2-5c71-4d96-8e14-7a2c9d0f6b35",
  "event_type": "withdrawal.sent",
  "environment": "sandbox",
  "occurred_at": "2026-11-02T10:00:00.000Z",
  "subject": {
    "type": "withdrawal",
    "id": "LUVEI-WTHD-100002"
  },
  "data": {
    "id": "LUVEI-WTHD-100002",
    "status": "sent",
    "amount": "50.00",
    "currency": "USD",
    "usdt_amount": "49.950000",
    "asset": "USDT",
    "network": "TRC20",
    "tx_hash": "9f2b6c1d4e8a7f30b5c2d9e1a6f4b7c08d3e5a2f1b9c6d4e7a0f3b8c5d2e1f6a"
  }
}

İmza doğrulama

BaşlıkDeğer
X-Event-Idevent_id ile aynıdır.
X-Environmentsandbox veya live. İlgili ortamın gizli anahtarını kullanın.
X-TimestampBu gönderim denemesinin Unix zamanı.
X-SignatureX-Timestamp + "." + raw body dizesinin küçük harfli onaltılık (hex) HMAC-SHA256 değeri.
  1. JSON’u ayrıştırmadan önce ham istek gövdesinin baytlarını okuyun.

  2. X-Timestamp değerinin 10 haneli olduğunu ve sunucu saatinizden en fazla 300 saniye farklı olduğunu doğrulayın.

  3. X-Timestamp + "." + raw body dizesinin HMAC-SHA256 değerini API gizli anahtarınızla hesaplayın ve sonucu X-Signature ile sabit zamanlı olarak karşılaştırın.

  4. Doğrulama başarısız olursa isteği 401 ile reddedin.

// Node.js 20+: verify a Payniko notification before parsing it.
import {createHmac, timingSafeEqual} from 'node:crypto';

export function verifyWebhook(rawBody, headers, apiSecret, now = Math.floor(Date.now() / 1000)) {
  const timestamp = headers['x-timestamp'];
  const signature = headers['x-signature'];
  if (typeof timestamp !== 'string' || !/^[0-9]{10}$/.test(timestamp) ||
      typeof signature !== 'string' || !/^[0-9a-f]{64}$/.test(signature) ||
      Math.abs(now - Number(timestamp)) > 300) return false;
  const expected = createHmac('sha256', apiSecret).update(timestamp + '.').update(rawBody).digest();
  return timingSafeEqual(expected, Buffer.from(signature, 'hex'));
}

// Example with node:http - read the raw bytes first, never re-serialise the JSON.
// import http from 'node:http';
// http.createServer((req, res) => {
//   const chunks = [];
//   req.on('data', (c) => chunks.push(c)).on('end', () => {
//     const raw = Buffer.concat(chunks);
//     const secret = req.headers['x-environment'] === 'live' ? process.env.LIVE_API_SECRET : process.env.SANDBOX_API_SECRET;
//     if (!verifyWebhook(raw, req.headers, secret)) { res.writeHead(401).end(); return; }
//     const event = JSON.parse(raw.toString('utf8'));
//     // Save event.event_id (unique) and the event, then answer quickly.
//     res.writeHead(204).end();
//   });
// }).listen(8080);
<?php
// PHP 8.1+: verify a Payniko notification before parsing it.
function payniko_verify_webhook(string $rawBody, string $timestamp, string $signature, string $apiSecret, ?int $now = null): bool
{
    $now ??= time();
    if (!preg_match('/^[0-9]{10}$/', $timestamp) || !preg_match('/^[0-9a-f]{64}$/', $signature)) return false;
    if (abs($now - (int) $timestamp) > 300) return false;
    $expected = hash_hmac('sha256', $timestamp . '.' . $rawBody, $apiSecret);
    return hash_equals($expected, $signature);
}

$raw = file_get_contents('php://input');            // raw bytes, before json_decode
$environment = $_SERVER['HTTP_X_ENVIRONMENT'] ?? '';
$secret = $environment === 'live' ? getenv('LIVE_API_SECRET') : getenv('SANDBOX_API_SECRET');
if (!payniko_verify_webhook($raw, $_SERVER['HTTP_X_TIMESTAMP'] ?? '', $_SERVER['HTTP_X_SIGNATURE'] ?? '', (string) $secret)) {
    http_response_code(401);
    exit;
}
$event = json_decode($raw, true, 32, JSON_THROW_ON_ERROR);
// Save $event['event_id'] (unique) and the event, then answer quickly.
http_response_code(204);
# Python 3.9+: verify a Payniko notification before parsing it.
import hashlib, hmac, re, time


def verify_webhook(raw_body: bytes, headers, api_secret: str, now=None) -> bool:
    timestamp = headers.get("X-Timestamp", "")
    signature = headers.get("X-Signature", "")
    if not re.fullmatch(r"[0-9]{10}", timestamp) or not re.fullmatch(r"[0-9a-f]{64}", signature):
        return False
    if abs(int(now or time.time()) - int(timestamp)) > 300:
        return False
    expected = hmac.new(api_secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

# Flask example:
# @app.post("/webhooks/payments")
# def payniko_webhook():
#     raw = request.get_data()                    # raw bytes, before JSON parsing
#     env = request.headers.get("X-Environment")
#     secret = os.environ["LIVE_API_SECRET" if env == "live" else "SANDBOX_API_SECRET"]
#     if not verify_webhook(raw, request.headers, secret):
#         return "", 401
#     event = json.loads(raw)
#     # Save event["event_id"] (unique) and the event, then answer quickly.
#     return "", 204

Olayların yalnızca bir kez işlenmesi

  1. event_id değerini benzersiz dizine (unique index) sahip bir tabloda saklayın. Değer zaten mevcutsa 2xx koduyla yanıt verin ve işlemi sonlandırın.

  2. Birkaç saniye içinde 2xx koduyla yanıt verin; zaman alan işlemleri (e-posta gönderimi, stok güncellemesi) daha sonra bir kuyrukta gerçekleştirin.

  3. Olaylar gecikmeli, birden fazla kez veya farklı sırayla gelebilir. Zaman aşımıyla sonuçlanan bir ödeme bir dakika veya daha uzun bir süre sonra bildirilebilir. Kararlarınızı data içindeki duruma göre verin veya nesneyi GET ile yeniden sorgulayın.

  4. Bir siparişi hiçbir zaman yalnızca tarayıcının success_url adresine dönmesine dayanarak teslim etmeyin.

Yeniden denemeler

  • Yaklaşık 10 saniye içinde alınan her 2xx yanıtı başarılı gönderim sayılır. Yönlendirmeler izlenmez ve başarısız gönderim olarak değerlendirilir.
  • Başarısız gönderimler yaklaşık 1 dakika, 5 dakika, 15 dakika, 1 saat ve 6 saat sonra yeniden denenir. İstek gövdesi ve X-Event-Id değişmez; X-Timestamp ve X-Signature her deneme için yeniden üretilir.
  • Son denemeden sonra gönderim failed olarak işaretlenir. Bazı hatalarda (özel IP hedefi, eksik imzalama anahtarı) yeniden deneme yapılmaz ve gönderim hemen failed olarak işaretlenir. Başarısız gönderimler gönderim API’si ile yeniden gönderilebilir.
  • webhook_url adresiniz her denemede genel bir IP adresine çözümlenmelidir.

Webhook gönderimleri

Gönderilen her webhook bir gönderim kaydı olarak saklanır. Başarısız gönderimleri belirlemek ve yeniden göndermek için bu API’yi kullanın.

GET/api/webhooks/deliveries

Gönderimleri listeleme ve sorgulama

./payniko.sh GET '/api/webhooks/deliveries?status=failed&page=1&per_page=20'
import {api} from './payniko.mjs';

const deliveries = await api('GET', '/api/webhooks/deliveries?status=failed&page=1&per_page=20');
console.log(deliveries);
<?php
require __DIR__ . '/payniko.php';

$deliveries = payniko_api('GET', '/api/webhooks/deliveries?status=failed&page=1&per_page=20');
print_r($deliveries);
from payniko import api

deliveries = api("GET", "/api/webhooks/deliveries?status=failed&page=1&per_page=20")
print(deliveries)

Sonuçları status ile filtreleyin (pending, delivered, failed) ve page ile per_page parametreleriyle sayfalayın. Liste, anahtarınızın ortamına ait gönderimleri içerir. GET /api/webhooks/deliveries/{id} ayrıca url ve payload alanlarını döndürür.

AlanTürAçıklama
idstring · uuidGönderim ID’si; webhook’un event_id değeriyle aynıdır.
eventstringOlay adı, örneğin payment.succeeded.
statusstringGönderim durumu.
pendingdeliveredfailed
payment_idstring · LUVEI-ABCD-123456 | nullİlgili ödeme (varsa).
subject_typestring | nullrefund, dispute veya withdrawal; ödeme olaylarında null.
subject_idstring | nullİadenin, itirazın veya para çekme talebinin ID’si.
attempt_countintegerŞimdiye kadar yapılan deneme sayısı.
last_status_codeinteger | nullSon denemede uç noktanızın döndürdüğü HTTP durum kodu.
last_error_codestring | nullSon denemenin başarısız olma nedeni; aşağıdaki tabloya bakın.
last_error_messagestring | nulllast_error_code için okunabilir açıklama.
last_errorstring | nullSerbest metin biçiminde hata; Türkçe olabilir. Bunun yerine last_error_code alanını kullanın.
next_attempt_atstring · date-time | nullBir sonraki otomatik denemenin zamanı.
delivered_atstring · date-time | nullUç noktanızın gönderimi kabul ettiği zaman.
created_atstring · date-timeWebhook’un oluşturulduğu zaman.
urlstringYalnızca ayrıntı yanıtında: hedef URL.
payloadobject | nullYalnızca ayrıntı yanıtında: saklanan webhook gövdesinin yeniden kodlanmış hâli (ödeme referansları payment_id olarak gösterilir). İmzalanan baytlarla eşleşmez; imza doğrulamasını bu içeriğe göre yapmayın.

Yanıt · Örnek

{
  "id": "0a6f3c9e-2b7d-4e15-9c84-6d1f2a3b5e70",
  "event": "payment.succeeded",
  "status": "failed",
  "payment_id": "LUVEI-PAYN-123456",
  "subject_type": null,
  "subject_id": null,
  "attempt_count": 6,
  "last_status_code": 500,
  "last_error": "Bildirim adresi başarısız yanıt verdi.",
  "last_error_code": "webhook_response_unsuccessful",
  "last_error_message": "Webhook delivery failed.",
  "next_attempt_at": null,
  "delivered_at": null,
  "created_at": "2026-11-01T12:02:00.000Z"
}
KodAnlam
webhook_response_unsuccessfulUç noktanız 2xx dışında bir durum koduyla (bkz. last_status_code) veya bir yönlendirmeyle yanıt verdi.
webhook_unreachableUç noktanıza ulaşılamadı (DNS, bağlantı veya zaman aşımı).
invalid_webhook_targetKayıtlı URL geçerli bir webhook URL’si değil.
webhook_target_private_networkURL özel (private) veya yerel bir IP adresine çözümlendi.
webhook_environment_invalidWebhook’un ortamı belirlenemedi.
webhook_signing_unavailableWebhook imzalanamadı.
webhook_egress_proxy_unreachablePayniko tarafında geçici bir gönderim sorunu.
webhook_delivery_failedDiğer gönderim hataları.
POST/api/webhooks/deliveries/{id}/retryIdempotency-Key zorunlu

Başarısız gönderimi yeniden gönderme

KEY="$(openssl rand -hex 16)"  # store with the order before sending; reuse it after network errors
./payniko.sh POST '/api/webhooks/deliveries/0a6f3c9e-2b7d-4e15-9c84-6d1f2a3b5e70/retry' '' "$KEY"
import {api, randomUUID} from './payniko.mjs';

const key = randomUUID(); // Store this key with the order before sending; reuse it after network errors.
const delivery = await api('POST', '/api/webhooks/deliveries/0a6f3c9e-2b7d-4e15-9c84-6d1f2a3b5e70/retry', undefined, key);
console.log(delivery);
<?php
require __DIR__ . '/payniko.php';

$key = bin2hex(random_bytes(16)); // store with the order; reuse it after network errors
$delivery = payniko_api('POST', '/api/webhooks/deliveries/0a6f3c9e-2b7d-4e15-9c84-6d1f2a3b5e70/retry', null, $key);
print_r($delivery);
from payniko import api
import uuid

key = str(uuid.uuid4())  # store with the order; reuse it after network errors
delivery = api("POST", "/api/webhooks/deliveries/0a6f3c9e-2b7d-4e15-9c84-6d1f2a3b5e70/retry", None, key)
print(delivery)

Yanıt · 200

Gönderimi status: pending ile döndürür.

  • Yalnızca failed durumundaki gönderimler yeniden gönderilebilir; diğer gönderimler için 409 9000025 döner.
  • Aynı webhook aynı id, aynı event_id ve aynı gövdeyle yeniden kuyruğa alınır ve yeni bir deneme serisi başlar.
  • Boş bir istek gövdesi veya {} gönderin.

Olası hatalar

Sandbox ve test kartları

Canlı ortama geçmeden önce entegrasyonunuzu bir Sandbox anahtarıyla test edin. Sandbox, canlı ortamla aynı API’yi ve webhook’ları gerçek para hareketi ve gerçek banka işlemi olmadan sunar.

Sandbox’ın işleyişi

  • Ortam seçimi için ayrı bir ayar bulunmaz: Sandbox anahtarıyla imzalanan istekler Sandbox ortamında, canlı ortam anahtarıyla imzalanan istekler canlı ortamda çalışır.
  • Aşağıdaki kartlar hem ödeme sayfasında hem de kendi ödeme formunuzda çalışır; gerçek kartlar reddedilir. Kendi formunuz, BIN değeri 435509 veya 400000 olan diğer geçerli kart numaralarını da kabul eder; bu durumda açılan test sayfasında başarılı, başarısız veya yanıt yok sonuçlarından birini seçebilirsiniz.
  • 3-D Secure ekranları test ekranlarıdır ve kart numaralarının kontrol basamağı geçerli olmalıdır.
  • Webhook’lar, webhook_url adresinize gerçek HTTPS istekleri olarak iletilir ve Sandbox gizli anahtarınızla imzalanır.

Test kartları

Tablo aksini belirtmedikçe kartlar Hosted API ve Direct API'de aynı davranır. Gelecekteki herhangi bir son kullanma tarihi ve 3 haneli herhangi bir CVV kullanın.

Kart numarasıSonuç
40000000000000103-D Secure olmadan onaylanır. Direct API’de oluşturma yanıtında durum doğrudan succeeded olarak döner.
4000000000000002 · 4355090000044242Test 3-D Secure adımının ardından succeeded durumu.
4000000000000051 · 43550900000400003-D Secure öncesinde reddedilir (do_not_honor). Hosted API: ödeme yaklaşık 5 dakika requires_action durumunda kalabilir ve failed olmadan önce payment.updated gönderebilir. Direct API: oluşturma yanıtı doğrudan failed döner.
4000000000000093 · 4355090000005151Test 3-D Secure adımının ardından insufficient_funds nedeniyle failed durumu.
4000000000000044Bankadan yanıt alınmaz ve 3-D Secure uygulanmaz; ödeme processing durumunda kalır.
4355090000089999Hosted API: 3-D Secure adımı olmadan processing. Direct API: test 3-D Secure adımı, ardından processing.
  • Herhangi bir test kartıyla, .77 ile biten bir tutar (örneğin 100.77) ödemeyi processing durumunda tutar (manuel inceleme).
  • Hiç yanıt almayan bir test ödemesi sonunda service_unavailable nedeniyle failed olarak kapatılır.
  • Reddedilen bir test kartıyla yapılan ödeme (örneğin 4000000000000051), nihai bir duruma ulaşmadan önce bir süre requires_action durumunda kalabilir. Kararlarınızı ara bir redde göre değil, webhook’a veya bir GET çağrısına göre verin.

Test BIN’leri

Sandbox ortamında BIN sorgusu aşağıdaki sonuçları döndürür:

BINSonuç
435509 · 400000payment_mode: direct içeren 200 yanıtı.
540061payment_mode: hosted içeren 200 yanıtı; müşteri işleme Sandbox ödeme sayfasında devam eder.
9792100055230 kodlu 409 yanıtı.
Diğer tüm altı haneli değerler0055233 kodlu 409 yanıtı.

İadeler ve para çekme

Sandbox'ta iadeler ve çekimler bir test bakiyesine karşı anında tamamlanır. Sonucu, imzanın parçası olmayan X-Sandbox-Scenario başlığıyla seçin. Her senaryo için yeni bir Idempotency-Key kullanın: başlık idempotency parmak izine dahildir ve aynı anahtar farklı bir senaryoyla 409 9000010 ile reddedilir. Bilinmeyen bir değer ya da canlı anahtarla gönderilen başlık 422 1000001 döndürür; kanıt uç noktası başlığı kabul etmez. Uygulanan senaryo X-Sandbox-Scenario yanıt başlığında geri döner.

Senaryo uygulanmadan önce sandbox canlıyla aynı kuralları uygular: en düşük ve günlük çekim limitleri, döviz kurları (kullanılamıyorsa 503 3000001) ve adres doğrulaması. Geçerli sağlama toplamına sahip her TRC20 adresi, TRON sıfır adresi dahil, sandbox'ta kabul edilir ve transfer yapılmaz; 1000030 (ödeme alamayan adres) sandbox'ta yeniden üretilemez.

Uç noktaDeğer
POST /api/account/refundscompleted (varsayılan) veya rejected
POST /api/account/withdrawalssent (varsayılan) veya rejected
KEY="$(openssl rand -hex 16)"  # store with the order before sending; reuse it after network errors
SANDBOX_SCENARIO=rejected ./payniko.sh POST '/api/account/refunds' '{
  "payment_id": "LUVEI-PAYN-123456",
  "operation_type": "refund",
  "reason": "Test refund"
}' "$KEY"
import {api, randomUUID} from './payniko.mjs';

const key = randomUUID(); // Store this key with the order before sending; reuse it after network errors.
const refund = await api('POST', '/api/account/refunds', {
  "payment_id": "LUVEI-PAYN-123456",
  "operation_type": "refund",
  "reason": "Test refund"
}, key, {'X-Sandbox-Scenario': 'rejected'});
console.log(refund);
<?php
require __DIR__ . '/payniko.php';

$key = bin2hex(random_bytes(16)); // store with the order; reuse it after network errors
$refund = payniko_api('POST', '/api/account/refunds', [
    "payment_id" => 'LUVEI-PAYN-123456',
    "operation_type" => 'refund',
    "reason" => 'Test refund',
], $key, ['X-Sandbox-Scenario: rejected']);
print_r($refund);
from payniko import api
import uuid

key = str(uuid.uuid4())  # store with the order; reuse it after network errors
refund = api("POST", "/api/account/refunds", {
    "payment_id": "LUVEI-PAYN-123456",
    "operation_type": "refund",
    "reason": "Test refund"
}, key, {"X-Sandbox-Scenario": "rejected"})
print(refund)

Sandbox’taki tx_hash değeri simüle edilmiştir ve gerçek bir blokzincir işlemine karşılık gelmez.

POST/api/sandbox/disputesIdempotency-Key zorunluYalnızca Sandbox anahtarı

Test itirazı oluşturma

İtiraz süreçlerinizi ve webhook’larınızı test edebilmeniz için başarılı bir Sandbox ödemesi üzerinde itiraz açar. Yalnızca Sandbox anahtarıyla kullanılabilir; canlı ortam anahtarıyla yapılan istekler 409 9000013 yanıtını alır.

AlanTürZorunluAçıklama
payment_idstring · LUVEI-ABCD-123456ZorunluDurumu succeeded olan Sandbox ödemelerinizden biri.
reasonstringZorunluİtiraz nedeni.
fraudunauthorizedproduct_not_deliveredduplicateamount_mismatchother
scenariostringİsteğe bağlıBaşlangıç durumu. Varsayılan: open.
openwonlost
KEY="$(openssl rand -hex 16)"  # store with the order before sending; reuse it after network errors
./payniko.sh POST '/api/sandbox/disputes' '{
  "payment_id": "LUVEI-PAYN-123456",
  "reason": "product_not_delivered",
  "scenario": "open"
}' "$KEY"
import {api, randomUUID} from './payniko.mjs';

const key = randomUUID(); // Store this key with the order before sending; reuse it after network errors.
const dispute = await api('POST', '/api/sandbox/disputes', {
  "payment_id": "LUVEI-PAYN-123456",
  "reason": "product_not_delivered",
  "scenario": "open"
}, key);
console.log(dispute);
<?php
require __DIR__ . '/payniko.php';

$key = bin2hex(random_bytes(16)); // store with the order; reuse it after network errors
$dispute = payniko_api('POST', '/api/sandbox/disputes', [
    "payment_id" => 'LUVEI-PAYN-123456',
    "reason" => 'product_not_delivered',
    "scenario" => 'open',
], $key);
print_r($dispute);
from payniko import api
import uuid

key = str(uuid.uuid4())  # store with the order; reuse it after network errors
dispute = api("POST", "/api/sandbox/disputes", {
    "payment_id": "LUVEI-PAYN-123456",
    "reason": "product_not_delivered",
    "scenario": "open"
}, key)
print(dispute)

Yanıt · 201

İtiraz nesnesini döndürür. open durumundaki bir test itirazı 7 gün boyunca yanıt kabul eder; won ve lost hemen nihai hâle gelir. under_review durumu Sandbox’ta oluşmaz ve test itirazları bakiyenizi etkilemez.

İstek kayıtları

İşlem kayıtları sayfası son API isteklerinizi durum, hata kodu ve correlation_id bilgileriyle listeler. Anahtar belirlenmeden önce reddedilen istekler (geçersiz anahtar, imza veya üye işyeri kodu) listelenmez. Sandbox ödemeleri Sandbox API siparişleri sayfasında listelenir.

Hata kodları

Hatalar application/problem+json biçiminde döndürülür. İş mantığınızı HTTP durum koduna ve 7 haneli code değerine dayandırın; title ve detail alanları insanlar tarafından okunmak üzere hazırlanır.

Hata yanıtı biçimi

AlanAçıklama
typeHata türünün URL’si: https://payniko.com/problems/{code}.
titleHatanın kısa özeti.
statusHTTP durum kodu.
detailBu istekteki hatanın açıklaması.
codeDeğişmeyen 7 haneli kod; iş mantığınızda bu kodu kullanın.
correlation_idX-Correlation-Id başlığıyla aynıdır. Destek taleplerinize ekleyin.
errorsDoğrulama hatalarında alan bazında mesajlar: { "field": ["message"] }.
integrationEntegrasyon ipuçları: stage, reason, violations (location, path, expected, hint), docs_url, retryable ve bazı durumlarda retry_after.
metadataDirect API hatalarında ve kimlik doğrulama ile istek sınırı hatalarında, retry_class ve bilgilendirme amaçlı developer_detail ile birlikte yer alabilir. Bu alanı isteğe bağlı olarak değerlendirin.
limits9000005 hatasında izin verilen tutar aralığı. 0055235 hatasında bunun karşılığı amount_limit alanıdır.
retry_afterBeklenmesi gereken süre (saniye); bazı 503 hatalarında yer alır.

HTTP 422 · 1000001

{
  "type": "https://payniko.com/problems/1000001",
  "title": "Validation failed",
  "status": 422,
  "detail": "The submitted fields are not valid.",
  "code": "1000001",
  "correlation_id": "9b8c7d6e-5f40-4a3b-8c2d-1e0f9a8b7c6d",
  "errors": {
    "merchant_order_id": [
      "This field is required."
    ]
  },
  "integration": {
    "stage": "validation",
    "reason": "validation_failed",
    "violations": [
      {
        "location": "body",
        "path": "merchant_order_id",
        "expected": {
          "type": "string",
          "min_length": 1,
          "max_length": 64,
          "unique_within": "account_and_environment"
        },
        "hint": "Check this field against the documented format."
      }
    ],
    "docs_url": "/en/developers#errors",
    "retryable": false
  }
}

Yeniden deneme yönergeleri

retry_classAnlam
no_retryİsteği yeniden göndermeden önce düzeltin.
retry_same_requestKısa bir süre sonra birebir aynı isteği yeniden gönderin (aynı anahtar, aynı istek gövdesi).
retry_afterRetry-After başlığında belirtilen süre kadar bekleyin, ardından isteği yeniden gönderin.
read_orderNasıl ilerleyeceğinize karar vermeden önce ödemeyi GET ile sorgulayın.
  • retry_class yalnızca metadata taşıyan yanıtlarda bulunur (Direct API, kimlik doğrulama ve hız sınırı hataları). Diğer hatalar bunun yerine integration.retryable taşır.
  • Zaman aşımı veya 5xx hatasından sonra hiçbir zaman yeni bir ödeme oluşturmayın; ödemeyi sorgulayın veya isteği aynı anahtarla yeniden gönderin.
  • Kartın reddedilmesi bir HTTP hatası değildir: ödeme 200, status: failed ve failure ile döndürülür.
  • developer_detail değişebilir; entegrasyon mantığınızı bu alana dayandırmayın.

Tüm hata kodları

Kimlik doğrulama ve erişim

KodHTTPAnlamÖnerilen işlem
0055502401 / 403Erişim reddedildi. 401: X-Api-Key veya X-Merchant-Code eksik, bir başlık yinelenmiş veya hatalı biçimlendirilmiş, anahtar tanınmıyor veya iptal edilmiş, üye işyeri kodu eşleşmiyor ya da X-Environment gönderilmiş ancak anahtarla uyuşmuyor. 403: hesap veya Direct API erişimi etkin değil.Başlıkları ve API anahtarını doğrulayın. İsteği otomatik olarak yeniden denemeyin.
0055504401X-Timestamp veya X-Signature eksik ya da hatalı biçimlendirilmiş veya sunucu saatinizle Payniko sunucu saati arasındaki fark 300 saniyeyi aşıyor.Sunucu saatinizi (NTP) düzeltin ve isteği güncel bir zaman damgasıyla yeniden imzalayın.
0055505401İmza istekle eşleşmiyor.Özeti, gönderdiğiniz istek gövdesinin baytları üzerinden birebir hesaplayın, yolu sorgu dizesi olmadan imzalayın ve doğru API gizli anahtarını kullanın.
9000002401İstek işleme alındığında geçerlilik süresi dolmuştu.İsteği güncel bir zaman damgasıyla yeniden imzalayın ve tekrar gönderin.
9000040403Hosted API hesabınızda etkin değil.Destek ekibiyle iletişime geçin.
1000004403Yeni ödemeler geçici olarak durduruldu.Daha sonra yeniden deneyin.
9000004403Hesabınız bu işlem için kısıtlanmış.Destek ekibiyle iletişime geçin.

Idempotency-Key ve çakışmalar

KodHTTPAnlamÖnerilen işlem
1000009422Idempotency-Key eksik veya geçersiz.Her POST isteğinde A-Z a-z 0-9 _ - karakterlerinden oluşan, 8–128 karakter uzunluğunda bir anahtar gönderin.
9000010409Bu Idempotency-Key daha önce farklı bir istek için kullanılmış ya da aynı imzalı istek farklı bir anahtarla yeniden gönderilmiş.İsteği aynı anahtar ve aynı istek gövdesiyle yeniden deneyin; yeni anahtarı yalnızca yeni bir işlem için kullanın.
9000039409Bu Idempotency-Key ile gönderilen ilk istek hâlâ işleniyor (Hosted API).Kısa bir bekleme sonrası aynı isteği yeniden gönderin.
9000007409Bu merchant_order_id için zaten bir ödeme mevcut (Hosted API).Mevcut ödemeyi sorgulayın veya yeni bir sipariş referansı kullanın.
0055503409Direct API çakışması: bu merchant_order_id için zaten bir ödeme mevcut, Idempotency-Key farklı bir istek gövdesiyle ya da bir Hosted ödeme için kullanılmış veya ilk istek hâlâ işleniyor.Ödemeyi GET ile sorgulayın. Yalnızca birebir aynı isteği yeniden gönderin.

Doğrulama

KodHTTPAnlamÖnerilen işlem
1000001422İstek geçersiz: bilinmeyen bir alan, geçersiz bir biçim, POST isteğinde sorgu dizesi, desteklenmeyen bir sorgu parametresi veya JSON nesnesi olmayan bir istek gövdesi. İlgili alanlar errors içinde listelenir.Listelenen alanları düzeltin.
1000020422Tutar biçimi geçersiz.Tutarı, başında sıfır olmayan, iki ondalık basamaklı pozitif bir dize olarak gönderin; örneğin "100.00".
1000021422Para birimi desteklenmiyor.USD, EUR, AED veya TRY kullanın ya da currency alanını göndermeyin.
1000023422Tutar çok yüksek.Daha düşük bir tutar gönderin.
9000008422webhook_url geçersiz.443 portu üzerinden erişilebilen, en fazla 500 karakterlik, herkese açık bir HTTPS URL’si kullanın.
9000038422success_url veya fail_url geçersiz.En fazla 2048 karakterlik bir HTTPS URL’si kullanın. Sandbox ortamı http://localhost adresini de kabul eder.

Tutar, kart ve BIN

KodHTTPAnlamÖnerilen işlem
9000005422Tutar, hesabınızın tutar sınırlarının dışında (Hosted API). İzin verilen aralık limits alanında yer alır.Tutarı güncelleyin.
0055235422Tutar kabul edilmiyor (Direct API ve BIN sorgusu). Bilgi mevcut olduğunda amount_limit alanı min ve max değerlerini içerir.Tutarı güncelleyin.
0055210422BIN altı haneli değil.Kart numarasının ilk altı hanesini gönderin.
0055233409BIN tanınmıyor.Müşteriden farklı bir kart isteyin. Bu bir banka reddi değildir.
0055230409Bu kart bu ödeme için kullanılamaz.Müşteriden farklı bir kart isteyin. Bu bir banka reddi değildir.
0055231409Bu kart bu ödemede kabul edilmiyor.Müşteriden farklı bir kart isteyin.
0055221409Bu kartın limitlerinden birine ulaşıldı.Farklı bir kart kullanın veya daha sonra yeniden deneyin.
0055234409Kart bilgileri geçersiz: kart numarası, son kullanma tarihi veya CVV. metadata.developer_detail hangi alanın geçersiz olduğunu belirtir: card_number_invalid, card_expiry_invalid veya card_cvv_invalid.Müşteriden kart bilgilerini yeniden girmesini isteyin.
0055001409Hesabınız bu ödeme için hazır değil. Sandbox anahtarıyla bu aynı zamanda sandbox simülasyonunun geçici olarak kullanılamadığı anlamına gelir (developer_detail: sandbox_unavailable), 9000013 ile eşdeğerdir.Destek ekibiyle iletişime geçin.
0055002403Hesabınızda kartlı ödemeler etkin değil.Destek ekibiyle iletişime geçin.
9000013409Sandbox işlemi kullanılamıyor; örneğin yalnızca Sandbox’a özel bir uç nokta canlı ortam anahtarıyla çağrıldığında.Sandbox anahtarı kullanın veya daha sonra yeniden deneyin.

Bulunamayan kaynaklar

KodHTTPAnlamÖnerilen işlem
9000009404Ödeme bu hesapta ve ortamda mevcut değil ya da bu işlem için kullanılamaz.payment_id değerini doğrulayın ve aynı ortama ait bir anahtar kullanın.
1000002404Bulunamadı: yol bilinmiyor ya da bu API ürünü platformda etkin değil. Hesabınız için kapatılmış bir API bunun yerine 403 döndürür (Hosted API'de 9000040, Direct API'de 0055502).Yolu doğrulayın; API'nin kullanılabilir olması gerekiyorsa destekle iletişime geçin.
1000007405HTTP yöntemine izin verilmiyor. Geçerli yöntemler Allow başlığında listelenir.Dokümantasyonda belirtilen yöntemi kullanın.

Sınırlar ve erişilebilirlik

KodHTTPAnlamÖnerilen işlem
1000005429Çok fazla istek gönderildi ya da art arda gelen imza hatalarının ardından geçici bir kilitleme uygulandı.Retry-After başlığında belirtilen süre kadar bekleyin, ardından artan bekleme aralıklarıyla yeniden deneyin.
1000011503Hizmet geçici olarak kullanılamıyor.Aynı isteği yeniden deneyin. Yeni kaynak oluşturmadan önce mevcut kaynakları sorgulayın.
1000013503Bu özellik henüz kullanıma sunulmadı.retry_after alanında belirtilen süre (saniye) dolduktan sonra yeniden deneyin.
1000014503Planlı bakım çalışması sürüyor. Ayrıntılar until ve note alanlarında yer alır.Retry-After başlığında belirtilen süre dolduktan sonra yeniden deneyin.
3000001503Döviz kuru geçici olarak alınamıyor.Daha sonra yeniden deneyin.
1000006500Beklenmeyen bir sunucu hatası oluştu.Aynı isteği yeniden deneyin veya kaynağı sorgulayın.

Hesap işlemleri

KodHTTPAnlamÖnerilen işlem
9000014409Bu ödeme için açık veya tamamlanmış bir iade ya da iptal işlemi ya da lehinize sonuçlanmamış bir itiraz bulunuyor.İadeyi veya itirazı sorgulayın.
9000015404İtiraz kaydı bulunamadı.İtiraz ID’sini doğrulayın.
9000016409İtiraz artık yanıt kabul etmiyor (itiraz kapanmış veya evidence_due_at zamanı geçmiş).İtirazı sorgulayın.
9000017422TRC20 adresi geçersiz.Adresi sahibiyle birlikte doğrulayın.
1000030422Bu adres para çekme işlemlerinde alıcı olarak kullanılamaz.Farklı bir adres kullanın.
9000018422address_acknowledged değeri true değil.Adresi teyit edin, ardından true gönderin.
9000019422Para çekme tutarı geçersiz veya en düşük tutarın altında.Tutarı güncelleyin.
9000020409Kullanılabilir bakiye yetersiz.Kullanılabilir bakiyeyi kontrol edin.
9000021409Günlük para çekme limitine ulaşıldı.Ertesi gün yeniden deneyin veya daha düşük bir tutar talep edin.
9000022409Şu anda işlenmekte olan başka bir para çekme talebi var.Bekleyin, ardından aynı isteği yeniden gönderin.
9000023404Para çekme talebi bulunamadı.Para çekme talebinin ID’sini doğrulayın.
9000024404Webhook gönderimi bulunamadı.Gönderim ID’sini doğrulayın.
9000025409Yalnızca başarısız gönderimler yeniden gönderilebilir.Gönderimin durumunu kontrol edin.

Yalnızca Sandbox

KodHTTPAnlamÖnerilen işlem
9000026503Sandbox ortamı bu işlem için henüz kullanılamıyor.Retry-After başlığında belirtilen süre dolduktan sonra yeniden deneyin.
9000027409Test bakiyesi başka bir işlem tarafından kullanılıyor.Kısa bir süre sonra aynı isteği yeniden gönderin.
9000030409Tutar test bakiyesine eklenemedi.Daha sonra yeniden deneyin.
9000033409Test bakiyesi bu iade için yetersiz. Bakiyesi yetersiz bir sandbox çekimi bunun yerine 9000020 döndürür.Önce ek test ödemeleri oluşturun.

Mesaj dili

title ve detail alanları Accept-Language başlığına göre belirlenir (en, tr, de, ar, es, el; de-AT değeri de olarak değerlendirilir). Diğer tüm değerlerde İngilizce döner. Kullanılan dil Content-Language başlığında belirtilir.

Canlı ortama geçiş kontrol listesi

Gerçek ödeme kabul etmeye başlamadan önce bu kontrol listesini tamamlayın.

  • Sandbox ortamında bir ödeme succeeded, bir diğeri failed durumuyla sonuçlandı ve sipariş durumlarınız buna göre güncellendi.

  • Webhook uç noktanız imzayı ham istek gövdesi üzerinden doğruluyor, eski zaman damgalarını reddediyor, her event_id değerini bir kez kaydediyor ve vakit kaybetmeden 2xx koduyla yanıt veriyor.

  • Siparişleri yalnızca doğrulanmış bir webhook veya bir GET çağrısı succeeded durumunu gösterdikten sonra teslim ediyorsunuz; tarayıcının geri dönüşünü hiçbir zaman esas almıyorsunuz.

  • Her POST isteği, Idempotency-Key değerini göndermeden önce saklıyor ve ağ hatalarından sonra aynı değeri yeniden kullanıyor.

  • Kodunuz requires_action, processing, 429 ve 5xx durumlarını mükerrer ödeme oluşturmadan yönetiyor.

  • Canlı ortam anahtarı ve gizli anahtarı yalnızca gizli bilgi kasanızda saklanıyor. Koddaki tek değişiklik, Sandbox anahtarının canlı ortam anahtarıyla değiştirilmesi.

  • webhook_url, success_url ve fail_url canlı ortamdaki HTTPS adreslerinizi gösteriyor.

  • Her istek için X-Correlation-Id değerini log kayıtlarınıza yazıyorsunuz.

Anahtarlar hazır olsa da gerçek tahsilata başlamadan önce entegrasyonunuzu bu listeyle doğrulayın. Test tamamlamak canlı anahtar oluşturma koşulu değildir.