PayFerrari

Production API

PayFerrari Merchant API

Bahis, casino ve e-ticaret siteleri için yatırım (deposit) ve oyuncu çekimi (withdraw) entegrasyonu. Public/secret key kimlik doğrulama, HMAC-SHA256 imza, IP kısıtlaması, imzalı webhook'lar ve iş yeri paneli.

Base URLhttps://payferrari.com
Formatapplication/json
Tutar birimikuruş (1 TL = 100)

Kimlik doğrulama

Her iş yerinin iki anahtarı vardır. İkisi de iş yeri paneli → API anahtarları sayfasındadır.

Public key Public pk_live_8fK2…

İş yerinizi tanımlar. Her istekte X-Public-Key başlığında gönderilir. Gizli değildir ve yenilenmez.

Secret key Secret sk_live_Qe71…

İstekleri ve webhook'ları imzalar. Yalnızca sunucunuzda, ortam değişkeninde tutun. Tarayıcıya, mobil uygulamaya veya repoya koymayın.

Sandbox iş yerlerinde anahtarlar pk_test_ / sk_test_ önekiyle başlar ve tüm yanıtlarda "sandbox": true döner.

Başlıklar

BaşlıkZorunluAçıklama
X-Public-KeyTüm isteklerPublic key (pk_live_…)
X-Signatureİmzalı uç noktalarBase64(HMAC-SHA256(secret_key, imza_dizgisi))
X-PayFerrari-TimestampÖnerilirİsteğin Unix zaman damgası (saniye). İmzaya dahil edilir ve tekrar saldırılarını önler.
Content-TypePOSTapplication/json
X-Public-Key: pk_live_xxxxxxxxxxxxxxxxxxxxxxxx
X-PayFerrari-Timestamp: 1760000000
X-Signature: 3q0b7cF0m6l3…Vt8=
Content-Type: application/json
Eski entegrasyonlar X-Merchant-Key, X-PayFerrari-Signature başlıkları ve gövdedeki merchant_key / payferrari_token alanları geriye dönük uyumluluk için kabul edilmeye devam eder. Yeni entegrasyonlarda X-Public-Key ve X-Signature kullanın.

Panelde IP kısıtlaması tanımlıysa yalnızca listedeki IPv4/IPv6 adreslerinden ve CIDR bloklarından gelen istekler kabul edilir; diğerleri 403 ip_denied alır.

İstek imzalama

Önerilen (zaman damgalı): İsteğe X-PayFerrari-Timestamp başlığını ekleyin ve imza dizgisini v2 etiketi, zaman damgası ve her alan yeni satır (\n) ile ayrılmış olacak şekilde oluşturun. Ayraç kullanımı dizgi belirsizliğini ortadan kaldırır; zaman damgası imzaya bağlandığı için yakalanan bir istek tekrar oynatılamaz.

message   = "v2\n" + timestamp + "\n" + alan1 + "\n" + alan2 + …
signature = Base64( HMAC-SHA256( secret_key, message ) )
Uç noktaİmza alanları (sırayla)
POST/v1/payments/initpublic_key, merchant_oid, payment_amount, email, currency, merchant_ok_url, merchant_fail_url
POST/v1/payments/statuspublic_key, merchant_oid
POST/v1/wallets/lookuppublic_key, wallet_id
POST/v1/payoutspublic_key, merchant_oid, payment_amount, wallet_id, wallet_name
  • payment_amount kuruş cinsinden tam sayıdır ve dizgiye ondalıksız yazılır (50000).
  • email küçük harfe çevrilmiş ve baştaki/sondaki boşlukları kırpılmış haliyle imzalanır; currency büyük harfle (TRY).
  • Cüzdan doğrulamada wallet_id yalnızca rakamlarına indirgenmiş haliyle imzalanır ("97 15 29" → 971529).
  • Diğer alanlar gövdede gönderdiğiniz değerle birebir aynı olmalıdır.
KontrolDavranış
Zaman penceresiZaman damgası sunucu saatinden en fazla ±5 dakika sapabilir; aksi halde 401 timestamp.
Tekrar korumasıHer imza yalnızca bir kez kabul edilir; aynı imza tekrar gelirse 409 replay.
Durum sorgulamaAynı işlemi tekrar sorgularken her istekte güncel bir zaman damgası gönderin; böylece imza her seferinde değişir.
Eski (zaman damgasız) şema X-PayFerrari-Timestamp göndermeyen mevcut entegrasyonlar için imza, alanların ayraç olmadan art arda eklenmesiyle oluşan dizginin HMAC-SHA256 Base64 halidir (HMAC(secret, public_key + alan1 + alan2 + …)) ve geriye dönük uyumluluk için kabul edilmeye devam eder. Yeni oluşturulan iş yerleri zaman damgalı imzayı zorunlu tutar.

Anahtar yenileme

Secret key'in sızdığından şüpheleniyorsanız veya periyodik olarak değiştirmek istiyorsanız panelde API anahtarları → Secret key'i yenile adımını kullanın. İşlem hesap şifrenizle onaylanır; public key değişmez.

Geçiş süresiDavranış
HemenEski secret ile imzalanan istekler anında 401 hash alır.
1 saat · 24 saat · 72 saatSüre boyunca hem eski hem yeni secret ile imzalanan istekler kabul edilir. Süre dolmadan panelden “Şimdi geçersiz kıl” diyebilirsiniz.
Webhook doğrulaması Yenilemeden sonra oluşturulan bildirimler yeni secret ile imzalanır; öncesinde kuyruğa girip tekrar denenen bildirimler eski secret ile imzalı kalır. Geçiş süresince webhook hash değerini iki anahtarla da doğrulayın.

Bağlantı testi

GET/v1/ping

Public key'i ve IP kısıtlamasını doğrular. İmza gerekmez; askıdaki iş yerleri için de yanıt döner.

curl https://payferrari.com/v1/ping \
  -H "X-Public-Key: $PF_PUBLIC_KEY"
{
  "status": "success",
  "service": "payferrari",
  "merchant": "Demo Mağaza",
  "sandbox": false,
  "time": "2026-10-12T12:00:00.000Z"
}

Yatırım başlatma

POST/v1/payments/init

Oyuncu bakiyesine yükleme için tek kullanımlık ödeme oturumu açar. Oyuncuyu yanıttaki payment_url adresine yönlendirin. Oturum 15 dakika geçerlidir (sandbox: 10 dakika).

Gövde

AlanTipAçıklama
merchant_oid zorunlustring ≤ 64Sizin sipariş numaranız; iş yeri içinde benzersiz.
payment_amount zorunluintegerKuruş cinsinden tutar (500,00 TL → 50000).
email zorunlustringOyuncunun e-posta adresi.
currencystringYalnızca TRY (varsayılan).
merchant_ok_url zorunluurlBaşarılı ödeme sonrası dönüş adresi.
merchant_fail_url zorunluurlBaşarısız / iptal dönüş adresi.
merchant_notify_url zorunluhttps urlWebhook adresi; herkese açık HTTPS olmalı.
user_basketarray[{ "name", "price", "qty" }]; price kuruş cinsinden birim fiyattır. Ödeme sayfasında kalem listesi olarak gösterilir.
{
  "merchant_oid": "DEP-10001",
  "payment_amount": 50000,
  "email": "oyuncu@site.com",
  "currency": "TRY",
  "merchant_ok_url": "https://siteniz.com/deposit/ok",
  "merchant_fail_url": "https://siteniz.com/deposit/fail",
  "merchant_notify_url": "https://siteniz.com/api/payferrari/notify",
  "user_basket": [{ "name": "Bakiye yükleme", "price": 50000, "qty": 1 }]
}

Yanıt

{
  "status": "success",
  "token": "W53_qCE6mXZ3WH2aWOqt1bzhF_4CeqfG",
  "payment_url": "https://payferrari.com/pay/W53_qCE6mXZ3WH2aWOqt1bzhF_4CeqfG",
  "expires_at": "2026-10-12T12:15:00.000Z",
  "sandbox": false
}

Siparişi yalnızca webhook status=success geldiğinde ve hash doğrulandıktan sonra “ödendi” işaretleyin. Dönüş URL'leri yalnızca kullanıcı deneyimi içindir.

İşlem durumu

POST/v1/payments/status

Yatırım veya çekim durumunu sorgular. Webhook gecikirse yedek kontrol olarak kullanın.

{ "merchant_oid": "DEP-10001" }

Yanıt

{
  "status": "success",
  "type": "deposit",
  "sandbox": false,
  "payment": {
    "merchant_oid": "DEP-10001",
    "status": "paid",
    "amount_kurus": 50000,
    "currency": "TRY",
    "paid_at": "2026-10-12T12:05:00.000Z",
    "notify_status": "delivered",
    "created_at": "2026-10-12T12:00:00.000Z",
    "expires_at": "2026-10-12T12:15:00.000Z"
  }
}
payment.statusAnlam
pendingOturum açık, ödeme bekleniyor
paidÖdeme tamamlandı
expiredOturum süresi doldu
refundedİş yeri tarafından iade edildi

Çekimlerde type = withdraw ve payment.status = paid döner. Kayıt yoksa 404 not_found.

Cüzdan doğrulama

POST/v1/wallets/lookup

Çekimden önce üye numarası ile ad soyadın eşleştiğini kontrol edin.

{
  "wallet_id": "971529",
  "wallet_name": "Ege Yılmaz"
}
{ "status": "success", "wallet_no": "971529", "match": true, "sandbox": false }

match: ad gönderilmezse null; gönderilirse büyük/küçük harf ve Türkçe karakter farkları yok sayılarak karşılaştırılır.

Oyuncu çekimi

POST/v1/payouts

İş yeri bakiyenizden oyuncunun PayFerrari cüzdanına anında aktarım yapar. Ad soyad eşleşmesi zorunludur.

AlanTipAçıklama
merchant_oid zorunlustring ≤ 64Çekim referansınız; benzersiz.
payment_amount zorunluintegerKuruş cinsinden tutar.
wallet_id zorunlustringOyuncunun PayFerrari üye numarası.
wallet_name zorunlustringCüzdan sahibinin ad soyadı.
merchant_notify_urlhttps urlBoşsa paneldeki bildirim adresi kullanılır.
{
  "merchant_oid": "WD-90001",
  "payment_amount": 25000,
  "wallet_id": "971529",
  "wallet_name": "Ege Yılmaz",
  "merchant_notify_url": "https://siteniz.com/api/payferrari/notify"
}
{ "status": "success", "merchant_oid": "WD-90001", "wallet_no": "971529", "sandbox": false }

Aynı cüzdana bir saat içinde ikinci çekim gönderilemez (429 rate_limit). Bakiye yetersizse 400 balance döner.

Webhook'lar

İşlem sonuçlandığında PayFerrari, merchant_notify_url adresine POST application/json gönderir. Her teslimat X-PayFerrari-Signature ve X-PayFerrari-Timestamp başlıklarını taşır; imza, ham gövdenin zaman damgasına bağlı HMAC-SHA256 Base64 halidir. Yanıtınız HTTP 2xx olmalı ve gövdesinde OK geçmelidir.

X-PayFerrari-Timestamp: 1760000000
X-PayFerrari-Signature: Base64( HMAC-SHA256( secret_key, timestamp + "." + ham_gövde ) )
X-PayFerrari-Event: deposit
{
  "merchant_oid": "DEP-10001",
  "status": "success",
  "payment_amount": 50000,
  "currency": "TRY",
  "hash": "Base64(HMAC-SHA256(secret_key, merchant_oid + status + payment_amount))",
  "type": "deposit"
}
  • Doğrulamayı X-PayFerrari-Signature başlığıyla yapın (önerilen); gövdedeki hash alanı geriye dönük uyumluluk içindir.
  • Zaman damgası ±5 dakikadan eski ise bildirimi reddedin (tekrar koruması).
  • type: yatırımda deposit, oyuncu çekiminde withdraw.
  • metadata (isteğe bağlı): panelden oluşturduğunuz ödeme bağlantısına eklediğiniz ek bilgiler varsa anahtar/değer nesnesi olarak aynen iletilir.
  • Zaman aşımı 2,5 saniyedir. Başarısız teslimatlar üstel artan aralıklarla (en fazla 5 dakikada bir) yeniden denenir.
  • Aynı bildirim birden fazla gelebilir; merchant_oid bazında idempotent işleyin.
  • Teslimat kayıtlarını, test gönderimini ve manuel yeniden denemeyi panelde Webhook'lar sayfasında bulabilirsiniz.
import crypto from "node:crypto";
import express from "express";

const SECRET_KEY = process.env.PF_SECRET_KEY;
const app = express();

// İmza ham gövde üzerinden doğrulanır: express.raw kullanın
app.post("/api/payferrari/notify", express.raw({ type: "*/*" }), (req, res) => {
  const ts = req.get("X-PayFerrari-Timestamp") || "";
  const sig = req.get("X-PayFerrari-Signature") || "";
  const raw = req.body; // Buffer
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.status(401).send("timestamp");
  const expected = crypto.createHmac("sha256", SECRET_KEY)
    .update(`${ts}.${raw}`).digest("base64");
  const a = Buffer.from(sig), b = Buffer.from(expected);
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.status(401).send("sign");

  const { merchant_oid, status } = JSON.parse(raw.toString("utf8"));
  // merchant_oid için idempotent: daha önce işlendiyse yalnızca OK dönün
  res.type("text/plain").send("OK");
});

Tutarlar ve limitler

  • Tüm tutarlar kuruş cinsindendir: 500,00 TL → 50000. Para birimi TRY.
  • Minimum yatırım ve minimum çekim tutarları iş yerinize özeldir (panel → Güvenlik ve ayarlar → Hesap bilgileri).
  • merchant_oid iş yeri içinde benzersizdir; tekrar gönderimde 409 duplicate_oid döner.
  • IP başına dakikalık istek limitleri: init 120, status 180, lookup 60, payouts 80. Aşımda 429 rate_limit.

Hata kodları

Hatalı isteklerde gövde { "status": "failed", "reason": "…" } biçimindedir; bazı hatalarda açıklayıcı bir message alanı da bulunur.

HTTPreasonAnlam
401merchantPublic key eksik veya geçersiz
401merchant_statusİş yeri askıya alınmış
401hashİmza hatalı veya eski secret'ın geçiş süresi dolmuş
401timestampZaman damgası eksik, hatalı veya ±5 dakika penceresinin dışında
409replayAynı imza daha önce kullanılmış (tekrar oynatma)
403ip_deniedİstek IP'si kısıtlama listesinde değil
400paramsEksik veya hatalı alan
400notify_urlBildirim adresi herkese açık HTTPS değil
400currencyDesteklenmeyen para birimi
400min_deposit · min_withdrawTutar iş yeri limitinin altında
400balanceİş yeri bakiyesi çekim için yetersiz
400name_mismatchAd soyad cüzdan sahibiyle eşleşmedi
404walletCüzdan bulunamadı
404not_foundİşlem bulunamadı
409duplicate_oidmerchant_oid daha önce kullanılmış
429rate_limitİstek limiti aşıldı / aynı cüzdana kısa sürede çekim

Örnek kod

Anahtarları PF_PUBLIC_KEY ve PF_SECRET_KEY ortam değişkenlerinde saklayın. Örnek, yatırım oturumu açar.

import crypto from "node:crypto";

const PUBLIC_KEY = process.env.PF_PUBLIC_KEY; // pk_live_…
const SECRET_KEY = process.env.PF_SECRET_KEY; // sk_live_… yalnızca sunucuda
const sign = (ts, ...fields) =>
  crypto.createHmac("sha256", SECRET_KEY).update(["v2", ts, ...fields].join("\n")).digest("base64");

const order = {
  merchant_oid: `DEP-${Date.now()}`,
  payment_amount: 50000, // 500,00 TL
  email: "oyuncu@site.com",
  currency: "TRY",
  merchant_ok_url: "https://siteniz.com/deposit/ok",
  merchant_fail_url: "https://siteniz.com/deposit/fail",
  merchant_notify_url: "https://siteniz.com/api/payferrari/notify",
};

const ts = Math.floor(Date.now() / 1000);
const res = await fetch("https://payferrari.com/v1/payments/init", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Public-Key": PUBLIC_KEY,
    "X-PayFerrari-Timestamp": String(ts),
    "X-Signature": sign(ts, PUBLIC_KEY, order.merchant_oid, order.payment_amount, order.email,
      order.currency, order.merchant_ok_url, order.merchant_fail_url),
  },
  body: JSON.stringify(order),
});
const data = await res.json();
if (data.status !== "success") throw new Error(data.reason);
// Oyuncuyu data.payment_url adresine yönlendirin

Canlıya geçiş

  1. Panelde API anahtarları sayfasından public key'i kopyalayın; secret key'i şifrenizle görüntüleyip sunucu ortam değişkenine kaydedin.
  2. GET /v1/ping ile public key'i ve IP kısıtlamasını doğrulayın.
  3. Bildirim adresinizi HTTPS olarak kaydedin ve panelden test webhook'u gönderin.
  4. Üretim sunucusu IP'lerinizi panelde IP kısıtlamasına ekleyin.
  5. Yatırım: init → yönlendirme → webhook hash doğrulama → bakiye yükleme (merchant_ok_url yalnızca kullanıcı deneyimi içindir).
  6. Çekim: lookup (match=true) → payouts → webhook → site bakiyesinden düşme.
  7. merchant_oid değerlerini kendi tarafınızda benzersiz ve idempotent saklayın.
  8. Secret key'i düzenli olarak geçiş süreli yenileyin.

Destek: iş yeri paneli · kurumsal