Skip to content

HemenFast — Integration Guide

1. Overview

HemenFast accepts deposit and withdraw requests from your site, routes them to a payment channel/account, and notifies your callback URL with a signed message once the result is final.

Direction Caller Description
Request You → HemenFast HMAC-signed calls to /paymentapi/*
Callback HemenFast → You Signed JSON POST to your callback URL on approve/reject

Deposit flow:

1. User starts a deposit on your site
2. You  ──POST /paymentapi/deposit──▶  HemenFast        (HMAC signed)
3. HemenFast returns a bank account (bank_name, iban, holder_name)
4. User pays that account
5. On approval  HemenFast ──POST {callback_url}──▶  You  (hashcode signed)
6. You verify hashcode, credit the user, return HTTP 200

Withdraw is the same shape: create via /paymentapi/withdraw, result delivered by callback.


2. Base URL

Environment Base URL
Production https://api.hemenfast.com

A separate test (UAT) URL, if available for your integration, is shared at onboarding. Examples below use the production base URL.

GET /health (no authentication) returns {"status": "healthy", ...} — use it to check connectivity before testing signed requests.


3. Credentials

Value Description
API Key Sent in the X-API-Key header. Identifies your account.
Secret Key Used to sign requests and to verify the callback hashcode. Never expose it client-side.

The secret key is shared once at creation. If it is lost it must be regenerated: regeneration issues a new API key and a new secret key together, and the old pair stops working immediately.


4. Authentication (HMAC-SHA256)

Every request to /paymentapi/* requires these headers:

Header Description Constraint
X-API-Key Your API key Must be active
X-Signature HMAC-SHA256 signature 64-char lowercase hex (uppercase hex is rejected)
X-Timestamp Unix timestamp (seconds) Within ±5 minutes of server time
X-Nonce Per-request unique value 8–64 chars, never reused
X-Callback-URL Per-transaction callback override Optional — must point to the same scheme + host (+ port) as the webhook URL registered on your account
Content-Type application/json —

X-Callback-URL restriction: the header is only accepted when it targets the same scheme + host (+ port) as your registered webhook URL — you can change the path, not the destination. A header pointing anywhere else (or sent while no webhook is registered on your account) is rejected with 400 X-Callback-URL must match the firm's registered webhook host. If no callback URL can be resolved at all, the request fails with 400 Callback URL not configured on provider. In production the callback URL must use https and resolve to a public address, otherwise the request fails with 400 Invalid callback URL: ….

Platform integrations: if your payment platform (e.g. a turnkey/aggregator provider) has a central callback URL registered with HemenFast, all results are delivered there — this header and your account webhook are not used. See section 6.

Signature

Message to sign (lines joined with literal \n newlines):

{HTTP_METHOD}\n{PATH}\n{BODY}\n{TIMESTAMP}\n{NONCE}
  • HTTP_METHOD: POST or GET (uppercase)
  • PATH: e.g. /paymentapi/deposit (path without query string)
  • BODY: the raw JSON body (sign the exact bytes you send); for GET requests with no body, an empty string — the message then contains an empty BODY line
  • TIMESTAMP, NONCE: same values as the headers
X-Signature = HMAC_SHA256(secret_key, message)  →  lowercase hex

Example (bash)

API_KEY="<api-key>"
SECRET_KEY="<secret-key>"
TIMESTAMP=$(date +%s)
NONCE=$(openssl rand -hex 16)
BODY='{"amount":"1000","bnk":"1","first_name":"Ali","last_name":"Yilmaz","username":"aliyilmaz","userid":"user001","transactionid":"TX-DEP-001","method":"fast"}'

MESSAGE="POST\n/paymentapi/deposit\n${BODY}\n${TIMESTAMP}\n${NONCE}"
SIGNATURE=$(printf "%b" "${MESSAGE}" | openssl dgst -sha256 -hmac "${SECRET_KEY}" | awk '{print $2}')

curl -X POST "https://api.hemenfast.com/paymentapi/deposit" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: ${API_KEY}" \
  -H "X-Signature: ${SIGNATURE}" \
  -H "X-Timestamp: ${TIMESTAMP}" \
  -H "X-Nonce: ${NONCE}" \
  -H "X-Callback-URL: https://yoursite.com/hemenfast/callback" \
  -d "${BODY}"

Common mistake: the BODY you sign and the body you send must be byte-identical. Do not re-serialize the JSON — sign and send the same string.

Complete client implementations (PHP, Node.js, Python) are in section 10.


5. Endpoints

All request fields are JSON strings — including amount. Sending a number (e.g. "amount": 1000) or malformed JSON fails with 400 Invalid request format.

5.1 POST /paymentapi/deposit

Creates a deposit; routed to a payment account automatically. POST /paymentapi/getbank is an alias with identical behaviour.

Request:

{
  "amount": "1000",
  "bnk": "1",
  "tcid": "12345678901",
  "first_name": "Ali",
  "last_name": "Yilmaz",
  "username": "aliyilmaz",
  "userid": "user001",
  "transactionid": "TX-DEP-001",
  "method": "fast"
}
Field Type Required Constraint
amount string Yes Number > 0, . as decimal separator (e.g. "1000", "1000.50"); stored rounded to 2 decimals
bnk string Yes 1–20 chars (payment channel / bank id)
tcid string No National ID — no format check
first_name string Yes 2–50 chars: letters (Turkish letters included), spaces, - ' .
last_name string Yes 2–50 chars: letters (Turkish letters included), spaces, - ' .
username string Yes 3–30 chars: letters, digits, _ . -
userid string Yes 1–50 chars — your stable id for the end user
transactionid string Yes 1–100 chars, unique (idempotency key) — see note below
method string No fast, eft or havale; defaults to fast

transactionid must be unique across all your deposits and withdrawals, and it is never reusable — even after a rejection. Use a site-specific prefix (e.g. MYSITE-DEP-000123) and stick to URL-safe characters (A–Z a–z 0–9 - _ .), since the same value is used in the getbyexternalid path.

Success (200):

{
  "status": true,
  "msg": "Deposit received",
  "bank_name": "Ziraat Bankasi",
  "holder_name": "Mehmet Kaya",
  "iban": "TR330006100519786457841326"
}

Show these account details to the user; payment goes to this account.

Business rejections — the request was valid but the deposit was not created. The body is always { "status": false, "msg": "<reason>" }:

msg Cause HTTP HTTP in legacy mode
Bu islem numarasi zaten kullanilmis transactionid already used 409 200
Halihazirda bekleyen talebiniz var This userid already has a pending deposit or withdrawal on your account 409 200
Bu tutar icin uygun hesap yok No payment account available for this amount right now 422 200
Yatırım şu anda kapalı Deposits are currently closed for your account (manually or by a scheduled pause window) 403 200
Yatırım tutarı limit dışında Amount is outside the min/max deposit limits configured for your account 403 200
Kullanıcı yasaklı The end user is on the block list (matched by userid or tcid) 403 200

HTTP status codes for business rejections are on by default. Integrations that need every business rejection as HTTP 200 can be switched to legacy mode at onboarding; the body is the same in both modes. Either way, always branch on the status field.

The "one pending request per user" rule can be relaxed for platform integrations on request.


5.2 POST /paymentapi/withdraw

Creates a withdrawal.

Request:

{
  "bankid": "1",
  "amount": "5000",
  "first_name": "Ali",
  "last_name": "Yilmaz",
  "tcid": "12345678901",
  "iban": "TR330006100519786457841326",
  "transactionid": "TX-WDR-001",
  "userid": "user001",
  "username": "aliyilmaz"
}
Field Type Required Constraint
bankid string Yes 1–20 chars
amount string Yes Number > 0 (same rules as deposit)
first_name string Yes 2–50 chars: letters (Turkish letters included), spaces, - ' .
last_name string Yes 2–50 chars: letters (Turkish letters included), spaces, - ' .
tcid string No Optional
iban string No* Up to 64 chars — send the user's TR IBAN (26 chars, e.g. TR33 0006 1005 …; spaces are allowed)
transactionid string Yes 1–100 chars, unique (shared namespace with deposits, see 5.1)
userid string Yes 1–50 chars
username string Yes 3–30 chars: letters, digits, _ . -

* A missing or malformed IBAN does not reject the request: it is stored as sent (normalized when it is a valid TR IBAN) and flagged as invalid to the operator. Always send the correct IBAN — the payout is made to it.

Success (200):

{ "status": true, "msg": "Çekim talebiniz alındı. Birazdan işleme alınacaktır" }

Business rejections ({ "status": false, "msg": "<reason>" }):

msg Cause HTTP HTTP in legacy mode
Bu islem numarasi zaten kullanilmis transactionid already used 409 200
Halihazirda bekleyen talebiniz var This userid already has a pending deposit or withdrawal on your account 409 200
Çekim şu anda kapalı Withdrawals are currently closed for your account (manually or by a scheduled pause window) 403 200
Kullanıcı yasaklı The end user is on the block list (matched by userid or tcid) 403 200

5.3 GET /paymentapi/getbyid/{id}

Query status by HemenFast's internal transaction ID. You only see your own transactions.

Response (200):

{
  "status": true,
  "msg": "pending",
  "data": {
    "id": 1,
    "transactionid": "TX-DEP-001",
    "type": "deposit",
    "status": "pending",
    "amount": 1000.00,
    "method": "fast",
    "first_name": "Ali",
    "last_name": "Yilmaz",
    "username": "aliyilmaz",
    "userid": "user001",
    "tcid": "1234***01",
    "assigned_bank_name": "Ziraat Bankasi",
    "assigned_holder_name": "Mehmet Kaya",
    "assigned_iban": "TR330006100519786457841326",
    "created_at": "2026-03-30T12:00:00Z",
    "updated_at": "2026-03-30T12:00:00Z"
  }
}
  • msg repeats the current status (pending, approved or rejected).
  • data is a slim, account-scoped view. Internal fields are not returned (firm_id, merchant_id, callback_url, processed_by, handling_by, amount_edited_by, original_amount, decision_note*, refund_*). tcid is masked (1234***01). amount is a JSON number with 2 decimals. Withdrawals additionally include bankid and iban; the assigned_* fields are present when a payment account is assigned.

Errors — note that these two status endpoints use an error field, not the status/msg envelope:

HTTP Body Cause
400 { "error": "invalid id" } {id} is not an integer (getbyid only)
404 { "error": "transaction not found" } No such transaction, or it belongs to another account

5.4 GET /paymentapi/getbyexternalid/{transactionid}

Query by the transactionid you submitted. Same response and error format as getbyid.

These endpoints are for status checks / reconciliation — they do not replace the callback, which remains the primary result notification. The status they return is always the authoritative current status.


6. Callbacks

When a transaction is approved or rejected, HemenFast sends an asynchronous POST to the callback URL. The destination is resolved in this priority order:

  1. Platform central callback URL — if your payment platform (turnkey/aggregator provider) has one registered with HemenFast, all results go there, signed with the platform's secret and signature scheme.
  2. X-Callback-URL header — per-transaction override (same-host restriction, see section 4).
  3. Account webhook URL — the default registered on your account.

In cases 2 and 3 the callback is signed with your account's secret key as described below. In case 1 the platform verifies and relays the result to you through its own channels — the rest of this section then applies to the platform, not to your site.

The result is final at the moment it is decided; the callback notifies you of it. Delivery problems never change a transaction's status.

A reject callback is sent when a transaction is:

  • rejected by an operator,
  • automatically cancelled after staying pending longer than the timeout configured for your integration (if any), or
  • a deposit that the operator marks for return/refund.

6.1 Payload

Sent as a JSON body (Content-Type: application/json) — not as form fields:

{
  "transid": "TX-DEP-001",
  "amount": "1000.00",
  "status": "approve",
  "hashcode": "a1b2c3d4e5..."
}
Field Description
transid The transactionid you submitted
amount Amount as a string, always 2 decimals (e.g. 1000.00)
status approve or reject
hashcode Signature (lowercase hex) — verify as below

All values are JSON strings. The amount is the transaction's final amount, which can differ from the requested amount if an operator corrected it before deciding.

The hashcode is computed over the exact amount string sent (always 2 decimals). Build the verification message from the values received verbatim — do not reformat them.

6.2 Verifying the hashcode

Callbacks to your account webhook or X-Callback-URL are always signed like this:

message  = "{transid},{amount},{status}"
hashcode = HMAC-SHA256(secret_key, message)  →  lowercase hex

Platform callbacks (case 1) can use a different scheme configured for the platform — e.g. plain sha256 over secret,transid,amount,status — and may carry extra payload fields. The active scheme is confirmed at onboarding.

PHP:

$payload = json_decode(file_get_contents('php://input'), true); // JSON body, not $_POST
if (!is_array($payload) || !isset($payload['transid'], $payload['amount'], $payload['status'], $payload['hashcode'])) {
    http_response_code(400);
    exit('invalid payload');
}

$message  = $payload['transid'] . ',' . $payload['amount'] . ',' . $payload['status'];
$expected = hash_hmac('sha256', $message, $secretKey);
if (!hash_equals($expected, $payload['hashcode'])) {
    http_response_code(400);
    exit('invalid signature');
}

// safe: apply the result once (deduplicate on transid), then:
header('Content-Type: application/json');
echo json_encode(['status' => true, 'msg' => 'OK']);

Node.js (Express):

const crypto = require('crypto')

app.post('/hemenfast/callback', express.json(), (req, res) => {
  const { transid, amount, status, hashcode } = req.body || {}
  const message  = `${transid},${amount},${status}`
  const expected = Buffer.from(crypto.createHmac('sha256', SECRET_KEY).update(message).digest('hex'))
  const received = Buffer.from(String(hashcode || ''))

  // timingSafeEqual throws on different lengths, so compare lengths first
  if (received.length !== expected.length || !crypto.timingSafeEqual(expected, received)) {
    return res.status(400).send('invalid signature')
  }

  // apply the result once (deduplicate on transid), then:
  res.status(200).json({ status: true, msg: 'OK' })
})

Python (Flask):

import hashlib
import hmac

from flask import request


@app.post("/hemenfast/callback")
def hemenfast_callback():
    p = request.get_json(silent=True) or {}
    message  = f"{p.get('transid')},{p.get('amount')},{p.get('status')}".encode()
    expected = hmac.new(SECRET_KEY.encode(), message, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, str(p.get("hashcode", ""))):
        return "invalid signature", 400

    # apply the result once (deduplicate on transid), then:
    return {"status": True, "msg": "OK"}, 200

6.3 Your response

  • Return any 2xx status within the timeout (15 seconds). Any other status — including 3xx, since redirects are not followed — is treated as a failure and retried.
  • If the response body is a JSON object, it must contain "status": true. A JSON object with "status": false, or without a status field (e.g. {"msg": "OK"} or {}), is treated as a failure and retried. An empty body or plain text (e.g. OK) is accepted.
  • Recommended response: HTTP 200 with {"status": true, "msg": "OK"}.
  • Keep the handler fast: verify, record, respond. A response that arrives after the timeout counts as a failure even if you processed it, and the callback will be sent again.

6.4 Retry policy

Property Value
Max attempts 3 in total (first delivery + 2 retries)
Retry spacing Retries are scheduled 1 s, then 2 s after a failure; the delivery worker runs every 5 seconds, so a retry normally arrives within ~5–10 seconds
HTTP timeout 15 seconds
Durability Stored in an outbox before sending; pending deliveries are re-processed after a restart
Delivery guarantee At-least-once — the same callback can arrive more than once
After the last failed attempt Delivery stops; the transaction keeps its final status. Reconcile with the status endpoints (sections 5.3 / 5.4).

6.5 Status mapping

Transition Callback status
pending → approved approve
pending → rejected reject

A callback is sent only once per transaction, for its first and only result (pending → approved/rejected). Later back-office status corrections on the panel (e.g. approved → rejected) do not emit a callback — treat the first result you receive as final and irreversible.

Idempotency: the callback for a given transid may be re-delivered (retries, at-least-once delivery) — always with the same status. Deduplicate on transid; never apply the same result twice.


7. Transaction Statuses

Status Description
pending Created, awaiting result
approved Approved (callback approve)
rejected Rejected/cancelled (callback reject)

8. Rate Limiting

  • 100 requests/minute per API key (token bucket: bursts of up to 100 requests, refilling at ~1.7 requests/second).
  • Requests that fail authentication are rejected before the limiter and do not count.
  • On limit, HTTP 429:
{ "status": false, "msg": "Rate limit exceeded. Please try again later." }

9. Error Codes

HTTP Meaning
200 Success — in legacy mode also business rejections, with status: false (section 5)
400 Bad request / validation error / invalid callback URL
401 Authentication failed (HMAC)
403 Deposits/withdrawals closed, amount outside limits, blocked user — or account not set up for payments
404 Transaction not found (status endpoints)
409 Duplicate transactionid / pending request exists
413 Request too large (>1 MB)
422 No payment account available for this amount
429 Rate limit exceeded
500 Server error

The deposit and withdraw endpoints (and authentication/rate-limit failures) share the response envelope { "status": false, "msg": "<reason>" } — the same shape as a success, with status: false. The two status endpoints use { "error": "<reason>" } instead (see 5.3). Always branch on the body, not the HTTP code alone.

9.1 Authentication failures (401)

The HMAC check runs before the request reaches any endpoint, in the order below. Each failure returns HTTP 401 with { "status": false, "msg": … }:

msg Cause & fix
Authentication required: missing headers One of X-API-Key / X-Signature / X-Timestamp / X-Nonce is absent
Invalid timestamp: invalid timestamp format / …: timestamp too old / …: timestamp too far in future X-Timestamp is not an integer, or is outside the ±5 minute window — send the current Unix time in seconds and keep your clock NTP-synced
Invalid nonce: nonce must be between 8 and 64 characters X-Nonce length is out of range
Invalid API key X-API-Key doesn't match an active account
Invalid signature HMAC mismatch — recompute the signature per section 4, use lowercase hex, and make sure the body you sent is byte-identical to the body you signed
Nonce replay detected This X-Nonce was already used with your API key — generate a fresh random nonce for every request

9.2 Request errors (4xx / 5xx)

Returned by the deposit/withdraw endpoints after authentication passes, same { "status": false, "msg": … } envelope:

HTTP msg (examples)
400 Invalid request format (malformed JSON, or a field sent as a non-string)
400 Validation failed: <reason> — only the first problem is reported, e.g. Amount must be a positive number, FirstName must contain only letters, spaces, or - ' ., Username is too short, TransactionID is required
400 X-Callback-URL must match the firm's registered webhook host, Callback URL not configured on provider, Invalid callback URL: … (scheme / https / public-address check)
403 Firm not identified, Firm is not linked to an active provider — your account is not set up for payments; contact support
413 Request too large (body larger than 1 MB)
429 Rate limit exceeded. Please try again later.
500 An internal error occurred. Please try again later.

Business rejections (duplicate transactionid, pending request, no account, closed, limits, blocked user) are listed in sections 5.1 and 5.2 — they return 409 / 422 / 403 (or HTTP 200 in legacy mode) with status: false.

Timeouts and 5xx: if a create request times out or returns 5xx, do not assume it failed. Query getbyexternalid with the same transactionid first — or resend the identical request: if the first one was recorded you will get Bu islem numarasi zaten kullanilmis (409), and the status endpoint returns the assigned account details. Never retry with a new transactionid for the same user action.


10. Sample Client Code

Each example signs and sends a request, covering both POST (JSON body) and GET (empty body — the BODY line in the signed message is an empty string). The same helper works for every /paymentapi/* endpoint; only the path and payload change. Callback verification examples are in section 6.2.

10.1 PHP

<?php
const BASE_URL   = 'https://api.hemenfast.com';
const API_KEY    = '<api-key>';
const SECRET_KEY = '<secret-key>';

function hemenfastRequest(string $method, string $path, ?array $payload = null): array
{
    $body      = $payload === null ? '' : json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    $timestamp = (string) time();
    $nonce     = bin2hex(random_bytes(16));

    // METHOD \n PATH \n BODY \n TIMESTAMP \n NONCE
    $message   = implode("\n", [$method, $path, $body, $timestamp, $nonce]);
    $signature = hash_hmac('sha256', $message, SECRET_KEY);

    $ch = curl_init(BASE_URL . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            'X-API-Key: '    . API_KEY,
            'X-Signature: '  . $signature,
            'X-Timestamp: '  . $timestamp,
            'X-Nonce: '      . $nonce,
        ],
    ]);
    if ($body !== '') {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $body); // send the exact string you signed
    }
    $response = curl_exec($ch);
    curl_close($ch);
    return json_decode((string) $response, true) ?? [];
}

// Deposit
$deposit = hemenfastRequest('POST', '/paymentapi/deposit', [
    'amount'        => '1000',
    'bnk'           => '1',
    'first_name'    => 'Ali',
    'last_name'     => 'Yilmaz',
    'username'      => 'aliyilmaz',
    'userid'        => 'user001',
    'transactionid' => 'TX-DEP-001',
    'method'        => 'fast',
]);

if (!empty($deposit['status'])) {
    // show $deposit['bank_name'], $deposit['holder_name'], $deposit['iban'] to the user
} else {
    // e.g. "Bu tutar icin uygun hesap yok" or "Bu islem numarasi zaten kullanilmis"
}

// Status check (reconciliation)
$status = hemenfastRequest('GET', '/paymentapi/getbyexternalid/TX-DEP-001');

10.2 Node.js (18+)

const crypto = require('crypto')

const BASE_URL   = 'https://api.hemenfast.com'
const API_KEY    = '<api-key>'
const SECRET_KEY = '<secret-key>'

async function hemenfastRequest(method, path, payload) {
  const body      = payload ? JSON.stringify(payload) : ''
  const timestamp = Math.floor(Date.now() / 1000).toString()
  const nonce     = crypto.randomBytes(16).toString('hex')

  // METHOD \n PATH \n BODY \n TIMESTAMP \n NONCE
  const message   = [method, path, body, timestamp, nonce].join('\n')
  const signature = crypto.createHmac('sha256', SECRET_KEY).update(message).digest('hex')

  const res = await fetch(BASE_URL + path, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key':    API_KEY,
      'X-Signature':  signature,
      'X-Timestamp':  timestamp,
      'X-Nonce':      nonce,
    },
    body: body || undefined, // send the exact string you signed
    signal: AbortSignal.timeout(30_000),
  })
  return res.json()
}

// Deposit
const deposit = await hemenfastRequest('POST', '/paymentapi/deposit', {
  amount: '1000',
  bnk: '1',
  first_name: 'Ali',
  last_name: 'Yilmaz',
  username: 'aliyilmaz',
  userid: 'user001',
  transactionid: 'TX-DEP-001',
  method: 'fast',
})

// Status check (reconciliation)
const status = await hemenfastRequest('GET', '/paymentapi/getbyexternalid/TX-DEP-001')

10.3 Python

import hashlib
import hmac
import json
import secrets
import time

import requests

BASE_URL   = "https://api.hemenfast.com"
API_KEY    = "<api-key>"
SECRET_KEY = "<secret-key>"


def hemenfast_request(method: str, path: str, payload: dict | None = None) -> dict:
    body      = "" if payload is None else json.dumps(payload, separators=(",", ":"), ensure_ascii=False)
    timestamp = str(int(time.time()))
    nonce     = secrets.token_hex(16)

    # METHOD \n PATH \n BODY \n TIMESTAMP \n NONCE
    message   = "\n".join([method, path, body, timestamp, nonce])
    signature = hmac.new(SECRET_KEY.encode(), message.encode(), hashlib.sha256).hexdigest()

    resp = requests.request(
        method,
        BASE_URL + path,
        headers={
            "Content-Type": "application/json",
            "X-API-Key":    API_KEY,
            "X-Signature":  signature,
            "X-Timestamp":  timestamp,
            "X-Nonce":      nonce,
        },
        data=body.encode() or None,  # send the exact bytes you signed
        timeout=30,
    )
    return resp.json()


# Deposit
deposit = hemenfast_request("POST", "/paymentapi/deposit", {
    "amount": "1000",
    "bnk": "1",
    "first_name": "Ali",
    "last_name": "Yilmaz",
    "username": "aliyilmaz",
    "userid": "user001",
    "transactionid": "TX-DEP-001",
    "method": "fast",
})

# Status check (reconciliation)
status = hemenfast_request("GET", "/paymentapi/getbyexternalid/TX-DEP-001")

Notes

  • Withdraw uses the same helper: POST /paymentapi/withdraw with the fields from section 5.2.
  • Sign exactly what you send. All three helpers serialize the payload once and use the same string for both the signature and the request body. Non-ASCII names (e.g. Şahin) are fine as long as the signed and sent bytes are the same UTF-8 string.
  • X-Nonce must be unique per request — a reused nonce is rejected with 401 Nonce replay detected. The examples generate a random 32-hex-char nonce each call.
  • Clock skew: X-Timestamp must be within ±5 minutes of server time; keep your server clock NTP-synced.
  • To send a per-transaction X-Callback-URL header, remember the same-host restriction in section 4.