Banka Hesapları
API Referansı
Para Transferi

Para Transferi

Bu uçları kim çağırır?Dış API (s2s)
Taban yol
/api/v1/bank-connect
Kimlik
Her istekte Ed25519 istek imzası — X-Key-Id, X-Date, X-Nonce, X-Signature. Zaman penceresi ±5 dk, nonce tek kullanımlık. Bearer jetonu ya da panel oturumu bu uçlarda kabul edilmez (401 AUTH_HEADERS_MISSING).
Yetki
Anahtarınız Banka Bağlantıları ürününü (BANK_CONNECT) ve bu sayfadaki uçların yetkisini taşımalıdır: bank-connect:transfer. Ürünü olmayan anahtar 403 PRODUCT_NOT_ENABLED, yetkisi olmayan anahtar 403 API_INSUFFICIENT_SCOPE alır. Bkz. API'ye Giriş › Yetkiler.
Çapraz yüzey
Bu uçlar s2s yüzeyindedir; kendi sunucunuzdan imzalı istekle çağırırsınız. Aynı verinin panelde ekranları var, ama panel uçları API anahtarıyla çağrılamaz; bu uçlar da panel oturumuyla çağrılamaz. Bankayı bağlama ve kesme yalnız panelde yapılır.

Bağlantınız transfer destekliyorsa bağlı banka hesabınızdan bir IBAN'a para gönderebilirsiniz. Bir bağlantının transfer destekleyip desteklemediğini Bağlantılar ucundaki capabilities alanından okursunuz: listede OUTGOING_TRANSFER varsa bu bağlantının hesaplarından transfer yapılabilir.

API anahtarıyla gönderilen transferde e-posta doğrulama kodu istenmez. Onay; anahtarınızın yetkisi, isteğin imzası ve isteğin IP izin listesindeki bir adresten gelmesidir. Transfer oluşturulduğu anda SUBMITTED durumuna geçer ve bankaya iletilmek üzere sıraya alınır. Transferin hangi yoldan gideceğini (hesaplarınız arası, aynı banka içi, FAST ya da EFT) banka belirler.

⚠️

IP izin listesi zorunludur. bank-connect:transfer yetkisi yalnız IP izin listesi tanımlı bir API anahtarına verilebilir ve istek bu listedeki bir IP adresinden gelmelidir. Listesi boş anahtar 403 bank_connect.auth.ip_allowlist_required, listede olmayan bir adresten gelen istek 403 bank_connect.auth.ip_not_allowed alır. Bu kural okuma uçları dahil bu sayfadaki bütün uçlarda geçerlidir. → API'ye Giriş › IP izin listesi

Uçlar

YöntemYolAçıklama
GET/transfer-purposes?connectionId=…Bankanın kabul ettiği ödeme amaçları
POST/transfersTransfer oluşturur. Idempotency-Key zorunlu.
GET/transfersTransferleri listeler
GET/transfers/{transferId}Tek transfer

Bütün uçların yetkisi bank-connect:transfer'dır. Ortamı anahtarınız belirler; istekte environment gönderilmez.

Gövdeli isteği imzalama

Okuma uçlarının imzası API'ye Giriş sayfasındaki gibidir. POST isteklerinde gövdenin SHA-256 özeti Digest başlığında gönderilir ve kanonik dizinin son satırı olur. Bu sayfadaki cURL örnekleri aşağıdaki kabuk fonksiyonunu kullanır:

# lunixi-sign.sh — Kullanım: source lunixi-sign.sh; lunixi_sign POST "/api/v1/bank-connect/transfers" "$BODY"
LUNIXI_KEY_ID="mk_test_..."
LUNIXI_KEY_FILE="lunixi-signing.key"
 
lunixi_sign() {
  local method="$1" req_path="$2" body="$3"
  DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
  NONCE=$(openssl rand -hex 16)
  if [ -n "$body" ]; then
    DIGEST="SHA-256=$(printf '%s' "$body" | openssl dgst -sha256 -binary | base64)"
    printf '%s\n%s\nX-Date:%s\nX-Nonce:%s\nDigest:%s' "$method" "$req_path" "$DATE" "$NONCE" "$DIGEST" > canonical.txt
  else
    DIGEST=""
    printf '%s\n%s\nX-Date:%s\nX-Nonce:%s' "$method" "$req_path" "$DATE" "$NONCE" > canonical.txt
  fi
  SIGNATURE=$(openssl pkeyutl -sign -inkey "$LUNIXI_KEY_FILE" -rawin -in canonical.txt | base64 | tr -d '\n')
}

Gövdeyi tek satırlık (boşluksuz) JSON olarak gönderin ve özeti tam olarak gönderdiğiniz metinden hesaplayın. Kurallar: Kimlik Doğrulama › Digest başlığının biçimi.

PHP örnekleri PHP SDK'nın 0.22.0 ve üstü sürümünü kullanır; $bankConnect->transfers() bu sayfadaki uçları çağırır ve her isteği imzalar.

Ödeme amaçları

GET /api/v1/bank-connect/transfer-purposes?connectionId={connectionId}

Bankanın bu bağlantıda kabul ettiği ödeme amacı kodlarını döner. Transferde paymentPurpose alanına bu kodlardan birini yazabilirsiniz; alanı göndermezseniz bankanın varsayılanı kullanılır.

{
  "status": "success",
  "code": "SUCCESS",
  "data": {
    "items": [
      { "code": "OTHER", "name": "Diğer", "order": 1 },
      { "code": "RENT", "name": "Kira", "order": 2 }
    ]
  },
  "statusCode": 200,
  "requestId": "00000000-0000-0000-0000-000000000000"
}

Kodlar bankaya göre değişir; listeyi sabit kodlamayın, bu uçtan okuyun.

Transfer oluşturma

POST /api/v1/bank-connect/transfers

BaşlıkDeğer
Idempotency-KeyZorunlu. 8–200 yazdırılabilir ASCII karakter (boşluksuz). → Idempotency-Key
Content-Typeapplication/json
Gövde alanıTipZorunluAçıklama
accountIdstring (UUID)EvetParanın çıkacağı hesap. Hesabın bağlantısı OUTGOING_TRANSFER desteklemelidir.
receiverIbanstringEvetAlıcının TR IBAN'ı. Boşluklu ya da boşluksuz yazılabilir; kontrol hanesi doğrulanır.
receiverNamestringEvetAlıcının adı ya da unvanı, 2–140 karakter.
amountMinorintegerEvetTutar, minor birimde (kuruş), sıfırdan büyük tam sayı: 150075 = ₺1.500,75.
currencystringHayırISO 4217. Varsayılan ve bugün desteklenen tek değer TRY.
paymentPurposestringHayırÖdeme amaçları ucundan bir kod.
descriptionstringHayırAçıklama, en çok 140 karakter.

Gövdede başka bir alan (environment, merchantId …) gönderilirse istek 400 VALIDATION_ERROR alır.

source lunixi-sign.sh
REQ_PATH="/api/v1/bank-connect/transfers"
BODY='{"accountId":"00000000-0000-0000-0000-000000000100","receiverIban":"TR000000000000000000000000","receiverName":"Örnek Tedarik A.Ş.","amountMinor":150075,"description":"Fatura 2026-0042"}'
lunixi_sign POST "$REQ_PATH" "$BODY"
 
curl -X POST "https://api-gateway.lunixi.com$REQ_PATH" \
  -H "Content-Type: application/json" \
  -H "X-Key-Id: $LUNIXI_KEY_ID" \
  -H "X-Date: $DATE" \
  -H "X-Nonce: $NONCE" \
  -H "Digest: $DIGEST" \
  -H "X-Signature: $SIGNATURE" \
  -H "Idempotency-Key: payout-2026-0042" \
  --data-raw "$BODY"

Örnekteki IBAN yer tutucudur; kontrol hanesi geçerli bir IBAN gönderin.

Başarılı yanıt 201 döner:

{
  "status": "success",
  "code": "SUCCESS",
  "data": {
    "transfer": {
      "id": "00000000-0000-0000-0000-000000002000",
      "connectionId": "00000000-0000-0000-0000-000000000010",
      "accountId": "00000000-0000-0000-0000-000000000100",
      "environment": "live",
      "status": "SUBMITTED",
      "rail": null,
      "amountMinor": 150075,
      "currency": "TRY",
      "sourceIbanMasked": "TR00 **** **** **** **** **00 00",
      "receiverIbanMasked": "TR00 **** **** **** **** **00 00",
      "receiverNameMasked": "Ö**** T****** A.Ş.",
      "receiverBankCode": "0000",
      "paymentPurpose": null,
      "description": "Fatura 2026-0042",
      "idempotencyKey": "payout-2026-0042",
      "failureCode": null,
      "outcomeUnknown": false,
      "initiatedVia": "API",
      "sanctionStatus": "NOT_SCREENED",
      "bankTransactionId": null,
      "bankState": null,
      "createdAt": "2026-01-01T07:00:00.000Z",
      "submittedAt": "2026-01-01T07:00:00.000Z",
      "completedAt": null,
      "failedAt": null,
      "returnedAt": null,
      "canceledAt": null,
      "otpExpiresAt": null
    },
    "stepUp": null,
    "replayed": false
  },
  "statusCode": 201,
  "requestId": "00000000-0000-0000-0000-000000000000"
}
AlanAçıklama
transferOluşan transfer; alanları aşağıda.
stepUpAPI anahtarıyla yapılan isteklerde her zaman null (doğrulama kodu yoktur).
replayedtrue: aynı Idempotency-Key ve aynı gövdeyle daha önce oluşturulmuş transfer döndü; yeni transfer açılmadı.

Lunixi bakiye kontrolü yapmaz; bakiyenin yeterli olup olmadığına banka karar verir. Bakiye yetmezse transfer FAILED olur ve failureCode bank_connect.transfer.insufficient_balance döner.

Idempotency-Key

Aynı transferin iki kez gönderilmemesi için her transfer isteği bir Idempotency-Key taşır. Anahtar; organizasyonunuz ve ortam içinde tekildir.

DurumSonuç
Yeni anahtarYeni transfer oluşturulur, replayed: false.
Aynı anahtar, aynı gövdeYeni transfer açılmaz; mevcut transfer replayed: true ile döner.
Aynı anahtar, farklı gövde409 bank_connect.transfer.idempotency_conflict.
Başlık yok400 bank_connect.transfer.idempotency_key_required.
Biçim dışı anahtar400 bank_connect.transfer.idempotency_key_invalid.
  • Anahtarı kendi sisteminizdeki ödeme kaydından türetin (ör. payout-<ödeme kimliğiniz>). Böylece süreciniz yeniden başlasa da aynı ödeme için aynı anahtarı gönderirsiniz.
  • Zaman aşımı ya da ağ hatası aldığınızda isteği aynı anahtar ve aynı gövdeyle yeniden gönderin. Transfer oluştuysa mevcut transfer döner; oluşmadıysa şimdi oluşur.
  • SDK'lar anahtarı siz vermezseniz kendisi üretir ve kendi yeniden denemelerinde aynı anahtarı kullanır. Kullanılan anahtar yanıttaki transfer.idempotencyKey alanındadır.

Transferi izleme

Transferin sonucu iki yoldan gelir:

  • Webhook: bank_transfer.created, bank_transfer.completed, bank_transfer.failed ve bank_transfer.returned olayları. → Webhook Olayları › Transfer olayları
  • Sorgu: GET /api/v1/bank-connect/transfers/{transferId}. Webhook'u kaçırdıysanız ya da endpoint'iniz yoksa transferi bu uçtan izleyin.

Durumların anlamı ve olası geçişler: Kavramlar › Transfer durumları.

Tek transfer

GET /api/v1/bank-connect/transfers/{transferId} tek bir transferi döner; data transfer nesnesinin kendisidir. Bilinmeyen, başka bir organizasyona ya da diğer ortama ait transfer 404 bank_connect.transfer.not_found döner.

source lunixi-sign.sh
REQ_PATH="/api/v1/bank-connect/transfers/00000000-0000-0000-0000-000000002000"
lunixi_sign GET "$REQ_PATH"
 
curl "https://api-gateway.lunixi.com$REQ_PATH" \
  -H "X-Key-Id: $LUNIXI_KEY_ID" \
  -H "X-Date: $DATE" \
  -H "X-Nonce: $NONCE" \
  -H "X-Signature: $SIGNATURE"

Transferleri listeleme

GET /api/v1/bank-connect/transfers transferleri en yeniden eskiye listeler. Panelden ve API'den başlatılan transferler birlikte listelenir (initiatedVia).

Sorgu parametresiTipAçıklama
accountIdstring (UUID)Yalnız bu hesaptan yapılan transferler.
statusstringVirgülle ayrılmış durum listesi, ör. COMPLETED,RETURNED.
createdFromstringISO 8601, dahil.
createdTostringISO 8601, hariç.
limitintegerSayfa başına kayıt, 1–100. Varsayılan 25.
cursorstringÖnceki yanıttaki pageInfo.nextCursor.

Sayfalama kuralları okuma uçlarıyla aynıdır. → API'ye Giriş › Sayfalama

Sonucu doğrulanamayan transfer

Bankadan kesin cevap alınamadığında (ör. banka zamanında yanıt vermediğinde) transfer SUBMITTED durumunda kalır ve outcomeUnknown true olur. Bu durumda:

  • Transferi yeniden göndermeyin. Para bankada çıkmış olabilir; yeni bir transfer çift ödemeye yol açabilir.
  • Lunixi sonucu bankayla doğrular. Transfer sonuçlandığında durumu COMPLETED ya da FAILED olur ve ilgili webhook gönderilir.
  • Banka kontrolünde transferin gerçekleşmediği anlaşılırsa failureCode bank_connect.transfer.failed_after_bank_check olur. Bu durumda aynı ödemeyi yeni bir Idempotency-Key ile yeniden gönderebilirsiniz.
Sonucu teyit edilmemiş transferAkış: bankadan kesin yanıt gelmezse transfer outcomeUnknown true ile SUBMITTED kalır ve tekrar gönderilmemelidir; Lunixi sonucu bankayla teyit eder ve transferi COMPLETED ya da failed_after_bank_check ile FAILED olarak kapatır; bundan sonra ödemeyi yeni bir Idempotency-Key ile tekrar gönderebilirsiniz.YANIT YOKBANKA TEYİDİİSTERSENİZTransfer gönderildiPOST …/transfersSUBMITTEDoutcomeUnknown: trueLunixi bankaylateyit ederCOMPLETEDbank_transfer.completedFAILEDfailed_after_bank_checkYeni anahtarlatekrar gönderinIdempotency-Keytekrar göndermeyinLEJANTADIMBEKLİYOR — HATA DEĞİLSONUÇLANDISİZİN KARARINIZ
Para bankada çıkmış olabilir; yeni bir transfer çift ödemeye yol açabilir. Ödemeyi yalnız failed_after_bank_check ile FAILED olduktan sonra ve yeni bir Idempotency-Key ile tekrar gönderin.

Transfer nesnesi

Transfer, döviz teklifi ve döviz işlemi yanıtlarında değeri olmayan alanlar null olarak döner; alan yanıttan çıkarılmaz. Bu, okuma uçlarındaki "değer yoksa alan yoktur" kuralından farklıdır.

AlanTipAçıklama
idstring (UUID)Transferin kimliği.
connectionIdstring (UUID)Hesabın bağlantısı.
accountIdstring (UUID)Paranın çıktığı hesap.
environmentstringtest ya da live.
statusstringTransferin durumu. → Transfer durumları
railstring | nullBankanın seçtiği yol: INTERNAL, INTRABANK, FAST, EFT ya da UNKNOWN. Banka bildirene kadar null.
amountMinorintegerTutar, minor birimde.
currencystringPara birimi (ISO 4217).
sourceIbanMaskedstring | nullKaynak hesabın maskeli IBAN'ı.
receiverIbanMaskedstringAlıcının maskeli IBAN'ı.
receiverNameMaskedstringAlıcının maskeli adı.
receiverBankCodestring | nullAlıcı bankanın EFT kodu.
paymentPurposestring | nullGönderdiğiniz ödeme amacı kodu.
descriptionstring | nullGönderdiğiniz açıklama.
idempotencyKeystringTransferin oluşturulduğu Idempotency-Key.
failureCodestring | nullFAILED, RETURNED ya da CANCELED transferde sebep kodu (bank_connect.transfer.*). → Hata Kodları › Transfer sonuç kodları
outcomeUnknownbooleantrue: sonuç bankadan henüz doğrulanamadı. → Sonucu doğrulanamayan transfer
initiatedViastringAPI ya da PANEL.
sanctionStatusstringAlıcı tarama durumu: NOT_SCREENED, CLEAR, POTENTIAL_MATCH, ERROR. Bilgi amaçlıdır; transferi durdurmaz.
bankTransactionIdstring | nullTransferin hesabınızdaki giden hareketi bulunduğunda o hareketin transactionId'si. → Hareketler
bankStatestring | nullBankanın transfer için bildirdiği durum metni; yalnız bilgi amaçlıdır, kodunuzu status'a göre yazın.
createdAtstring | nullOluşturulma zamanı.
submittedAtstring | nullOnaylanıp bankaya iletilmek üzere sıraya alındığı zaman.
completedAtstring | nullTamamlandığı zaman.
failedAtstring | nullBaşarısız olduğu zaman.
returnedAtstring | nullİade edildiği zaman.
canceledAtstring | nullİptal edildiği zaman.
otpExpiresAtstring | nullYalnız panelden başlatılıp doğrulama kodu bekleyen transferde kodun geçerlilik sonu.

IBAN ve alıcı adı yalnız maskeli döner; tam IBAN ve tam ad hiçbir yanıtta yoktur. → Kavramlar › Maskeleme

Hatalar

HTTPcodeNe yapmalı
400VALIDATION_ERRORTanımlanmamış bir alan ya da parametre gönderdiniz, ya da alan biçimi hatalı.
400bank_connect.validation.invalidIBAN, alıcı adı, tutar, açıklama ya da ödeme amacı kurala uymuyor; details.errors[] alanına bakın.
400bank_connect.transfer.idempotency_key_requiredIdempotency-Key başlığını ekleyin.
400bank_connect.transfer.idempotency_key_invalid8–200 yazdırılabilir ASCII karakter kullanın.
403API_INSUFFICIENT_SCOPEAnahtarınıza bank-connect:transfer yetkisini ekleyin.
403bank_connect.auth.ip_allowlist_requiredAnahtarınıza IP izin listesi tanımlayın.
403bank_connect.auth.ip_not_allowedİsteği izin listesindeki bir IP'den gönderin ya da listeyi güncelleyin.
404bank_connect.transfer.not_foundKimliği ve anahtarınızın ortamını kontrol edin.
404bank_connect.account.not_foundHesap kimliğini kontrol edin.
409bank_connect.transfer.account_not_eligibleHesap, bağlantı ya da banka bilgileri şu anda etkin değil.
409bank_connect.transfer.idempotency_conflictBu anahtarı başka bir gövdeyle kullandınız; yeni transfer için yeni anahtar kullanın.
412bank_connect.transfer.capability_unavailableBu bağlantı transfer desteklemiyor.
422bank_connect.transfer.currency_unsupportedTRY gönderin ya da currency alanını boş bırakın.
429RATE_LIMITEDTransfer istek limiti aşıldı; Retry-After kadar bekleyin. → Sınırlar
503bank_connect.auth.ip_allowlist_unavailableGeri çekilerek aynı istekle yeniden deneyin.

Banka transferi reddederse istek yine 201 döner; transfer sonradan FAILED olur ve sebep failureCode alanındadır. → Hata Kodları › Transfer sonuç kodları