WinPay Entegrasyon BelgeleriEnglish →

WinPay API — Entegrasyon Kılavuzu

Bu belge, WinPay ödeme yöntemini kendi sitesine bağlayacak geliştiriciler içindir. Yalnızca API sözleşmesini anlatır: üç uç, imza, sonuç bildirimi, durumlar ve hatalar.

Sürüm: v1 · Taban adres: https://api.win-pay.co · English version


1. Genel bakış

WinPay bir ödeme yöntemidir: sizin adınıza tahsilat ve ödeme yapar, sonucu imzalı bir bildirimle (callback) size iletir. Üyenizin bakiyesini siz yönetirsiniz.

Ne Metot ve yol
Yatırım talebi aç POST /api/v1/deposits/init
Çekim talebi aç POST /api/v1/withdrawals/init
Sonuç bildirimi POSTsizin HTTPS adresinize

Yatırım: siteniz talep açar → yanıttaki payment_url adresine üyeyi yönlendirir → üye ödeme sayfasında tutarı seçer, IBAN ve QR ile transferi yapar → ödeme doğrulanınca size callback gelir → siteniz bakiyeyi artırır.

Çekim: siteniz talep açar (üyenin IBAN'ı ile) → transfer yapılır → size callback gelir → siteniz çekimi tamamlanmış işaretler.

Önemli: 201 yanıtı "para geldi/gitti" demek değildir, "talep oluşturuldu" demektir. Bakiyeyi yalnızca callback geldiğinde değiştirin.

İşlem durumu sorgulayan bir uç yoktur. Sonucu callback ile alırsınız; kaybolduğunu düşünüyorsanız aynı request_id ile talebi tekrar gönderin — mevcut işlem döner (bölüm 8).


2. Erişim bilgileri

Size verilecekler:

Değer Ne için
Taban adres https://api.win-pay.co
api_key Sitenizi tanıtır — X-API-Key başlığında gönderilir
api_secret İsteklerinizi imzalamak için
callback_secret Gelen bildirimi doğrulamak için (farklı bir değerdir)

Sizin vermeniz gerekenler:

Değer Not
Callback URL'iniz HTTPS zorunlu, yönlendirme yapmamalı. Örn. https://api.sitem.com/odeme/callback
Sunucunuzun çıkış IP'si IP izin listesine eklenir. Birden fazlaysa hepsini bildirin

Güvenlik kuralları


3. Kimlik doğrulama ve imza

Her istekte üç katman vardır:

Katman Nasıl
Site kimliği X-API-Key başlığı
IP kontrolü Çıkış IP'niz izin listesinde olmalı
İstek imzası X-WinPay-Signature — HMAC-SHA256

İmza nasıl hesaplanır

imzalanan_metin = <unix_saniye> + "." + <istek gövdesinin ham baytları>
imza            = HMAC_SHA256(imzalanan_metin, api_secret)  →  64 haneli küçük harf hex

Gönderilecek başlıklar:

X-API-Key:           <api_key>
X-WinPay-Timestamp:  1789305600
X-WinPay-Signature:  3f2a9c...64 hane...b1
Content-Type:        application/json

Zaman penceresi ±300 saniyedir. Saatiniz 5 dakikadan fazla kayarsa istekler reddedilir.

⚠️ En sık yapılan hata: gövdeyi bir kez oluşturup aynı string'i hem imzalayın hem gönderin. JSON'u ayrıştırıp yeniden üretirseniz anahtar sırası değişebilir ve imza tutmaz. İmza gönderdiğiniz baytların üzerinden doğrulanır.


4. Yatırım

4.1 Talep

POST /api/v1/deposits/init
{
  "request_id": "dep-20260910-000001",
  "member_id": "uye-42",
  "member_name": "Ali Veli",
  "amount": "1000.00",
  "is_fast": false
}
Alan Tür Zorunlu Kural
request_id string 1–100 karakter; harf, rakam, . _ : -. Her talepte benzersiz
member_id string Boş olmayan metin, en fazla 100. Sayısal ID'yi de string gönderin
member_name string En fazla 150 karakter
amount string Pozitif, noktalı ondalık, en fazla 2 basamak: "1000.00"
is_fast boolean FAST transfer talebi

Kabul edilmeyen tutarlar: "1.000,00", -100, 0, "1.001", 1e3, "abc"400.

Banka hesabını ve IBAN'ı siz seçmezsiniz; sistem tutara uygun hesabı belirler. Siteniz gövdeden değil, API anahtarından tanınır.

4.2 Yanıt — 201

{
  "success": true,
  "data": {
    "transaction_id": 201,
    "payment_url": "https://api.win-pay.co/pay/<64-hane-token>",
    "amount": "1000.00",
    "status": "pending_payment",
    "expires_at": "2026-09-10T17:00:00.000Z"
  }
}
Alan Tür Not
transaction_id integer WinPay işlem numarası — saklayın
payment_url string Üyeyi buraya yönlendirin
amount string Kabul edilen tutar
status string Oluşturulduğunda her zaman pending_payment
expires_at string ISO-8601 UTC, 20 dakika sonrası
idempotent boolean Yalnızca aynı request_id ile tekrarda gelir

4.3 Üyeyi yönlendirin

Süre: 20 dakika. Bu süre içinde onaylanmayan talep expired olur.

Tutar değişebilir. Üye ödeme sayfasında sizin gönderdiğinizden farklı bir tutar seçebilir. Bakiyeye yazacağınız tutar callback'teki approved_amount olmalıdır — ilk amount değil.


5. Çekim

POST /api/v1/withdrawals/init
{
  "request_id": "wd-20260910-000001",
  "member_id": "uye-42",
  "member_name": "Ali Veli",
  "amount": "500.00",
  "iban": "TR330006100519786457841326",
  "account_name": "ALI VELI"
}

Yatırımla aynı alanlar, is_fast yok, iki ek zorunlu alan var:

Alan Tür Zorunlu Kural
iban string TR IBAN, 26 karakter, mod-97 kontrolü yapılır
account_name string Boş olmayan metin, en fazla 150

Yanıt — 201:

{
  "success": true,
  "data": {
    "transaction_id": 202,
    "status": "pending_assignment",
    "amount": "500.00"
  }
}

Çekimde payment_url dönmez. Sonucu callback ile alırsınız.

IBAN doğrulaması biçimi kontrol eder, hesabın kime ait olduğunu kanıtlamaz. Hesabın üyeye ait olduğunu doğrulamak ve çekilebilir bakiyeyi bloke etmek/iade etmek sizin tarafınızdadır.


6. Sonuç bildirimi (callback)

Entegrasyonun en kritik kısmı. Para burada kesinleşir.

6.1 Gelen istek

POST https://api.sitem.com/odeme/callback
Content-Type: application/json
X-WinPay-Signature: <64 haneli hex HMAC-SHA256>
X-WinPay-Event-Id: winpay:901
{
  "event_id": "winpay:901",
  "event": "transaction.approved",
  "transaction_id": 201,
  "request_id": "dep-20260910-000001",
  "type": "deposit",
  "amount": "1000.00",
  "approved_amount": "950.00",
  "status": "approved",
  "member_id": "uye-42",
  "timestamp": "2026-09-10T16:45:00.000Z"
}
Alan Tür Not
event_id string winpay:<n>mükerrer kontrol anahtarınız
event string transaction.approved / .rejected / .completed / .expired
transaction_id integer Talep yanıtındaki numara
request_id string Sizin gönderdiğiniz request_id
type string deposit veya withdrawal
amount string İlk talep edilen tutar
approved_amount string Gerçekten hareket eden tutar — bunu kullanın
status string Bölüm 6.3'teki tablo
member_id string Gönderdiğiniz üye kimliği
timestamp string ISO-8601 UTC, bildirimin hazırlandığı an

Callback imzasında zaman damgası öneki yoktur (isteklerinizden farklı) ve api_secret değil callback_secret kullanılır: HMAC_SHA256(ham gövde, callback_secret).

6.2 Doğrulama — sırayla yapın

  1. İmzayı ham gövde baytları üzerinden doğrulayın. JSON'u parse edip yeniden stringify etmeyin. Karşılaştırmayı sabit süreli yapın (crypto.timingSafeEqual, hash_equals, hmac.compare_digest).
  2. Gövdedeki event_id ile X-WinPay-Event-Id başlığı aynı olmalı.
  3. request_id sizin açtığınız, o üyeye ait bir talep olmalı.
  4. event_id değerini UNIQUE indeksli bir kolona yazın. Zaten varsa 2xx dönüp durun, ikinci bir mali hareket oluşturmayın.
  5. Bakiye değişikliğini aynı veritabanı transaction'ında uygulayın.
  6. Commit ettikten sonra 2xx dönün. Gövde okunmaz.

6.3 Hangi durumda ne yapmalı

type status Yapılacak
deposit approved Üyeye approved_amount kadar bakiye yaz. Kesin
deposit rejected Bakiye değişmez
deposit expired Bakiye değişmez
withdrawal completed Çekimi tamamlanmış işaretle. İkinci bir ödeme emri değildir. Kesin
withdrawal rejected Bloke ettiğiniz tutarı üyeye iade edin

6.4 Teslimat davranışı

Konu Değer
Zaman aşımı 10 saniye
Yönlendirme (redirect) Takip edilmez
Okunan en büyük yanıt 64 KB
Başarısız olursa Artan aralıklarla tekrar denenir, en fazla 1 saat arayla
Tekrarlarda event_id ve gövde bayt bayt aynı kalır
Protokol Yalnız HTTPS

Tekrarlar saatler sonra gelebilir. Sırf zaman geçti diye geçerli bir bildirimi atmayın; mükerrer korumasını kalıcı event_id kaydıyla yapın, zaman penceresiyle değil.


7. Durum kodları

yatırım:  pending_payment  →  pending_approval  →  approved
                                               →  rejected
                           →  expired (20 dk)

çekim:    pending_assignment  →  assigned  →  processing  →  completed
                                                          →  rejected
Durum Anlamı
pending_payment Talep açıldı, üye henüz ödeme bildirmedi
pending_approval Üye "ödedim" dedi — para geldi demek değildir
approved Ödeme doğrulandı. Kesin, geri dönüşü yok
pending_assignment Çekim talebi alındı
assigned / processing İşleme alındı
completed Transfer yapıldı. Kesin
rejected / expired Para hareketi olmadı

Yalnızca approved (yatırım) ve completed (çekim) başarı demektir. Yatırım hiçbir zaman completed olmaz.


8. Tekrar eden istekler

Tekrar anahtarı: site + işlem türü + request_id

Durum Sonuç
Aynı request_id, aynı gövde Mevcut işlem döner, data içinde "idempotent": true. Yeni işlem oluşmaz
Aynı request_id, farklı gövde 409 CONFLICT
request_id ile Idempotency-Key başlığı farklı 400

Zaman aşımı aldıysanız yeni bir request_id üretmeyin; aynı request_id ile tekrar gönderin — sorgu gibi davranır ve mevcut işlemi döner.

Idempotency-Key başlığını da kullanabilirsiniz; gövdedeki request_id ile aynı olmalıdır.

Tekrar yanıtında expires_at bulunmayabilir. Yatırım ve çekim referansları ayrı sayılır, yine de dep- / wd- gibi ayırt edici önekler kullanın.


9. Hata kodları

Tüm hatalar aynı biçimdedir. code ile dallanın, message ile değil — mesajlar Türkçe, insan içindir ve değişebilir.

{ "success": false, "code": "BAD_REQUEST", "message": "Açıklama" }
HTTP code Anlamı Yapılacak
400 BAD_REQUEST Biçim, tutar, IBAN, referans veya limit hatası Veriyi düzeltin. Kör tekrar etmeyin
400 INVALID_JSON Gövde geçerli JSON değil Serileştiriciyi düzeltin
401 UNAUTHORIZED API anahtarı geçersiz / imza tutmuyor / zaman damgası pencere dışı Kimlik ve saat kontrolü
403 FORBIDDEN IP izinli değil, kara liste veya site kapalı IP listesini bizimle kontrol edin
404 NOT_FOUND Yanlış adres Yolu doğrulayın
409 CONFLICT Aynı request_id, farklı gövde Mevcut işlemi inceleyin, yeni hareket açmayın
429 TX_RATE_LIMIT init uçları — dakikada 600, API anahtarı başına Üstel geri çekilme
429 RATE_LIMIT Diğer /api/ yolları — dakikada 200, IP başına Üstel geri çekilme
503 SERVICE_UNAVAILABLE Tutara uygun hesap yok veya bakım Üyeye gösterin; sürerse bize bildirin
500 INTERNAL_ERROR Beklenmeyen hata Aynı request_id ile bir kez tekrar

Sınırlı uçlarda RateLimit-* yanıt başlıkları gönderilir.

Gerçek hata yanıtları

Gönderilen Yanıt
"amount": "1.000,00" 400 · Tutar: ondalık ayırıcı nokta, en fazla iki kuruş hanesi kullanın
"amount": "100.001" 400 · aynı mesaj
member_id yok 400 · member_id gerekli
Kontrol basamağı hatalı IBAN 400 · IBAN kontrol basamakları hatalı
Sitenizin limitini aşan tutar 400 · Tutar sitenin yatırım limitleri dışında
Yanlış imza 401 · Istek imzasi dogrulanamadi
İmza başlıkları eksik 401 · X-WinPay-Signature basligi gerekli (64 haneli hex HMAC-SHA256)
Saat 300 sn'den fazla kaymış 401 · Istek zaman damgasi 300 saniyelik pencerenin disinda; sunucu saatini kontrol edin
IP izinli değil 403 · Bu IP site API erişimine izinli değil
Üye veya IBAN kara listede 403 · İşlem kabul edilmedi
Tutara uygun hesap yok 503 · Bu tutar için uygun IBAN yok; tutarı veya zamanı değiştirin

10. İstek sınırları

Sınır Anahtar
/deposits/init, /withdrawals/init 600 istek / dakika API anahtarı başına — diğer siteler sizi etkilemez
Diğer /api/ yolları 200 istek / dakika IP başına

Aşarsanız 429 alırsınız; üstel geri çekilme (exponential backoff) uygulayın.


11. Örnek istemci kodu

Node.js

const crypto = require('crypto');

async function winpayCall(path, payload) {
  const body = JSON.stringify(payload);              // BİR KEZ üret
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const signature = crypto
    .createHmac('sha256', process.env.WINPAY_API_SECRET)
    .update(timestamp).update('.').update(Buffer.from(body, 'utf8'))
    .digest('hex');

  const res = await fetch(process.env.WINPAY_BASE_URL + path, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': process.env.WINPAY_API_KEY,
      'X-WinPay-Timestamp': timestamp,
      'X-WinPay-Signature': signature,
    },
    body,                                            // AYNI string
  });
  return { status: res.status, data: await res.json() };
}

// Callback doğrulama (Express — ham gövde şart)
app.post('/odeme/callback',
  express.raw({ type: 'application/json' }),
  async (req, res) => {
    const expected = crypto
      .createHmac('sha256', process.env.WINPAY_CALLBACK_SECRET)
      .update(req.body)                              // Buffer, parse edilmemiş
      .digest('hex');
    const got = req.get('X-WinPay-Signature') || '';
    if (got.length !== expected.length ||
        !crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString('utf8'));
    if (event.event_id !== req.get('X-WinPay-Event-Id')) return res.sendStatus(400);

    await db.transaction(async (t) => {
      const [, created] = await t.findOrCreate({ where: { event_id: event.event_id } });
      if (!created) return;                          // zaten işlenmiş
      if (event.type === 'deposit' && event.status === 'approved') {
        await creditMember(t, event.member_id, event.approved_amount);
      }
    });
    res.sendStatus(200);
  });

PHP

<?php
function winpay_call(string $path, array $payload): array {
    $body      = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $timestamp = (string) time();
    $signature = hash_hmac('sha256', $timestamp . '.' . $body, getenv('WINPAY_API_SECRET'));

    $ch = curl_init(getenv('WINPAY_BASE_URL') . $path);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => $body,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 20,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'X-API-Key: '          . getenv('WINPAY_API_KEY'),
            'X-WinPay-Timestamp: ' . $timestamp,
            'X-WinPay-Signature: ' . $signature,
        ],
    ]);
    $response = curl_exec($ch);
    $status   = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return ['status' => $status, 'data' => json_decode($response, true)];
}

// Callback doğrulama
$raw      = file_get_contents('php://input');        // HAM gövde
$expected = hash_hmac('sha256', $raw, getenv('WINPAY_CALLBACK_SECRET'));
$got      = $_SERVER['HTTP_X_WINPAY_SIGNATURE'] ?? '';
if (!hash_equals($expected, $got)) { http_response_code(401); exit; }
$event = json_decode($raw, true);

Python

import hmac, hashlib, json, time, os, requests

def winpay_call(path, payload):
    body = json.dumps(payload, separators=(",", ":"), ensure_ascii=False)
    timestamp = str(int(time.time()))
    signature = hmac.new(
        os.environ["WINPAY_API_SECRET"].encode(),
        f"{timestamp}.".encode() + body.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()

    return requests.post(
        os.environ["WINPAY_BASE_URL"] + path,
        data=body.encode("utf-8"),
        headers={
            "Content-Type": "application/json",
            "X-API-Key": os.environ["WINPAY_API_KEY"],
            "X-WinPay-Timestamp": timestamp,
            "X-WinPay-Signature": signature,
        },
        timeout=20,
    )

# Callback doğrulama
def verify(raw_body: bytes, header_signature: str) -> bool:
    expected = hmac.new(
        os.environ["WINPAY_CALLBACK_SECRET"].encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, header_signature)

12. Uygulamanızı çevrimdışı doğrulayın

Aşağıdakiler yalnızca test değerleridir, üretimde kullanılmaz. Gerçek erişim bilgileri verilmeden önce HMAC kodunuzu bunlarla sınayabilirsiniz.

api_key         = wp_test_key_0001
api_secret      = test_api_secret_do_not_use_in_production
callback_secret = test_callback_secret_do_not_use_in_production

İstek imzası — bu zaman damgası ve gövde tam olarak bu imzayı üretmelidir:

timestamp = 1789041098
body      = {"request_id":"dep-20260910-000001","member_id":"member-42","member_name":"Ali Veli","amount":"1000.00","is_fast":false}
signature = e55ffec5d4ea23e78d86048bbd2d778349dd69efa0ea8c5961857d4ee1bba4fe

Callback doğrulaması — bu gövde tam olarak bu imzayı üretmelidir:

body      = {"event_id":"winpay:1","event":"transaction.approved","transaction_id":1,"request_id":"dep-20260910-000001","type":"deposit","amount":"1000.00","approved_amount":"950.00","status":"approved","member_id":"member-42","timestamp":"2026-09-10T11:51:38.941Z"}
signature = 9b2182c5a8155721a85bb246497c9d6a3d70c2b2c431bae1ab0fafdb23bf9aa7

Gerçek erişim bilgileriyle duman testi:

API_SECRET='<api_secret>'
API_KEY='<api_key>'
BASE='https://api.win-pay.co'

BODY='{"request_id":"dep-test-0001","member_id":"member-42","member_name":"Ali Veli","amount":"1000.00"}'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$API_SECRET" -r | cut -d' ' -f1)

curl -i -X POST "$BASE/api/v1/deposits/init" \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $API_KEY" \
  -H "X-WinPay-Timestamp: $TS" \
  -H "X-WinPay-Signature: $SIG" \
  --data-raw "$BODY"

Aynı request_id ile iki kez çalıştırın: ikinci çağrı aynı transaction_id ile "idempotent": true dönmelidir.


13. Canlıya çıkmadan önce kontrol listesi

Kimlik ve imza

Talep

Tekrar

Callback

Uçtan uca


14. Sık yapılan hatalar

Hata Sonuç Doğrusu
Gövdeyi imzaladıktan sonra yeniden oluşturmak Sürekli 401 Bir kez üretin, aynısını gönderin
Callback'te JSON'u parse edip yeniden stringify etmek İmza tutmaz Ham baytlar üzerinden doğrulayın
amount ile bakiye yazmak Yanlış tutar approved_amount kullanın
201 gelince bakiye yazmak Para gelmeden kredi Yalnız callback'te yazın
Aynı event_id için tekrar bakiye yazmak Çift kredi event_id benzersiz kaydı tutun
Zaman aşımında yeni request_id ile tekrar Çift talep Aynı request_id ile tekrar
withdrawal/completed görünce tekrar ödeme yapmak Çift ödeme Bu bir bildirimdir, emir değil
API anahtarını tarayıcıya koymak Anahtar sızar Yalnız sunucuda
Sunucu saatini NTP'siz bırakmak Rastgele 401 NTP kurun
request_id tekrar kullanmak 409 Her talepte benzersiz üretin

Destek

Sorun bildirirken şunları hazırlayın: request_id, isteğin tam zamanı (saat dilimiyle), aldığınız HTTP kodu ve code alanı, sunucunuzun çıkış IP'si.

Sır göndermeyinapi_key, api_secret veya callback_secret değerlerini mesaja, ekran görüntüsüne veya loga yazmayın.