AlfaBit API Documentation

Complete REST API for integrating all AlfaBit features into your applications.

ℹ️
Account Structure

Funding Account — main wallet for deposits, withdrawals, transfers. Trading Account — shared balance for exchange trading; via the API it exposes the USDT/RUB orderbook, while crypto exchange over the API runs from the Funding Account.

💡
TIP

To get started, create API keys in Developer Console.

Quick Start

Get started with the API in 5 minutes. This guide will walk you through from creating an API key to your first trade.

1

Create an API Key

Go to Developer Console → API Keys → Create Key. Select permissions and save the Secret Key — it is shown only once.

2

Set Up Request Signing

Every request is signed with HMAC-SHA256. Copy the ready-made code from the Authentication section (Python / JavaScript) — it works out of the box.

3

Verify Connection

Send your first request — get the account profile:

GET /api/v1/integration/account/profile
→ If you see your email and ID — everything works!
4

Check Your Balances

Make sure you have funds for operations:

GET /api/v1/integration/account/wallets
→ You will see all wallets with balances and deposit addresses.
5

Make Your First Operation

Choose what you want to do and go to the relevant section:

Platform Architecture

Before you start, it is important to understand how the platform works. This will help you choose the right endpoints and avoid errors.

Two Account Types

💰

Funding Account

Main account. All your funds are stored here. Used for: deposits, withdrawals, crypto exchanges, transfers, service payments and cards.

Used by most operations
📊

Trading Account

A shared balance for exchange trading — in the personal cabinet terminal every pair trades from it. Via the API it currently runs the USDT/RUB orderbook with market and limit orders: transfer funds via /funding/transfer/to-trading before trading. Crypto pairs are exchanged via the API as an instant market swap from the funding account (/integration/spot/crypto) — the orderbook and limit orders are not yet available for them via the API.

Exchange trading
💡
About currency codes

The currency code in the examples below (e.g. RUB or USDT) is shown to illustrate the request structure. The actual list of supported currencies for your integration is confirmed separately during onboarding.

Exchange Methods — When to Use Which?

ModuleWhat is itPublic RatesTradingPairs
Crypto SpotCrypto markets — crypto ↔ cryptoGET /spot/crypto/tickers
GET /spot/crypto/market-info
Market / Limit ordersBTC/USDT, ETH/USDT
Fiat SpotAlfaBit orderbook — crypto ↔ fiatGET /spot/fiat/orderbook
GET /spot/fiat/stats
GET /spot/fiat/instruments
Market / Limit ordersUSDT/RUB
ConverterInstant exchange with fixed rateGET /converter/crypto/rate
GET /converter/fiat/rate
Exchange at quoteBTC → ETH, USDT → RUB
Public Data

Each of the three modules has public endpoints (rates, tickers, orderbook) available without an API key — for widget integration, price monitoring and analytics. Trading operations (orders, exchange) require an API key with appropriate permissions.

Common Response Format

All API responses follow a common format. Successful operations return data in the data field, errors — in the error field:

Success Response
{ "success": true, "data": { ... }, "ts": 1706000000 }
Error Response
{ "success": false, "error": { "code": "...", "message": "..." }, "ts": 1706000000 }

Authentication

All requests must be signed with HMAC-SHA256 using your secret key.

HTTP Headers

HeaderRequiredDescription
X-API-KeyRequiredYour public key pk_live_xxx
X-API-SignatureRequiredHMAC-SHA256 signature
X-API-TimestampRequiredUnix timestamp (seconds)
X-Device-IdOptionalDevice identifier / fingerprint (anti-fraud). Pass it if available.
🛡️
Anti-fraud operation logging

All operation-creating requests (deposits, withdrawals, transfers, conversions, invoices) are logged on our side: source IP, User-Agent and X-Device-Id (if provided). This data is used for API key security and anti-fraud scoring and does not affect operation processing. The KYC-API checkout additionally requires the mandatory payer_ip and external_payment_id fields (see the “KYC-API Checkout” section).

IP allowlist

Each key can be restricted to a list of allowed addresses in the Developer Console — the “IP Whitelist” field when creating a key or in its settings. While the list is empty the key is accepted from any address. Once it contains at least one entry, requests from every other address are rejected: HTTP 403, error code IP_NOT_ALLOWED.

Accepted values are an exact address (203.0.113.10), a CIDR subnet (203.0.113.0/24) and IPv6. An entry like ::ffff:203.0.113.10 is treated as the same address as 203.0.113.10. Separate multiple values with commas; clearing the field removes the restriction. The address checked is the public one your request arrives from, so for a server behind NAT specify its public IP, not a private network address.

🔒
The IP check runs before the signature check

IP_NOT_ALLOWED is returned even when the signature is completely valid, so if you get a 403 after changing hosting or adding a server, check the key’s address list first. An IP restriction is the strongest protection for a key: even a leaked secret is useless from another address. Always set it for production integrations.

Signature Formula

Formula
message = timestamp + method + path + body
signature = HMAC-SHA256(secret_key, message)

Code Examples

Python
import hmac, hashlib, time, requests, json

API_KEY = "pk_live_xxxxxxxxxxxxx"
SECRET_KEY = "sk_live_xxxxxxxxxxxxx"
BASE_URL = "https://alfabit.org"

def sign_request(method, path, body=""):
    timestamp = str(int(time.time()))
    message = f"{timestamp}{method.upper()}{path}{body}"
    signature = hmac.new(
        SECRET_KEY.encode(), message.encode(), hashlib.sha256
    ).hexdigest()
    return {
        "X-API-Key": API_KEY,
        "X-API-Signature": signature,
        "X-API-Timestamp": timestamp,
        "Content-Type": "application/json"
    }

# Получить балансы кошельков
path = "/api/v1/integration/account/wallets"
resp = requests.get(f"{BASE_URL}{path}", headers=sign_request("GET", path))
print(resp.json())
# {"success": true, "data": [{"symbol": "USDT", "available": "1250.00", ...}], "ts": 1706000000}

# Создать ордер на крипто обмен
path = "/api/v1/integration/spot/crypto/order"
body = json.dumps({"from_symbol": "USDT", "to_symbol": "BTC", "from_amount": "100"})
resp = requests.post(f"{BASE_URL}{path}", headers=sign_request("POST", path, body), data=body)
print(resp.json())

Rate Limits

Rate limits are configured individually per API key. The value is set when creating a key (rate_limit_per_minute field). When the limit is exceeded, the API returns a 429 error.

Error Handling

Error Response
{
  "success": false,
  "error": {
    "code": "INVALID_SIGNATURE",
    "message": "Invalid request signature",
    "details": null
  },
  "ts": 1706000000
}
CodeHTTPDescription
INVALID_API_KEY401Invalid API key
INVALID_SIGNATURE401Invalid signature
SIGNATURE_EXPIRED401Timestamp expired (>5 min)
API_KEY_EXPIRED401API key has expired
API_KEY_INACTIVE401API key is inactive
IP_NOT_ALLOWED403Client IP is not in the key’s allowed list
PERMISSION_DENIED403Permission denied
INSUFFICIENT_BALANCE400Insufficient balance
DEEP_PAGINATION_NOT_SUPPORTED400Transaction feed page is too deep (page × limit > 2000) — narrow the period

Orderbook errors (Fiat Spot)

Rejections from POST /spot/fiat/order, DELETE /spot/fiat/order/{id} and trading account transfers come with a dedicated code, and the numbers you need are in details. Branch on code, not on the message text: message and details.reason_text may change.

Insufficient liquidity (market buy by quote_amount)
{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_LIQUIDITY",
    "message": "Not enough counter liquidity in the orderbook to fill this market order",
    "details": {
      "quote_amount": "3041.770000",
      "quote_currency": "RUB",
      "available_liquidity": "0.654719",
      "base_currency": "USDT",
      "min_order_size": "1.000000",
      "reason": "available_liquidity_below_min_order_size",
      "hint": "Retry later, reduce the order, or place a limit order to wait in the book."
    }
  },
  "ts": 1706000000
}
CodeHTTPDescriptionWhat to do
INSUFFICIENT_LIQUIDITY400The opposite side of the book cannot fill your market order: it is empty or the remainder is below the minimum lot. details: available_liquidity, min_order_size, reason.Retry later, reduce the order or place a limit order — increasing the order does not help here.
ORDER_BELOW_MIN_SIZE400The amount you sent is below the pair minimum lot. details.min_order_size.Increase amount up to min_order_size.
ORDER_ABOVE_MAX_SIZE400The order exceeds the pair maximum. details.max_order_size.Split it into several orders.
ORDER_BELOW_MIN_VALUE400Order value (amount × price) is below the minimum. details.min_order_value.Increase amount or price.
QUOTE_AMOUNT_TOO_SMALL400quote_amount does not cover even the minimum lot of the pair.Increase quote_amount, see limits in GET /spot/fiat/market-info.
PRICE_OUT_OF_BAND400Limit price is too far from the market. details: allowed_price_min, allowed_price_max.Move the price inside the range from details.
PRICE_NOT_ON_TICK400Price is not a multiple of the pair price step. details.price_step.Round the price to price_step.
PRICE_REFERENCE_UNAVAILABLE400No market reference price to validate the limit price.Retry in a few seconds.
INSUFFICIENT_BALANCE400Not enough funds on the trading account. details: currency, required, available, account.Top up via POST /funding/transfer/to-trading.
BALANCE_LOCK_FAILED400The balance changed concurrently with the order.Re-read the balance and retry with a new Idempotency-Key.
PAIR_NOT_FOUND400No such pair. details.pair.Use a pair from GET /spot/fiat/instruments.
PAIR_NOT_ACTIVE400The pair is temporarily closed. details: pair, pair_status.Wait for status active in GET /spot/fiat/instruments.
ORDER_NOT_FOUND404No order with this id.Check the id in GET /spot/fiat/orders.
ORDER_ACCESS_DENIED403The order belongs to another account.Check the id and the API key.
ORDER_NOT_CANCELLABLE400The order is already filled or cancelled. details.order_status.Read the final state: GET /spot/fiat/order/{id}.
INVALID_ORDER_REQUEST400Invalid request body. details.field.Check the fields against the documentation.
STOCKBOOK_ERROR4xx / 5xxA rejection not yet mapped to a code. details: upstream_status, reason_text, upstream_body (legacy field, kept for compatibility).Retry later; if it repeats, send reason_text to support.
STOCKBOOK_UNAVAILABLE502The trading system is unreachable (network/timeout).Retry in a few seconds with the same Idempotency-Key.

Account

User profile and crypto wallet management. View balances, create wallets and get deposit addresses.

Typical Scenario

On first connection, start with GET /account/profile to verify authentication works. Then get wallet list via GET /account/wallets. If you need a wallet that does not exist — create it via POST /account/wallets (this also returns a deposit address).

GET/api/v1/integration/account/wallets

Returns all user wallets with balances and deposit addresses. Wallets not yet created will not appear in the list.

Response
{
  "success": true,
  "data": [
    {
      "symbol": "USDT",
      "is_active": true,
      "balance": "1250.00",
      "available": "1250.00",
      "pending_deposit": "0.00",
      "pending_withdraw": "0.00",
      "balance_usdt": "1250.00",
      "balance_source": "ledger",
      "addresses": [
        { "network": "TRX", "address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE" },
        { "network": "ETH", "address": "0x742d35Cc6634C0532925a3b..." }
      ]
    },
    {
      "symbol": "BTC",
      "is_active": true,
      "balance": "0.05432100",
      "available": "0.05432100",
      "pending_deposit": "0.00",
      "pending_withdraw": "0.00",
      "balance_usdt": "2150.50",
      "balance_source": "ledger",
      "addresses": []
    }
  ],
  "ts": 1706000000
}
GET/api/v1/integration/account/wallets/{symbol}

Returns balance and addresses for specific currency. Returns 404 if wallet not found.

Response
{
  "success": true,
  "data": {
    "symbol": "USDT",
    "is_active": true,
    "balance": "1250.00",
    "available": "1250.00",
    "pending_deposit": "0.00",
    "pending_withdraw": "0.00",
    "balance_usdt": "1250.00",
    "balance_source": "ledger",
    "addresses": [
      { "network": "TRX", "address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE" }
    ]
  },
  "ts": 1706000000
}
POST/api/v1/integration/account/wallets

Creates a wallet for specified currency and network. If wallet already exists — returns existing one with deposit address. Use this to get a deposit address.

Request
{
  "symbol": "USDT",
  "network": "TRX"
}
Response
{
  "success": true,
  "data": {
    "symbol": "USDT",
    "available": "0.00",
    "addresses": [
      { "network": "TRX", "address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE" }
    ]
  },
  "ts": 1706000000
}
GET/api/v1/integration/account/profile

Returns profile info: ID, email, KYC status, tariff.

Response
{
  "success": true,
  "data": {
    "id": 12345,
    "username": "john_doe",
    "email": "john@example.com",
    "email_verified": null,
    "kyc_status": "verified",
    "tariff": null,
    "sso_client_id": "wallet-web",
    "created_at": "1700000000"
  },
  "ts": 1706000000
}

Deposits

Fund your account with crypto and fiat. Crypto — via deposit addresses, fiat — via bank transfer or SBP.

How to Fund Your Account
Crypto Deposit:
  1. Get deposit address: GET /funding/deposit/crypto/address
  2. Send crypto to the received address from an external wallet
  3. Track status via GET /transactions
Fiat Deposit:
  1. Get available methods: GET /funding/deposit/fiat/methods — use code (SBER, TINKOFF, …)
  2. Create request: POST /funding/deposit/fiat with currency, amount and payment_provider_alias_code from the methods list
  3. Pay via invoice_public_url, requisites_qr_code or requisites — they are the same SBP link. If all three are still null, poll GET /funding/deposit/fiat/{transaction_id}
💡
Tip

For crypto deposits, you only need to get the address once — it does not change. Save it on your side and reuse.

GET/api/v1/integration/funding/deposit/crypto/address

Get deposit address for cryptocurrency. If address not yet generated — it will be created automatically. Params: symbol (required), network (optional).

GET /funding/deposit/crypto/address?symbol=USDT&network=TRX
{
  "success": true,
  "data": {
    "symbol": "USDT",
    "addresses": [
      { "network": "TRX", "address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE" }
    ]
  },
  "ts": 1706000000
}
GET/api/v1/integration/funding/deposit/fiat/methods

Returns available fiat deposit methods: banks, SBP, etc. Parameter: currency (default RUB).

Response
{
  "success": true,
  "data": {
    "methods": [
      { "code": "SBER", "name": "Сбербанк", "type": "sbp" },
      { "code": "TINKOFF", "name": "Т-Банк", "type": "sbp" },
      { "code": "ALFA", "name": "Альфа-Банк", "type": "sbp" }
    ]
  },
  "ts": 1706000000
}
POST/api/v1/integration/funding/deposit/fiat

Creates a fiat deposit. Take payment_provider_alias_code from GET /funding/deposit/fiat/methods (SBER, TINKOFF, ALFA, …). Do not send internal channel codes. invoice_public_url, requisites_qr_code and requisites are the same SBP payment link. If all three are still null, poll GET /funding/deposit/fiat/{transaction_id}. amount is the requested sum; credited_amount is the net credit after fee; fee is the commission. Both are null until the fact is known.

Request
{
  "currency": "RUB",
  "amount": "10000",
  "payment_provider_alias_code": "SBER"
}
Response
{
  "success": true,
  "data": {
    "transaction_id": "7ad2bfd0-673f-455a-b75b-95a949a7476a",
    "status": "processing",
    "currency": "RUB",
    "amount": "10000",
    "credited_amount": null,
    "fee": null,
    "payment_provider_alias_code": "SBER",
    "requisites": "https://qr.nspk.ru/...",
    "requisites_qr_code": "https://qr.nspk.ru/...",
    "invoice_public_url": "https://qr.nspk.ru/...",
    "expires_at": "2026-08-14T12:00:00Z",
    "created_at": "2026-08-14T11:00:00Z"
  },
  "ts": 1706000000
}
GET/api/v1/integration/funding/deposit/fiat/{transaction_id}

Fiat deposit status. transaction_id is the UUID from POST /funding/deposit/fiat. invoice_public_url, requisites_qr_code and requisites are the same SBP link. status: processing | success | failed. The terminal outcome also arrives as deposit.confirmed / deposit.failed webhooks (data.kind=fiat). This is not invoice.paid: Invoice V2 is a separate product. amount is the requested sum; credited_amount is the net credit after fee; fee is the commission. Reconcile against credited_amount, not amount.

Response
{
  "success": true,
  "data": {
    "transaction_id": "7ad2bfd0-673f-455a-b75b-95a949a7476a",
    "status": "success",
    "currency": "RUB",
    "amount": "6500",
    "credited_amount": "6383",
    "fee": "117",
    "payment_provider_alias_code": "SBER",
    "requisites": "https://qr.nspk.ru/...",
    "requisites_qr_code": "https://qr.nspk.ru/...",
    "invoice_public_url": "https://qr.nspk.ru/...",
    "expires_at": "2026-08-14T12:00:00Z",
    "created_at": "2026-08-14T11:00:00Z"
  },
  "ts": 1706000000
}

Withdrawals

Withdraw funds from your funding account to external addresses and via SBP. Crypto is sent to a blockchain address; fiat (RUB) is paid by phone number via SBP to the selected bank.

⚠️
Important

Withdrawal deducts from your funding account. Make sure balance is sufficient (GET /account/wallets). Larger withdrawals may require KYC verification.

POST/api/v1/integration/funding/withdraw/crypto

Creates crypto withdrawal to external address. Funds are debited from funding account. Symbol, network, address and amount are required.

Request
{
  "symbol": "USDT",
  "amount": "100",
  "address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
  "bch_code": "TRX",
  "idempotency_key": "unique-withdraw-key-123"
}
Response
{
  "success": true,
  "data": {
    "transaction_id": "...",
    "symbol": "USDT",
    "amount": "100",
    "fee": "1.0",
    "total": "101.0",
    "address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
    "network": "TRX",
    "status": "processing"
  },
  "ts": 1706000000
}

amount is what the recipient gets. fee is the full commission charged on top of amount. total is the debit from your balance (amount + fee). Reconcile the balance against total, not amount. withdraw_service_fee from the currencies directory is the network base rate; the actual quote is in this POST response. Send idempotency_key: a repeat with the same key within 24 h returns the first request (even if it is already failed) — a new attempt needs a new key. Without a key, a dropped response plus retry risks a second payout. Terminal outcome — withdrawal.processing / .completed / .failed webhooks (kind=crypto).

Amount precision and network limits

Every network accepts its own number of decimals. USDT on TRX allows 6, the same USDT on BSC allows 8. Extra decimals are not an error: the amount is truncated down to the network precision and the remainder stays on your balance. The amount field in the response holds the actually accepted value — reconcile against it, not against what you sent.

Precision, minimum, maximum and withdrawal availability for every coin+network pair come from the GET /api/v1/integration/market/currencies directory — fields withdraw_amount_decimals, min_withdraw_amount, max_withdraw_amount, withdraw_service_fee, is_withdraw_active.

GET/api/v1/integration/funding/withdraw/fiat/banks

Bank directory for SBP withdraw. Returns public bank_code values (TINKOFF, SBER, …) to pass into POST /withdraw/fiat. Internal Pay provider codes are not exposed.

Response
{
  "success": true,
  "data": {
    "currency": "RUB",
    "banks": [
      { "bank_code": "TINKOFF", "name": "T-Pay" },
      { "bank_code": "SBER", "name": "S-Pay" },
      { "bank_code": "ALFA", "name": "A-Pay" }
    ]
  },
  "ts": 1706000000
}
POST/api/v1/integration/funding/withdraw/fiat

Creates a RUB SBP withdrawal to the recipient phone. Payout channel (provider) is resolved server-side from the profile assignment — do not send it. Ledger balance is checked synchronously (amount + fee).

Request
{
  "currency": "RUB",
  "amount": "500",
  "recipient": "79992122496",
  "bank_code": "TINKOFF",
  "idempotency_key": "unique-fiat-withdraw-key-456"
}
FieldTypeDescription
amountstringAmount to receive (without fee), string
recipientstringSBP phone in 7XXXXXXXXXX format
bank_codestringBank code from GET /withdraw/fiat/banks
currencystring?RUB only (default RUB)
idempotency_keystring?Idempotency key, 24 h. After failed the same key returns the first request and does not create a new one — use a new key.
Response
{
  "success": true,
  "data": {
    "transaction_id": "68182a10-9f8a-4c06-a1bf-4d5d66841284",
    "currency": "RUB",
    "amount": "500.00",
    "amount_fact": null,
    "fee": "15.00",
    "total": "515.00",
    "status": "processing",
    "recipient": "7999****96",
    "bank_code": "TINKOFF"
  },
  "ts": 1706000000
}

amount is what the recipient gets (without fee). fee is the commission. total is the debit from your balance (amount + fee). The balance is checked against total before the request is created. Send idempotency_key: TTL 24 h; after failed the same key does not create a new payout. Terminal outcome — withdrawal.completed / withdrawal.failed webhooks (kind=fiat); there is no processing event for SBP.

GET/api/v1/integration/funding/withdraw/fiat/{transaction_id}

Fiat withdraw status by transaction_id from POST /withdraw/fiat. The terminal outcome also arrives as withdrawal.completed / withdrawal.failed webhooks (data.kind=fiat). There is no withdrawal.processing event for SBP — while the request is in flight, poll this endpoint (status: processing | success | failed). amount is to the recipient; fee is the commission; total is the debit from your balance; amount_fact is the actual payout once completed. The SBP bank reference is not in the response.

Response
{
  "success": true,
  "data": {
    "transaction_id": "68182a10-9f8a-4c06-a1bf-4d5d66841284",
    "status": "success",
    "currency": "RUB",
    "amount": "500.00",
    "amount_fact": "500.00",
    "fee": "15.00",
    "total": "515.00",
    "bank_code": "TINKOFF",
    "recipient": "7999****96",
    "created_at": 1706000000,
    "updated_at": 1706000120,
    "error": null
  },
  "ts": 1706000120
}

Transfers

Three types of transfers: internal (to another user), to trading account and back. Internal transfers are instant and free.

When Do You Need a Trading Account Transfer?

If you want to trade on fiat spot (USDT/RUB orderbook), funds must be on the trading account. Use /funding/transfer/to-trading before trading and /funding/transfer/from-trading to return funds after trading.

Funding→ to-trading →Trading→ Trade →Trading→ from-trading →Funding
POST/api/v1/integration/funding/transfer/internal

Transfer funds to another user by username or ID. Funds are debited from sender funding account.

ℹ️
Crypto symbols only

The endpoint accepts crypto symbols only; RUB cannot be sent via API. A RUB transfer is made by the user in the wallet interface — you can hand them a pre-filled link (see “Pre-filled transfer link”). Incoming funds are observable the same way for both currencies: the transfer.received webhook and a record in GET /integration/transactions.

Request
{
  "symbol": "USDT",
  "amount": "100",
  "to_username": "john_doe",
  "to_user_id": null,
  "comment": "Payment for services",
  "idempotency_key": "unique-transfer-key-789"
}
Response
{
  "success": true,
  "data": {
    "task_id": "celery-task-id-...",
    "symbol": "USDT",
    "amount": "100",
    "to": "john_doe",
    "status": "processing"
  },
  "ts": 1706000000
}
POST/api/v1/integration/funding/transfer/to-trading

Transfer from funding to trading account. Required for fiat spot trading. Accepts currency symbol (USDT or RUB) and amount.

Request
{
  "currency": "USDT",
  "amount": "1000"
}
Response
{
  "success": true,
  "data": {
    "status": "ok"
  },
  "ts": 1706000000
}
POST/api/v1/integration/funding/transfer/from-trading

Transfer from trading back to funding account. Accepts currency symbol (USDT or RUB) and amount.

Request
{
  "currency": "USDT",
  "amount": "500"
}
Response
{
  "success": true,
  "data": {
    "status": "ok"
  },
  "ts": 1706000000
}

Crypto Spot

Crypto pair trading. Supports market and limit orders. Operations are executed from the funding account.

How to Buy BTC with USDT — Step by Step
  1. Check tickers: GET /spot/crypto/tickers — current prices for all pairs (public, no key)
  2. Get pair details: GET /spot/crypto/market-info?from_symbol=BTC&to_symbol=USDT (public, no key)
  3. Check available trading pairs: GET /spot/crypto/pairs (requires API key)
  4. Create order: POST /spot/crypto/order with type="market" (instant) or type="limit" (at your price)
  5. Track status: GET /spot/crypto/orders
Order types:
  • market — instant execution at current market price. Specify from_amount OR to_amount. Ideal for quick exchange.
  • limit — order at a specified price. Executed when market price reaches the target. Requires from_amount and order_price. Good for DCA strategies and buying dips.
💡
Which Order Type?

Market — when speed matters (exchange right now). Limit — when price matters (want to buy cheaper). Limit orders can be cancelled before execution via DELETE /spot/crypto/order/{order_id}.

Public Data (no API key)

GET/api/v1/integration/spot/crypto/tickers

Current tickers for all crypto pairs — prices, bid/ask, 24h volumes. Cached and updated every ~3 minutes. No API key required.

Query Parameters

ParameterTypeDescription
symbolstringFilter by base symbol: BTC, ETH (optional)
Response
{
  "success": true,
  "data": [
    {
      "pair": "BTC-USDT",
      "base": "BTC",
      "quote": "USDT",
      "last_price": "97450.50",
      "bid_price": "97448.00",
      "ask_price": "97453.00",
      "volume_24h": "12450000",
      "high_24h": "98100.00",
      "low_24h": "96800.00",
      "price_change_24h": "0.0124"
    }
  ],
  "ts": 1706000000
}
GET/api/v1/integration/spot/crypto/market-info

Detailed info for a specific trading pair: current rate, min/max amounts, trading filters. No API key required.

Query Parameters

ParameterTypeDescription
from_symbol *stringSource currency: BTC
to_symbol *stringTarget currency: USDT
Response
{
  "success": true,
  "data": {
    "symbol": "BTCUSDT",
    "priceTo": "71114.201",
    "priceOut": "69706.098",
    "price": "70410.2",
    "mainSymbol": "BTC",
    "minorSymbol": "USDT",
    "quote_asset_precision": 7,
    "base_asset_precision": 6,
    "step": "0.000001",
    "minValue": "0.000001",
    "min_value_minor": "5",
    "maxValue": "230",
    "max_value_minor": "8000000",
    "limit_price_step": "0.005",
    "price_step": "0.1"
  },
  "ts": 1706000000
}

Trading (API key required)

GET/api/v1/integration/spot/crypto/pairs

List of available trading pairs with limits. Requires API key with can_spot_crypto_read permission.

Query Parameters

ParameterTypeDescription
symbolstringFilter by symbol (optional)
Response
{
  "success": true,
  "data": ["NAKA", "SIGN", "ARKM", "BTC", "ETH", "SOL"],
  "ts": 1706000000
}
GET/api/v1/integration/spot/crypto/pair-info

Pair info: current rate, min/max exchange amounts.

Query Parameters

ParameterTypeDescription
from_symbolstringSource currency (required)
to_symbolstringTarget currency (required)
Response
{
  "success": true,
  "data": {
    "symbol": "BTCUSDT",
    "priceTo": "71114.201",
    "priceOut": "69706.098",
    "price": "70410.2",
    "mainSymbol": "BTC",
    "minorSymbol": "USDT",
    "quote_asset_precision": 7,
    "base_asset_precision": 6,
    "step": "0.000001",
    "minValue": "0.000001",
    "min_value_minor": "5",
    "maxValue": "230",
    "max_value_minor": "8000000",
    "limit_price_step": "0.005",
    "price_step": "0.1"
  },
  "ts": 1706000000
}
POST/api/v1/integration/spot/crypto/order

Market Order

Instant exchange at current market price. Specify from_amount (how much to spend) OR to_amount (how much to receive).

Request Body

FieldTypeRequiredDescription
from_symbolstringyesSource currency (e.g. USDT)
to_symbolstringyesTarget currency (e.g. BTC)
from_amountstringyes*Amount in source currency
to_amountstringyes*Amount in target currency
typestringno"market" (default)

* Specify from_amount OR to_amount, but not both.

Request
{
  "from_symbol": "USDT",
  "to_symbol": "BTC",
  "from_amount": "100",
  "type": "market"
}
Response
{
  "success": true,
  "data": {
    "order_id": "550e8400-e29b-41d4-a716-446655440000",
    "from_symbol": "USDT",
    "to_symbol": "BTC",
    "from_amount": "100",
    "to_amount": "0.00231",
    "type": "market",
    "order_price": null,
    "status": "processing"
  },
  "ts": 1706000000
}
POST/api/v1/integration/spot/crypto/order

Limit Order

Order at a specified price. Will be executed when market price reaches the target level. Requires from_amount and order_price.

Request Body

FieldTypeRequiredDescription
from_symbolstringyesSource currency (e.g. USDT)
to_symbolstringyesTarget currency (e.g. BTC)
from_amountstringyesAmount in source currency
typestringyes"limit"
order_pricestringyesDesired execution price (e.g. "42000.00")
Request
{
  "from_symbol": "USDT",
  "to_symbol": "BTC",
  "from_amount": "500",
  "type": "limit",
  "order_price": "42000.00"
}
Response
{
  "success": true,
  "data": {
    "order_id": "550e8400-e29b-41d4-a716-446655440001",
    "from_symbol": "USDT",
    "to_symbol": "BTC",
    "from_amount": "500",
    "to_amount": null,
    "type": "limit",
    "order_price": "42000.00",
    "status": "processing"
  },
  "ts": 1706000000
}
DELETE/api/v1/integration/spot/crypto/order/{order_id}

Cancel a limit order. Only works for limit orders that have not been executed yet.

Path Parameters

ParameterTypeDescription
order_idstringOrder ID from POST /order response
Response
{
  "success": true,
  "data": {
    "cancelled": true,
    "order_id": "550e8400-e29b-41d4-a716-446655440001",
    "status": "cancelled"
  },
  "ts": 1706000000
}
Errors
// Order not found
{ "success": false, "error": { "code": "ORDER_NOT_FOUND", "message": "Order not found" } }

// Order is not a limit order
{ "success": false, "error": { "code": "CANCEL_FAILED", "message": "Not a limit order" } }

// Order already cancelled
{ "success": false, "error": { "code": "CANCEL_FAILED", "message": "Order already cancelled" } }
GET/api/v1/integration/spot/crypto/orders

Crypto exchange order history with pagination. Includes market and limit orders.

Query Parameters

ParameterTypeDefaultDescription
limitinteger50Number of records (1-100)
pageinteger1Page number
Response
{
  "success": true,
  "data": [
    {
      "order_id": "...",
      "from_symbol": "USDT", "to_symbol": "BTC",
      "from_amount": "100", "to_amount": "0.00231",
      "type": "market",
      "status": "completed", "created_at": "2024-01-15T14:30:00Z"
    },
    {
      "order_id": "...",
      "from_symbol": "USDT", "to_symbol": "ETH",
      "from_amount": "500", "to_amount": null,
      "type": "limit", "order_price": "2100.00",
      "status": "processing", "created_at": "2024-01-15T15:00:00Z"
    }
  ],
  "ts": 1706000000
}

Fiat Spot

Full USDT/RUB orderbook with market and limit orders. Powered by the in-house AlfaBit trading engine. Unlike Crypto Spot, here you see a real orderbook and can control the price.

⚠️
Trading Account — Required Step

Trading runs on a separate trading balance. Before your first trade, transfer funds:

POST /api/v1/integration/funding/transfer/to-trading
{ "currency": "USDT", "amount": "1000" }
Flow: Buy USDT for RUB at a Limit Price
  1. Transfer RUB to trading account: POST /funding/transfer/to-trading
  2. Check balance: GET /spot/fiat/balance
  3. Study orderbook and current price: GET /spot/fiat/orderbook and GET /spot/fiat/stats
  4. Accept an order: POST /spot/fiat/order — you get an id immediately (usually status=new). Do not wait for filled on this response.
  5. Track fill: GET /spot/fiat/order/{id} / GET /spot/fiat/orders or webhook order.filled
  6. Return USDT to main account: POST /funding/transfer/from-trading

Public Data (no API key)

GET/api/v1/integration/spot/fiat/instruments

List of trading instruments (pairs) and their statuses.

Response
{
  "success": true,
  "data": [
    { "trading_pair": "USDT/RUB", "status": "active", "base": "USDT", "quote": "RUB" }
  ],
  "ts": 1706000000
}
GET/api/v1/integration/spot/fiat/balance

Trading account balances: USDT and RUB (available, locked).

Response
{
  "success": true,
  "data": {
    "USDT": { "available": "5000.00", "locked": "100.00" },
    "RUB": { "available": "150000.00", "locked": "0.00" }
  },
  "ts": 1706000000
}
GET/api/v1/integration/spot/fiat/orderbook?pair=USDT/RUB&depth=20

Orderbook: bids and asks. Parameter depth — orderbook depth (default 20).

Response
{
  "success": true,
  "data": {
    "bids": [
      { "price": "92.45", "amount": "5000" },
      { "price": "92.40", "amount": "12000" }
    ],
    "asks": [
      { "price": "92.55", "amount": "3000" },
      { "price": "92.60", "amount": "8000" }
    ]
  },
  "ts": 1706000000
}
GET/api/v1/integration/spot/fiat/trades?pair=USDT/RUB&limit=50

Public trades feed. No API key required.

Response
{
  "success": true,
  "data": [
    { "id": "...", "price": "92.50", "amount": "100", "side": "buy", "time": "2024-01-15T14:30:00Z" }
  ],
  "ts": 1706000000
}
GET/api/v1/integration/spot/fiat/stats?pair=USDT/RUB

24h statistics: volume, high, low, last price.

Response
{
  "success": true,
  "data": {
    "pair": "USDT/RUB", "last": "92.50", "high": "93.10",
    "low": "91.80", "volume": "1250000", "change": "+0.5%"
  },
  "ts": 1706000000
}

Trading (API key required)

POST/api/v1/integration/spot/fiat/order

Accept an order. Response is the same JSON with an id (usually status=new). Execution is async: do not expect filled on this POST. Use GET /spot/fiat/order/{id}, GET /spot/fiat/orders, or webhook order.filled. 4xx (balance, book, limits) is still synchronous — no order is created.

Market Order

Request
{
  "pair": "USDT/RUB",
  "side": "buy",
  "type": "market",
  "amount": "100"
}

Market Order — by quote amount (quote_amount)

If you start with rubles and do not know how much USDT you will get — pass quote_amount (the amount of quote currency you want to spend) instead of amount. The engine picks the base amount from the current orderbook without exceeding your budget. Market buy only. amount and quote_amount are mutually exclusive. Any unused budget is refunded; in a fast market the actually filled amount may differ slightly (as with any market order).

Request
{
  "pair": "USDT/RUB",
  "side": "buy",
  "type": "market",
  "quote_amount": "10000"
}

Limit Order

Request
{
  "pair": "USDT/RUB",
  "side": "sell",
  "type": "limit",
  "amount": "500",
  "price": "93.00"
}
Response
{
  "success": true,
  "data": {
    "order_id": "ord_limit_xyz789",
    "pair": "USDT/RUB",
    "side": "sell",
    "type": "limit",
    "amount": "500",
    "price": "93.00",
    "status": "open"
  },
  "ts": 1706000000
}
DELETE/api/v1/integration/spot/fiat/order/{order_id}

Cancel limit order by ID. Market orders cannot be cancelled — they execute instantly.

GET/api/v1/integration/spot/fiat/orders

Your open and filled orders on fiat spot.

Response
{
  "success": true,
  "data": [
    { "id": "...", "pair": "USDT/RUB", "side": "buy", "type": "limit",
      "price": "92.00", "amount": "100", "filled": "0", "status": "open" }
  ],
  "ts": 1706000000
}
GET/api/v1/integration/spot/fiat/my-trades

Your executed trades on fiat spot.

Response
{
  "success": true,
  "data": [
    { "id": "...", "pair": "USDT/RUB", "side": "buy",
      "price": "92.45", "amount": "100", "fee": "0.1", "time": "2024-01-15T14:30:00Z" }
  ],
  "ts": 1706000000
}

More public data

GET/api/v1/integration/spot/fiat/tickers

Aggregated tickers for all active pairs: price, bid/ask, 24h volume, change. Counterpart of /spot/crypto/tickers for fiat pairs.

GET/api/v1/integration/spot/fiat/market-info?pair=USDT/RUB

Detailed info for one pair: current price, spread, min/max amounts, price/quantity steps, precision.

GET/api/v1/integration/spot/fiat/instruments/{pair}

Details of one pair (status, base/quote, limits).

GET/api/v1/integration/spot/fiat/currencies

Trading account currency directory (USDT, RUB, etc.) with symbol and precision.

More private endpoints (API key required)

GET/api/v1/integration/spot/fiat/balance/{symbol}

Trading balance for a single currency (e.g. USDT or RUB).

GET/api/v1/integration/spot/fiat/order/{order_id}

Single order details by ID: status, fills, average price, fees.

Response
{
  "success": true,
  "data": {
    "id": "3f1c9a2e-8b7d-4e6a-9c11-2a5f4d7e0b9c",
    "order_number": 10432,
    "user_id": "b2c9e7a1-4f83-4d2a-9e6b-1c0f5a8d3e21",
    "side": "buy",
    "type": "market",
    "price": null,
    "amount": "100",
    "filled": "100",
    "status": "filled",
    "created_at": "2026-07-24T14:28:00.123456",
    "trading_pair": "USDT/RUB",
    "execution_message": "Order executed successfully",
    "avg_execution_price": "92.35",
    "total_fee": "9.235",
    "user_input_price": null,
    "user_input_price_total": "10000",
    "user_input_amount": null,
    "locked_quote_amount": "10000"
  },
  "ts": 1753363680
}

Numeric fields are returned as strings (to preserve precision). price and avg_execution_price are null until set / until the order is executed.

  • id — order ID (UUID).
  • order_number — human-readable order number.
  • user_id — internal user ID.
  • side — buy or sell.
  • type — market or limit.
  • price — limit order price; null for market.
  • amount — order amount in base currency (USDT).
  • filled — fill — how much base currency is already executed (in amount units).
  • status — new / partially_filled / filled / cancelled.
  • created_at — creation time (ISO-8601).
  • trading_pair — trading pair, e.g. USDT/RUB.
  • execution_message — execution message (relevant for market); may be null.
  • avg_execution_price — average execution price; null until there are trades.
  • total_fee — total fee for the order.
  • user_input_price, user_input_price_total, user_input_amount — original user input (display only). For a market buy with quote_amount, user_input_price_total holds the requested budget.
  • locked_quote_amount — amount of quote currency (RUB) locked for the BUY order.
GET/api/v1/integration/spot/fiat/trading/operations

History of deposit/withdraw operations on the trading account, paginated. Filters: currency_id, operation_type (deposit/withdraw), status (pending/completed/failed).

GET/api/v1/integration/spot/fiat/trading/settings/{symbol}

Deposit/withdraw limits and fee_percent for a specific currency.

Idempotency

POST /spot/fiat/order and DELETE /spot/fiat/order/{id} support Idempotency-Key header (1-128 chars: letters/digits/-/_, UUID recommended). Repeat POST with the same key does not create a second order (TTL 24h). For create, retry returns the current order from the engine (not the frozen accept snapshot) — safe after a dropped connection to learn status/filled.

Headers
X-API-Key: pk_live_...
X-API-Signature: ...
X-API-Timestamp: ...
Idempotency-Key: 4b8c8a1e-3f2c-4f3a-9c0d-2b3a4b5c6d7e

WebSockets

Real-time channel for integrators. Single WS server for all topics: Spot Fiat trading events, balance updates, invoice status, converter updates.

URL
wss://alfabit.org/wallet-web/ws/ws

Authentication (auth_api_key)

WS uses a simplified signature HMAC-SHA256(secret, timestamp + api_key) — no method/path/body. Allowed timestamp drift is ±300 sec.

Handshake example (JS)
const ws = new WebSocket('wss://alfabit.org/wallet-web/ws/ws');

// 1) handshake
ws.send(JSON.stringify({
  action: 'auth_api_key',
  api_key: 'pk_live_xxxxxxxxxxxx',
  timestamp: '1714210000',
  signature: '<hmac-sha256(secret, timestamp + api_key) hex>',
}));

// 2) subscribe
ws.send(JSON.stringify({
  action: 'subscribe',
  topics: [
    'spot.fiat.balance',
    'spot.fiat.order',
    'spot.fiat.orderbook.USDT/RUB'
  ]
}));
Signature (Python)
import hashlib, hmac, time
ts = str(int(time.time()))
sig = hmac.new(secret_key.encode(), (ts + api_key).encode(), hashlib.sha256).hexdigest()

Public topics (no auth)

Orderbook, trades, stats, charts — industry standard.

  • spot.fiat.orderbook.{pair} — Orderbook snapshot (bids/asks) for the pair.
  • spot.fiat.trades.{pair} — Trade tape for the pair.
  • spot.fiat.stats.{pair} — 24h stats for the pair.
  • spot.fiat.chart.{symbol}.{resolution} — TradingView candles for pair and interval (1, 5, 15, 30, 60, 240, D, W, M).

Private topics (require can_spot_fiat_read)

Your own orders and trading balance — filtered by profile_id, only visible to the API key owner.

  • spot.fiat.order — Status changes of your orders (created, filled, partially_filled, cancelled).
  • spot.fiat.balance — Trading balance changes.

Reconnect

On disconnect reconnect with exponential backoff: 1s → 2s → 4s → ... up to 30s. After reconnect repeat auth_api_key and subscribe.

Converter

Fast conversion with fixed rate. Supports crypto-crypto and crypto-fiat exchange. Unlike Spot, the converter guarantees the price — you get exactly what the quote showed.

How It Works: 2 Steps
1. Estimate (quote)2. Execute

The quote locks the rate for a limited time (see expires_in_seconds in response). Call /execute with the received quote_id within this time. If you miss the window — request a new quote.

💡
Converter vs Spot — Which to Choose?

Converter — for one-off operations with guaranteed price (send a client exactly $100 in ETH). Spot — for regular trading and strategies (DCA, limit orders, large volumes).

Crypto Converter

Rates and symbols are public. Exchange requires API key.

GET/api/v1/integration/converter/crypto/symbols

List of cryptocurrencies available for conversion. No API key required.

GET/api/v1/integration/converter/crypto/rate?from=BTC&to=ETH

Final exchange rate, limits and quote TTL. No API key required.

Response
{
  "success": true,
  "data": {
    "from_symbol": "BTC",
    "to_symbol": "ETH",
    "rate": "16.468",
    "min_amount_usdt": "10",
    "max_amount_usdt": "50000",
    "expires_in_seconds": 30
  },
  "ts": 1706000000
}
POST/api/v1/integration/converter/crypto/estimate

Get a fixed quote. Returns quote_id for /execute. Quote lifetime is specified in the expires_in_seconds field.

Request
{
  "from_symbol": "BTC",
  "to_symbol": "ETH",
  "from_amount": "0.5"
}
Response
{
  "success": true,
  "data": {
    "quote_id": "qt_abc123def456",
    "from_symbol": "BTC",
    "to_symbol": "ETH",
    "from_amount": "0.5",
    "to_amount": "8.234",
    "rate": "16.468",
    "expires_in_seconds": 30
  },
  "ts": 1706000000
}
POST/api/v1/integration/converter/crypto/execute

Execute conversion by previously obtained quote. Returns error if quote expired. Optional idempotency_key (valid 24h): a repeat request with the same key will not create a second conversion — the same result is returned (retry-safe). An identical request still in progress returns HTTP 409.

Request
{
  "quote_id": "qt_abc123def456",
  "idempotency_key": "your-unique-key-123"
}
Response
{
  "success": true,
  "data": {
    "uid": "conv_op_abc123",
    "from_symbol": "BTC",
    "to_symbol": "USDT",
    "from_amount": "0.01",
    "to_amount": "704.10",
    "rate": "70410.2",
    "status": "completed"
  },
  "ts": 1706000000
}

Fiat Converter

Convert crypto to fiat and vice versa (e.g. USDT → RUB). Rates are public, exchange requires API key.

GET/api/v1/integration/converter/fiat/currencies

Fiat currencies currently open for conversion. No API key required. Read it before requesting a rate: the set changes, and a request with a code missing from this response will be rejected.

Response
{
  "success": true,
  "data": [
    {
      "code": "RUB",
      "name": "Российский рубль",
      "symbol": "₽",
      "decimals": 2,
      "min_amount": "100.00",
      "max_amount": "250000.00"
    }
  ],
  "ts": 1706000000
}
  • decimals — amount precision for this currency. Round by it, not by your own format.
  • min_amount / max_amount — per-conversion limits in fiat units. null means no limit is set.
GET/api/v1/integration/converter/fiat/crypto-symbols

A flat array of crypto symbols allowed against fiat. No API key required. The list is long and changes — do not hardcode it, fetch and cache it on your side.

Response
{
  "success": true,
  "data": ["0G", "1INCH", "AAVE", "ADA", "BTC", "ETH", "USDT", "..."],
  "ts": 1706000000
}
GET/api/v1/integration/converter/fiat/rate?crypto=USDT&fiat=RUB&direction=sell

Current crypto/fiat rate. Params: crypto (symbol), fiat (code), direction (buy/sell).

Response
{
  "success": true,
  "data": {
    "crypto": "USDT", "fiat": "RUB", "direction": "sell",
    "rate": "92.50", "min_amount": "10", "max_amount": "100000"
  },
  "ts": 1706000000
}
POST/api/v1/integration/converter/fiat/estimate

Get quote for fiat conversion.

Request
{
  "crypto_symbol": "USDT",
  "fiat_code": "RUB",
  "direction": "sell",
  "amount": "100"
}
Response
{
  "success": true,
  "data": {
    "quote_id": "qt_fiat_abc123",
    "crypto_symbol": "USDT",
    "fiat_code": "RUB",
    "direction": "sell",
    "crypto_amount": "100",
    "fiat_amount": "9250.00",
    "rate": "92.50",
    "fee": "1.00",
    "expires_at": "2024-01-20T12:35:00Z"
  },
  "ts": 1706000000
}
POST/api/v1/integration/converter/fiat/execute

Execute fiat conversion by quote. Same fields as /fiat/estimate (crypto_symbol, fiat_code, direction), amount in from_amount, plus quote_id from the estimate response. quote_id alone is not enough — missing fields return 422. There is no separate idempotency_key: a repeat with the same quote_id returns the already created operation. A direct-exchange quote lives 30 seconds. Status — GET /converter/operations; success also arrives as conversion.completed.

Request
{
  "crypto_symbol": "USDT",
  "fiat_code": "RUB",
  "direction": "sell",
  "from_amount": "100",
  "quote_id": "qt_fiat_xyz789"
}
Response
{
  "success": true,
  "data": {
    "uid": "fiat_conv_xyz789",
    "crypto_symbol": "USDT",
    "fiat_code": "RUB",
    "direction": "sell",
    "crypto_amount": "100",
    "fiat_amount": "9250.00",
    "status": "completed"
  },
  "ts": 1706000000
}
GET/api/v1/integration/converter/operations?limit=20

All conversion history (crypto + fiat). Parameter type: crypto or fiat.

Response
{
  "success": true,
  "data": [
    { "uid": "...", "type": "crypto", "from_symbol": "BTC", "to_symbol": "ETH",
      "from_amount": "0.5", "to_amount": "8.234", "status": "completed" }
  ],
  "ts": 1706000000
}

Virtual Cards

Issue and manage virtual Visa/Mastercard bank cards. Pay for subscriptions, advertising and purchases using cryptocurrency.

💳
Card Types

SHOPPING — for online purchases. ADVERTISING — for ad platforms. Types and available options depend on your plan.

Flow: Issue and Top Up a Card
  1. Check conditions: GET /cards/settings (fees, limits) and GET /cards/meta (regions, payment systems)
  2. Get rate: GET /cards/rate (USDT → USD for cost calculation)
  3. Issue card: POST /cards (amount + payment_method: balance_usdt | onchain_usdt | rub_sbp). The ready card appears in GET /cards (async for balance_usdt; card.transaction webhook is also sent)
  4. Top up card: POST /cards/{card_id}/topup — funds are converted to USD
  5. Use card for payments and track transactions: GET /cards/{card_id}/transactions
GET/api/v1/integration/cards/settings

Card settings: types, issuance fee (buy_fee), top-up fee (top_up_fee), required top-up.

GET/api/v1/integration/cards/meta

Meta info: available currencies, regions, payment systems (Visa/Mastercard), Apple Pay.

GET/api/v1/integration/cards/rate

Current USDT/RUB rate for issuance and top-up cost calculation.

GET/api/v1/integration/cards

All user cards: type, status, balance, payment system.

POST/api/v1/integration/cards

Issue a virtual card (book + payment). amount is required. Payment via payment_method: balance_usdt (debit from balance), onchain_usdt (USDT transfer, network required) or rub_sbp (SBP). See current region / payment_system / currency in GET /cards/meta.

Request (from balance)
{
  "amount": "10",
  "payment_method": "balance_usdt",
  "source_symbol": "USDT",
  "card_type": "SHOPPING",
  "is_apple_pay_available": false
}
Request (SBP / on-chain USDT)
// rub_sbp — в ответе qr/ссылка на оплату
{ "amount": "10", "payment_method": "rub_sbp", "card_type": "SHOPPING" }

// onchain_usdt — в ответе адрес депозита
{ "amount": "10", "payment_method": "onchain_usdt", "onchain_network": "trc20", "card_type": "SHOPPING" }
ParameterDescription
amountRequired. Initial card top-up amount (USD).
payment_methodbalance_usdt, onchain_usdt, rub_sbp
source_symbolDebit asset for balance_usdt (default USDT).
onchain_networktrc20 | erc20 — required for onchain_usdt.
card_type SHOPPING or ADVERTISING
region / payment_systemOptional. Current values from GET /cards/meta (now HK / mastercard). Auto-selected if omitted.
idempotency_keyOptional (uuid4). Protects against duplicate issuance.
ℹ️

For balance_usdt issuance is async: the response is an order; get the ready card via GET /cards and the card.transaction webhook. For rub_sbp/onchain_usdt pay first using the returned details.

GET/api/v1/integration/cards/{card_id}

Card data: number, CVV, expiry date, status (ACTIVE, FROZEN, BLOCKED).

Response
{
  "success": true,
  "data": {
    "id": 123, "card_type": "SHOPPING", "status": "ACTIVE",
    "number": "4111 **** **** 1234", "cvc": "***",
    "date_expired": "12/27", "payment_system": "VISA",
    "balance": "150.00", "region": "WW"
  },
  "ts": 1706000000
}
GET/api/v1/integration/cards/{card_id}/balance

Current card balance in USD.

POST/api/v1/integration/cards/{card_id}/topup

Top up card. Amount converted to USD. Method: crypto, fiat_wallet or sbp.

Request
{
  "amount": "50",
  "source_symbol": "USDT",
  "payment_method": "crypto"
}
GET/api/v1/integration/cards/{card_id}/transactions

Card transactions: purchases, top-ups, refunds.

GET/api/v1/integration/cards/transactions/all

Transactions across ALL user cards. Can filter by card_type.

Gift Cards

Purchase gift cards and certificates of popular services (Steam, PlayStation, Spotify, etc.) with crypto. Certificate code is delivered instantly.

Flow: Buy a Gift Card
CatalogEstimatePurchaseGet code

First find the product in the catalog, then check how much it costs in crypto via /estimate, and finally buy via /purchase.

GET/api/v1/integration/giftcards/catalog

Available gift cards catalog. Supports category filtering, name search and pagination.

GET/api/v1/integration/giftcards/categories

Category list: Games, Entertainment, Music, Marketplaces, etc.

Response
{
  "success": true,
  "data": [
    { "id": 1, "title": "Игры", "slug": "games", "is_active": true, "products_count": 42 },
    { "id": 2, "title": "Развлечения", "slug": "entertainment", "is_active": true, "products_count": 18 },
    { "id": 3, "title": "Музыка", "slug": "music", "is_active": true, "products_count": 7 }
  ],
  "ts": 1706000000
}
POST/api/v1/integration/giftcards/estimate

Calculate gift card cost in crypto before purchase. Shows final amount including fees.

Request
{
  "product_id": 123,
  "face_value": 1000,
  "symbol": "USDT"
}
Response
{
  "success": true,
  "data": {
    "product_id": 123,
    "face_value": 1000,
    "crypto_amount": "10.85",
    "symbol": "USDT",
    "fee": "0.15",
    "total": "11.00"
  },
  "ts": 1706000000
}
POST/api/v1/integration/giftcards/purchase

Purchase gift card. Funds debited from crypto wallet. Certificate code is available in response (GET /giftcards/orders/{order_id}).

Request
{
  "product_id": 123,
  "face_value": 1000,
  "symbol": "USDT",
  "email": "user@example.com"
}
Response
{
  "success": true,
  "data": {
    "order_id": "gc_ord_abc123",
    "product_id": 123,
    "face_value": 1000,
    "crypto_amount": "11.00",
    "symbol": "USDT",
    "status": "processing"
  },
  "ts": 1706000000
}
GET/api/v1/integration/giftcards/orders

Gift card purchase history with certificate codes and statuses.

Response
{
  "success": true,
  "data": [
    { "id": "...", "product": "Steam 1000₽", "face_value": 1000,
      "cost_usdt": "11.50", "status": "completed", "code": "XXXX-YYYY-ZZZZ" }
  ],
  "ts": 1706000000
}

Pay Services

A service catalog for resale: mobile, internet, games, utilities and more. You buy the service from your AlfaBit balance (RUB or USDT) and sell it to your customer at your own price. Markup is yours — the API does not calculate or store it.

ℹ️
How to connect

Catalog and categories are public. Estimate, requisite check, pay and orders need an API key: can_services_read (read) and can_services_pay (debit). The catalog lists only enabled services. If it is empty, contact support to open the storefront.

Flow: buy a service and deliver it to your customer
CatalogEstimateRequisitePayStatus
  1. GET /services/catalog — show available services to your customer. Build the form from inputs (fallback: required_fields).
  2. POST /services/estimate — amount to debit from your balance in RUB or USDT (client_amount / client_currency).
  3. If requires_check=true or payment_type=REQUISITES — POST /services/check-requisite. Requisite: the requisite field or field_values.account / field_values.phone.
  4. POST /services/pay — debits your balance. You charge your customer yourself. Status: service_payment.* webhook or GET /services/orders/{id}.
GET/api/v1/integration/services/categories

Categories: Mobile, Games, Internet, TV, etc. No API key. Filter: country.

GET/api/v1/integration/services/catalog

Service catalog with pagination. No API key. Filters: category (alias), category_id, country, search, page, page_size.

GET/api/v1/integration/services/catalog/{service_id}

Service card: inputs, required_fields, payment_type, fixed_payment, requires_check, instruction. No API key.

Response
{
  "success": true,
  "data": {
    "id": 2,
    "name": "MegaCom",
    "category_alias": "mobile",
    "country": "Кыргызская Республика",
    "payment_type": "SIMPLIFIED",
    "fixed_payment": false,
    "requires_check": false,
    "inputs": [
      { "name": "account", "required": true, "title": "Номер", "regexp": "^0\\d{9}$" }
    ],
    "required_fields": []
  },
  "ts": 1706000000
}
POST/api/v1/integration/services/estimate

Amount to debit from your balance. debit_currency: RUB or USDT. For fixed_payment you may omit client_amount — the response returns it. Permission: can_services_read.

Request
{
  "service_id": 2,
  "debit_currency": "RUB",
  "client_amount": 500
}
Response
{
  "success": true,
  "data": {
    "service_id": 2,
    "service_name": "MegaCom",
    "client_amount": "512.40",
    "client_currency": "RUB",
    "quote_expires_at": "1706000300",
    "payment_type": "SIMPLIFIED",
    "fixed_payment": false,
    "requires_check": false
  },
  "ts": 1706000000
}
POST/api/v1/integration/services/check-requisite

Validate the account number. Required when requires_check=true or payment_type=REQUISITES. Requisite: requisite or field_values.account / phone. Pass agent_transaction_id and check_snapshot from the response into pay. Permission: can_services_read.

Request
{
  "service_id": 2,
  "debit_currency": "RUB",
  "client_amount": "512.40",
  "field_values": { "account": "0555123456" }
}
Response
{
  "success": true,
  "data": {
    "success": true,
    "agent_transaction_id": "agent-tx-uuid",
    "client_amount": "512.40",
    "client_currency": "RUB",
    "requires_identity": false,
    "check_snapshot": { "agent_transaction_id": "agent-tx-uuid" }
  },
  "ts": 1706000000
}
POST/api/v1/integration/services/pay

Pay from your balance (RUB or USDT). Pass quote_expires_at from estimate. If you called check-requisite — also agent_transaction_id and check_snapshot. Webhook: service_payment.*. Permission: can_services_pay.

Request
{
  "service_id": 2,
  "debit_currency": "RUB",
  "client_amount": "512.40",
  "field_values": { "account": "0555123456" },
  "quote_expires_at": "1706000300"
}
Response
{
  "success": true,
  "data": {
    "id": "550e8400-e29b-41d4-a716-446655440000",
    "service_id": 2,
    "status": "CREATED",
    "status_for_client": "wait",
    "client_amount": "512.40",
    "client_currency": "RUB",
    "requisite": "0555123456"
  },
  "ts": 1706000000
}
GET/api/v1/integration/services/orders

Payment history for your profile. Params: limit, offset. Permission: can_services_read. Use status_for_client for the storefront: wait, success, failed.

GET/api/v1/integration/services/orders/{order_id}

Order details. Same DTO as the pay response. Can be polled instead of a webhook.

Invoices — Accept Payments

Full-featured invoicing for business: one-off invoices, permanent QR/templates for cashier flows, donation links, open amount, deferred currency choice by the payer, automatic hedging, on-balance payments inside AlfaBit (no on-chain), embeddable Checkout widget and webhooks. Suitable for e-commerce, freelancers, charities, brick-and-mortar shops and POS cashier flows.

ℹ️
API version — v2

All invoice endpoints live under the prefix /api/v2/integration/invoices/.... v1 endpoints were removed on 2026-04-25 — use v2 only.

API-key permissions: can_invoices_create for create/cancel, can_invoices_read for reading and fetching the URL.

What it can do

  • One-off invoice. Fixed amount + TTL up to 30 days. The on-chain invoice is created without a hard amount: whatever arrives is credited. Overpay is the same success / invoice.paid with a larger amount_received. Underpay ≥ the network minimum is also success / invoice.paid with a smaller amount_received, not expired. expired is set only when there is no txid by TTL (no payment). Below the network minimum — failed, no invoice.paid.
  • Open amount (donations). Omit amount — the payer enters it on the payment page within the global min/max.
  • Deferred currency choice. Omit symbol/bch_code — the payer picks the coin and network on the page. Convenient for marketplaces.
  • Permanent QR (template). One QR forever — every payment creates its own child invoice. Suitable for cashier flows, salons, donation pages.
  • POS API. A cashier issues a receipt against a template via POST .../payments with idempotency_key and external_payment_id — repeated POST does not duplicate and is echoed back in the webhook.
  • Hedging (auto-conversion). Got USDT but want BTC on hand — set is_hedging + hedging_symbol to auto-convert on credit.
  • AlfaBit Checkout — pay from balance. AlfaBit users get a "Pay from AlfaBit" button. Internal balance transfer is instant, no on-chain transaction or network fee (payment_source="alfabit_balance", txid=null).
  • Payer-paid commission (shift_to_payer). Global platform setting: deduct the fee from the receiver, or grow the amount to pay — the payer pays on top.
  • Webhooks. Events invoice.created / paid / expired / cancelled / refunded / hedged (+ template.deactivated). URL is set separately via POST /api/v1/integration/webhooks — not in the invoice create body. HMAC signature; payload matches GET /invoices/{invoice_id}.
  • Embed widget. AlfaBit Checkout embeds right into your site via iframe / JS SDK / React / Vue with no buyer redirect.
Minimal 5-minute scenario
  1. Get commissions and limits: GET /api/v2/integration/invoices/settings.
  2. Create an invoice: POST /api/v2/integration/invoices with the Idempotency-Key header (one UUID per invoice) — get invoice_id and payment_url. On timeout retry with the same key: you get the same invoice, no duplicate.
  3. Send payment_url to the buyer (link/QR/iframe).
  4. Subscribe to the invoice.paid webhook (see Webhooks).
  5. Optionally, poll the status: GET /api/v2/integration/invoices/{invoice_id}.

Use cases

Real patterns covered by the current API. Each scenario is a sequence of endpoints and parameters that work in production.

1. E-commerce — invoice for a specific order

You know the amount and the currency. You want exactly that amount and want to track payment by your order_id.

POST /api/v2/integration/invoices
{
  "symbol": "USDT",
  "bch_code": "TRX",
  "amount": "100",
  "currency": "USDT",
  "description": "Order #ORD-12345 — iPhone case",
  "life_time_minutes": 30,
  "payment_policy": "all",
  "payer_email": "client@example.com",
  "show_receiver_publicly": false
}

In the `invoice.paid` webhook you receive the same uid (invoice_id) — match it in your DB against the saved order_id↔invoice_id mapping and close the order.

2. Freelancer — "pay any way" invoice

Amount known (in USDT) but the client prefers TON or BTC. Let the payer pick.

POST /api/v2/integration/invoices
{
  "amount": "250",
  "currency": "USDT",
  "description": "Frontend audit — May 2026"
}

Skip `symbol` and `bch_code` — the payment page shows the coin/network picker. Once chosen, both fields appear in GET responses and in the webhook.

3. Donations / tips — open amount

One recipient, many payers, each pays whatever they want. Better implemented as a permanent QR (see scenario 5).

POST /api/v2/integration/invoices
{
  "symbol": "USDT",
  "bch_code": "TRX",
  "description": "Buy me a coffee ☕"
}

No `amount` → open-amount invoice. The payer enters it within min/max from GET /settings (in USDT equivalent).

4. Receive USDT — keep BTC (hedging)

POST /api/v2/integration/invoices
{
  "symbol": "USDT",
  "bch_code": "TRX",
  "amount": "1000",
  "is_hedging": true,
  "hedging_symbol": "BTC"
}

After payment USDT is auto-converted to BTC at the converter rate. GET /settings exposes `hedging_commission_percent` — the extra hedging fee.

The actually credited BTC amount is in `hedged_amount` / `hedged_symbol` of GET /api/v2/integration/invoices/{invoice_id}. Conversion runs after payment, so at `invoice.paid` time they are still null; the final result arrives as a separate `invoice.hedged` event.

5. Permanent QR on the cafe wall

One QR — many payments. Each payment becomes a separate child invoice with its own address and TTL.

POST /api/v2/integration/invoices/v2/permanent
{
  "symbol": "USDT",
  "bch_code": "TRX",
  "description": "Coffee Shop on Tverskaya 7",
  "show_payments_count_publicly": true
}

You receive `template_uid` and `payment_url` — print a QR from this URL. The payer creates their own child invoice on the public page (entering the amount), pays — and you receive `invoice.paid` with `parent_template_uid`. `show_payments_count_publicly` shows the count of donors on the public page.

6. POS cashier — issue a receipt against a template

The shop has already created a template (see scenario 5). For every receipt the cashier sends POST .../payments with idempotency_key and external_payment_id (its own receipt ID).

POST /api/v2/integration/invoices/v2/permanent/{template_uid}/payments
{
  "amount": "12.50",
  "currency": "USDT",
  "description": "Receipt POS-AAA-9876",
  "external_payment_id": "POS-AAA-9876",
  "life_time_minutes": 15,
  "idempotency_key": "pos-aaa-9876-2026-05-15"
}

A repeat POST with the same `idempotency_key` returns THE SAME child — no duplicates. The `invoice.paid` webhook carries `external_payment_id` and `parent_template_uid` so the POS can auto-close the receipt.

7. Accept AlfaBit-balance only (closed community)

POST /api/v2/integration/invoices
{
  "symbol": "USDT",
  "amount": "10",
  "payment_policy": "alfabit_only"
}

`payment_policy`: "all" (default — both buttons), "alfabit_only" (AlfaBit balance only), "external_only" (blockchain only — "Pay from AlfaBit" button is hidden).

Settings & fees

A single endpoint to fetch current commissions, limits and feature availability. Use it before creating an invoice to validate the amount and show fees to the payer.

GET/api/v2/integration/invoices/settings

Required permission: can_invoices_read.

Response
{
  "success": true,
  "data": {
    "is_active": true,
    "invoice_commission_percent": "0.5",
    "hedging_commission_percent": "0.5",
    "shift_commission_to_payer": false,
    "is_hedging_enabled": true,
    "min_invoice_amount_usdt": "1",
    "max_invoice_amount_usdt": "10000000",
    "default_lifetime_minutes": 1440,
    "min_lifetime_minutes": 5,
    "max_lifetime_minutes": 43200
  }
}

Fields

FieldDescription
is_activeInvoices v2 globally enabled on the platform.
invoice_commission_percentBase commission for accepting a payment, %.
hedging_commission_percentExtra commission when is_hedging=true.
shift_commission_to_payertrue — fee is added on top (payer pays it). false — fee is deducted from the receiver amount.
is_hedging_enabledHedging is available (if false — is_hedging in POST is ignored).
min_invoice_amount_usdtMinimum invoice amount in USDT equivalent. For non-USDT — recalculated at the rate.
max_invoice_amount_usdtMaximum invoice amount in USDT equivalent.
default_lifetime_minutesDefault TTL in minutes — if life_time_minutes is omitted.
min_lifetime_minutesMinimum allowed TTL.
max_lifetime_minutesMaximum allowed TTL (43200 = 30 days).

Create invoice

POST/api/v2/integration/invoices

Permission: can_invoices_create. Returns payment_url and (if symbol/bch_code are set) the on-chain payment details.

Send the Idempotency-Key header (it overrides body.idempotency_key): 8–36 chars A–Z a–z 0–9 - _, UUID recommended. One key = one invoice. A repeat with the same key within 24 h returns the already created invoice — safe retry after a timeout or dropped connection. A new key creates a new invoice. If the first request has not been persisted yet, a retry may return HTTP 409 — wait 2–3 seconds and retry with the same key.

Request body

FieldTypeDescription
symbolstring?Coin (USDT, BTC, ETH...). NULL — the payer picks on the page.
bch_codestring?Network (TRX, ETH, TON...). NULL — the payer picks.
amountstring?Amount as a decimal string. NULL = open amount.
currencystring?Currency of the amount. Defaults to symbol. Supports USDT/USD as unit of account.
descriptionstring? (≤500)Visible to the payer on the page and in QR caption.
life_time_minutesint (1..43200)TTL in minutes. Default 60. Max 30 days.
is_hedgingboolAfter credit, auto-convert to hedging_symbol.
hedging_symbolstring?Target hedging coin (e.g. BTC).
payer_emailstring?Payer email (optional). Prefills AlfaBit Checkout; not returned in Integration GET responses.
payment_policyenumall (default) / alfabit_only / external_only.
show_receiver_publiclybool (default true)Whether to show the receiver name/email on the public page.
idempotency_keystring? (8..36)Idempotency key in the body. If Idempotency-Key header is also sent, the header wins.
curl — Idempotency-Key
curl -X POST "https://alfabit.org/api/v2/integration/invoices" \
  -H "X-API-Key: pk_live_..." \
  -H "X-API-Signature: ..." \
  -H "X-API-Timestamp: ..." \
  -H "Idempotency-Key: 4b8c8a1e-3f2c-4f3a-9c0d-2b3a4b5c6d7e" \
  -H "Content-Type: application/json" \
  -d '{"amount":"100","currency":"RUB","payment_policy":"alfabit_only"}'
Request — minimal
{
  "symbol": "USDT",
  "bch_code": "TRX",
  "amount": "100"
}
Request — full
{
  "symbol": "USDT",
  "bch_code": "TRX",
  "amount": "250.50",
  "currency": "USDT",
  "description": "Order #ORD-12345",
  "life_time_minutes": 180,
  "is_hedging": true,
  "hedging_symbol": "BTC",
  "payment_policy": "all",
  "show_receiver_publicly": false,
  "idempotency_key": "4b8c8a1e-3f2c-4f3a-9c0d-2b3a4b5c6d7e"
}
Response
{
  "success": true,
  "data": {
    "invoice_id": "01J7HZ...",
    "status": "wait",
    "symbol": "USDT",
    "network": "TRX",
    "address": "TXa1B2...",
    "memo_tag": null,
    "amount_requested": "250.50",
    "amount_requested_currency": "USDT",
    "amount_to_pay": "250.50",
    "exchange_rate": null,
    "commission": {
      "commission_percent": "0.5",
      "commission_amount": "1.25",
      "is_paid_by_payer": false
    },
    "amount_received": null,
    "txid": null,
    "payment_source": "blockchain",
    "description": "Order #ORD-12345",
    "is_hedging": true,
    "hedging_symbol": "BTC",
    "is_payer_marked_paid": false,
    "payment_url": "https://alfabit.org/en/pub/invoice/order/01J7HZ...",
    "alfabit_payment_policy": "all",
    "is_open_amount": false,
    "show_receiver_publicly": false,
    "created_at": "1747325000",
    "expires_at": "1747335800"
  }
}

Fields symbol/network/address/memo_tag will be NULL when you create an invoice with deferred currency — they get populated after the payer chooses the coin/network on the public page.

Get invoice details

GET/api/v2/integration/invoices/{invoice_id}

Permission: can_invoices_read. Same DTO as POST /invoices. Adds alfabit_payment_route and alfabit_payer_symbol/network when paid via AlfaBit Checkout. For QR+KYC, payer_name is the verified payer display name; omitted when there is no link. For auto-conversion invoices (is_hedging=true) this response also carries the outcome: hedged_amount and hedged_symbol — the amount and coin actually credited. Conversion runs after payment, so right after invoice.paid they are still null; prefer the invoice.hedged event over polling.

Response — paid via AlfaBit balance
{
  "success": true,
  "data": {
    "invoice_id": "01J7HZ...",
    "status": "success",
    "symbol": "USDT",
    "network": "TRX",
    "address": "TXa1B2...",
    "amount_requested": "250.50",
    "amount_received": "250.50",
    "txid": null,
    "payment_source": "alfabit_balance",
    "alfabit_payment_route": "direct_internal_transfer",
    "alfabit_payer_symbol": "USDT",
    "alfabit_payer_network": "TRX",
    "alfabit_payment_intent_uid": "intent_01J...",
    "is_payer_marked_paid": true,
    "payer_name": "И. Иван Иванович",
    "payment_url": "https://alfabit.org/en/pub/invoice/order/01J7HZ...",
    "show_receiver_publicly": false,
    "created_at": "1747325000",
    "expires_at": "1747335800"
  }
}
⚠️
404 — when the invoice is not found or owned by another user.

List invoices

GET/api/v2/integration/invoices

Permission: can_invoices_read. Pagination by page/limit. Each item is the same object as in GET /invoices/{invoice_id}.

Query parameters

ParamType / rangeDescription
statusstring?Filter: wait / success / failed.
limitint (1..100, default 50)Page size.
pageint (≥1, default 1)Page number.
GET /api/v2/integration/invoices?status=wait&limit=20&page=1
{
  "success": true,
  "data": [ /* массив объектов как в GET /invoices/{invoice_id} */ ],
  "pagination": {
    "total": 137,
    "limit": 20,
    "offset": 0
  }
}

Cancel invoice

POST/api/v2/integration/invoices/{invoice_id}/cancel

Permission: can_invoices_create. Cancellation works only in status wait. Moves the invoice to failed. If already paid (success / aml / blocked) — 409 INVALID_STATUS.

Response 200
{
  "success": true,
  "data": { /* тот же объект инвойса со status="failed" */ }
}
Response 409
{
  "success": false,
  "error": {
    "code": "INVALID_STATUS",
    "message": "Cannot cancel invoice in status 'success'"
  }
}

Payment URL

GET/api/v2/integration/invoices/{invoice_id}/payment-url

Permission: can_invoices_read. The link is public — share it with the payer. The same URL is already present as `payment_url` in any GET /invoices/{invoice_id} response; this endpoint is for cases when you don't want to fetch the whole object.

Response
{
  "success": true,
  "data": {
    "payment_url": "https://alfabit.org/en/pub/invoice/order/01J7HZ..."
  }
}

Permanent QR / template

A permanent invoice is a template. One template_uid lives forever; every actual payment creates a separate child invoice with its own address and TTL. Ideal for cashiers, permanent wall QR codes, donation links and POS terminals.

💡
Template ≠ payment

The template itself is not a payment — invoice.created webhook does NOT fire for it. invoice.paid / invoice.expired fire for each child and carry parent_template_uid + external_payment_id (when set).

Active children per template are capped — see template_max_active_children in GET template response (default 100).

Embed an amount into the public link/QR: append ?amount=500&currency=RUB&fixed=1 to payment_url — the payment page shows the amount as fixed (price tag), the payer cannot change it. Without parameters the amount stays open.

Create template

POST/api/v2/integration/invoices/v2/permanent

Permission: can_invoices_create. Creates a template without a one-time payment order and without TTL (lives until is_active=true).

Request body

FieldTypeDescription
symbolstring?Receiver target coin. NULL = payer picks on the receipt.
bch_codestring?Network. If set — inherited by every child.
currencystring?Default currency of amount for child receipts.
descriptionstring? (≤500)Public template description (visible to all payers).
life_time_minutesint (1..43200, default 60)Default TTL for child receipts.
is_hedgingboolHedging is inherited by children.
hedging_symbolstring?Hedging target coin.
payment_policyenumall / alfabit_only / external_only.
show_receiver_publiclyboolShow receiver name/email on the page.
show_payments_count_publiclyboolShow SUCCESS-payments counter on the public page.
Response
{
  "success": true,
  "data": {
    "template_uid": "01J8AB...",
    "status": "wait",
    "is_active": true,
    "payment_url": "https://alfabit.org/en/pub/invoice/permanent/01J8AB...",
    "invoice_currency": "USDT",
    "description": "Coffee Shop on Tverskaya 7",
    "payments_total": 0,
    "is_hedging": false,
    "hedging_symbol": null,
    "payment_policy": "all",
    "show_receiver_publicly": true,
    "show_payments_count_publicly": true,
    "template_max_active_children": 100,
    "created_at": "1747325000"
  }
}
GET/api/v2/integration/invoices/v2/permanent/{template_uid}

Current template state + payments_total (counter of SUCCESS children). Permission: can_invoices_read.

Issue a receipt against template (POS API)

POST/api/v2/integration/invoices/v2/permanent/{template_uid}/payments

Permission: can_invoices_create. Creates a normal child invoice with its own address, TTL and link. Idempotent: a repeat POST with the same idempotency_key returns the SAME child.

Request body

FieldTypeDescription
amountstring (required)Receipt amount (decimal string > 0).
currencystring?Currency of the amount. NULL — inherited from template.
life_time_minutesint? (1..43200)Receipt TTL. NULL = 60.
descriptionstring? (≤500)Visible to the payer on the receipt page.
external_payment_idstring? (≤128)Your POS-side receipt ID. Echoed back in invoice.paid / invoice.expired webhook.
idempotency_keystring (8..64, required)Receipt deduplication. Repeat POST with the same key does not create a second child.
Response (child invoice)
{
  "success": true,
  "data": {
    "invoice_id": "01J8CD...",
    "status": "wait",
    "symbol": "USDT",
    "network": "TRX",
    "address": "TXa1B2...",
    "amount_requested": "12.50",
    "amount_requested_currency": "USDT",
    "amount_to_pay": "12.50",
    "payment_source": "blockchain",
    "description": "Receipt POS-AAA-9876",
    "payment_url": "https://alfabit.org/en/pub/invoice/order/01J8CD...",
    "alfabit_payment_policy": "all",
    "show_receiver_publicly": true,
    "created_at": "1747326000",
    "expires_at": "1747326900",
    "parent_template_uid": "01J8AB...",
    "external_payment_id": "POS-AAA-9876"
  }
}

Template receipts list

GET/api/v2/integration/invoices/v2/permanent/{template_uid}/payments

Permission: can_invoices_read. All child receipts of the template with pagination. Supports filters by status and external_payment_id (exact match).

Query parameters

ParamType / rangeDescription
statusstring?wait / success / failed / expired.
external_payment_idstring?Exact match with previously sent receipt ID.
limitint (1..200, default 50)Page size.
pageint (≥1, default 1)Page number.
Response (item)
{
  "uid": "01J8CD...",
  "status": "success",
  "invoice_amount": "12.50",
  "invoice_currency": "USDT",
  "amount_received": "12.50",
  "symbol": "USDT",
  "bch_code": "TRX",
  "txid": "0x1a2b3c...",
  "public_comment": "Receipt POS-AAA-9876",
  "external_payment_id": "POS-AAA-9876",
  "created_at": "1747326000",
  "finished_at": "1747326200"
}

Statuses & payment_source

📌
How to read status

The status field in GET /invoices/{invoice_id} and in webhook data.status is the same. See the table below. Do not confuse status (invoice state) with event (webhook event name).

status

ValueMeaningWebhook event
waitInvoice created, awaiting payment. TTL not yet expired.invoice.created
successPayment received and credited. amount_received is the actual inbound amount; it may be greater or less than amount_requested when the sum is ≥ the network minimum.invoice.paid
expiredTTL elapsed and txid is empty: no payment arrived. An on-chain transfer that already arrived will not expire the invoice.invoice.expired
failedMerchant cancel via POST /cancel — or an on-chain payment below the network minimum / dust. A technical create failure has no invoice and no status.invoice.cancelled (cancel only; below minimum — no webhook, poll GET)
refundedThe provider refunded the payer (e.g. name mismatch on QR+KYC). The merchant was not credited.invoice.refunded
amlPayment received but AML-frozen. Resolved by support.
blockedSecurity policy block.

There are no invoice.aml / invoice.blocked webhooks: poll GET /api/v2/integration/invoices/{invoice_id} for an AML hold. After invoice.paid with status=success a move to aml / blocked is not possible — AML runs before credit. A second transfer to the same address after txid is recorded does not close the same invoice_id again.

payment_source

ValueMeaning
blockchainOn-chain payment. txid and network fee present. Default for status=wait.
alfabit_balancePayment from AlfaBit balance. txid=null. alfabit_payment_* fields populated.

Invoice webhooks

⚠️
Webhook URL is NOT passed in POST /invoices

There is no callback_url / webhook_url field on invoice create and there will not be one. The URL is set once per account via the Integration Webhooks API (or Developer Console → Webhooks). After you subscribe, events for all your invoices go to that URL.

1. How to subscribe

POST /api/v1/integration/webhooks
{
  "url": "https://your.domain/hooks/alfabit",
  "events": [
    "invoice.created",
    "invoice.paid",
    "invoice.expired",
    "invoice.cancelled",
    "invoice.refunded",
    "invoice.hedged"
  ]
}

The response returns secret once — store it to verify HMAC (X-Webhook-Signature header). Manage via GET/PATCH/DELETE /api/v1/integration/webhooks/{id}; delivery logs: GET .../webhooks/{id}/logs. Event catalog: GET /api/v1/integration/webhooks/events.

2. Delivery envelope

HTTP POST на ваш url
Headers:
  Content-Type: application/json
  X-Webhook-Event: invoice.paid
  X-Webhook-Signature: <hmac-sha256 hex>

Body:
{
  "event": "invoice.paid",
  "timestamp": "2026-07-21T11:17:55Z",
  "signature": "<same hmac>",
  "data": { /* см. ниже */ }
}

The signature covers the envelope {event, timestamp, data} without the signature field, re-serialized as canonical JSON — see Webhooks → “Signature verification (HMAC)” for the exact algorithm and Python / Node.js examples.

3. Events and data.status

eventdata.statusWhen
invoice.createdwaitRight after successful POST /api/v2/integration/invoices (and UI). NOT sent for permanent templates.
invoice.paidsuccessPayment credited (blockchain or AlfaBit balance), including permanent children. Reconcile amount_received: it may differ from the nominal (overpay or underpay ≥ the network minimum).
invoice.expiredexpiredTTL elapsed and txid is empty. An underpay that already arrived does not go here.
invoice.cancelledfailedMerchant called POST /api/v2/integration/invoices/{invoice_id}/cancel.
invoice.refundedrefundedThe provider refunded the payer. The merchant was not credited.
invoice.hedgedsuccessAuto-conversion (is_hedging) finished: data carries hedged_amount and hedged_symbol — the amount actually credited in the target coin.
invoice.template.deactivatedPermanent invoice template deactivated.

There is no invoice.failed event. Track merchant cancel via invoice.cancelled, TTL with no payment via invoice.expired, provider refund via invoice.refunded. A payment below the network minimum sets status=failed with no webhook — poll GET. An AML hold is GET-only as well (status=aml / blocked).

4. data fields

invoice.paid example
{
  "invoice_id": "9b854097-3de2-42aa-9f9c-1c0412058c73",
  "status": "success",
  "symbol": "USDT",
  "network": "BSC",
  "address": "0x9760...",
  "memo_tag": null,
  "amount_requested": "10",
  "amount_requested_currency": "USDT",
  "amount_received": "10",
  "txid": "0x1a2b...",
  "description": null,
  "is_hedging": false,
  "hedging_symbol": null,
  "is_payer_marked_paid": false,
  "payer_name": "И. Иван Иванович",
  "payment_source": "blockchain",
  "payment_url": "https://alfabit.org/en/pub/invoice/order/9b854097-...",
  "created_at": "1784631832.0136988",
  "expires_at": "1784632732.0136988",
  "fee_breakdown": {
    "invoice_fee_mode": "deduct_from_amount",
    "fiat_topup_fee_mode": "payer",
    "currency": "USDT"
  },
  "alfabit_payment_intent_uid": null,
  "alfabit_payment_route": null,
  "alfabit_payer_symbol": null,
  "alfabit_payer_network": null,
  "parent_template_uid": null,
  "external_payment_id": null
}

Fields match GET /api/v2/integration/invoices/{invoice_id} (data). For AlfaBit-balance payments: txid=null, payment_source=alfabit_balance, alfabit_* set. For permanent children: parent_template_uid; external_payment_id if you passed it via POS-API. For QR+KYC: payer_name is the verified payer display name; omitted when there is no link.

invoice.hedged example
{
  "invoice_id": "9b854097-3de2-42aa-9f9c-1c0412058c73",
  "hedged_amount": "0.01324718",
  "hedged_symbol": "BTC"
}

Sent only for invoices with is_hedging=true and only after the auto-conversion succeeds — i.e. AFTER invoice.paid, as a separate delivery. hedged_amount is the amount actually credited in hedged_symbol (rate and hedging fee already applied). The same two fields are available in GET /api/v2/integration/invoices/{invoice_id}: they stay null until the conversion completes.

KYC-API Checkout (KYC+QR)

A second, equally supported way to accept RUB payments — fully via API, without our hosted payment page. All UI (form, document upload, QR display) is on your side; the payer never leaves your site. Suited for exchangers and crypto projects that need full UX control.

⚖️
Two flows — both supported

1) Hosted invoice: POST /integration/invoices → payment_url → the payer pays on our page (KYC handled by us). 2) KYC-API checkout (this section): you collect payer documents yourself, submit them to us, receive a QR and render it in your own UI. Which flow is enabled for your account is defined by the acceptance profile (assigned by AlfaBit).

Flow

Scheme
1. POST /api/v1/integration/checkout/payments
   { amount, payer_phone, payer_ip, external_payment_id, ... }
   → status=kyc_required            (инвойс НЕ создаётся)
2. POST /api/v1/integration/checkout/payers/{phone}/documents
   (multipart: doc_type + file — паспорт главная стр., прописка)
   → kyc_status=pending, processing=true   (это НЕ финальный статус)
3. GET  /api/v1/integration/checkout/payers/{phone}
   опрашивать каждые 1–2 сек, пока processing=true
   → kyc_status=approved + expected_payer_name
   → processing=false и pending / manual_review=true → ручная сверка
     (те же фото заново не отправлять)
   → kyc_status=retry, client_action_required=true → нужны новые документы:
     покажите плательщику retry_comment и вернитесь к шагу 2
4. POST /api/v1/integration/checkout/payments  (обязательно повторно)
   → payment_uid, status=created    (без этого шага QR не будет)
5. GET  /api/v1/integration/checkout/payments/{uid}
   → qr_url / qr_payload            (СБП-реквизиты, когда готовы)
6. Webhook invoice.paid             (оплата прошла, ФИО плательщика совпало)
   или invoice.refunded             (провайдер вернул платёж, например ФИО не совпало)

Телефон в PATH — только цифры, без «+»:
  /payers/79001234567
В JSON-теле payer_phone можно передавать +79001234567 — мы нормализуем.
Вебхука на смену KYC плательщика нет: статус только GET (или повторный POST /payments).
POST/api/v1/integration/checkout/payments

Permission: can_invoices_create. Creates a RUB payment. If your acceptance profile requires payer verification and the phone is not approved yet, returns kyc_required: no invoice and no QR are created at this step. After documents are uploaded and GET returns approved, call this POST again — otherwise there will be no QR. payer_phone is the verification key: once verified, the payer pays again without re-uploading documents. Required anti-fraud fields: payer_ip (end payer IP, IPv4/IPv6) and external_payment_id (your own order id, for reconciliation — does not protect against duplicates). Optional: payer_user_agent, payer_device_id (device fingerprint).

amount is always the sum you will receive on your balance. Who pays our acceptance fee is controlled by the optional invoice_fee_mode field: deduct_from_amount (default, fee is deducted from amount on credit — as before) or payer (the amount encoded in the SBP QR is increased by the fee, you receive the full amount). The exact rate and figures are in the response below (commission_percent, commission_amount, amount_to_pay) and can be checked in advance via GET /channels.

Send idempotency_key (8–64 chars) for safe retries after a dropped connection: a repeat with the same key within 24 h will not create a second payment — the already created one is returned (same payment_uid/QR); if the original request is still running you get HTTP 409 IDEMPOTENCY_IN_FLIGHT, retry later with the same key. external_payment_id alone does not protect against duplicates — it is only your reconciliation ID. The key only gates the actual payment creation: it has no effect on the kyc_required response, and reusing the same key on the step-4 retry (after payer approval) is intentional — the payment is still created exactly once.

Request
{
  "amount": "5000.00",
  "external_payment_id": "MM-1234",
  "idempotency_key": "order-mm-1234-attempt-1",
  "description": "Order #1234",
  "payer_phone": "+79001234567",
  "payer_ip": "203.0.113.10",
  "payer_user_agent": "Mozilla/5.0 ...",
  "payer_device_id": "fp_7c9e6679",
  "invoice_fee_mode": "deduct_from_amount"
}
Response (KYC required)
{
  "success": true,
  "data": {
    "status": "kyc_required",
    "kyc_required": true,
    "payer_status": "not_started",
    "processing": false,
    "manual_review": false,
    "client_action_required": false,
    "retry_comment": null,
    "required_documents": ["passport_main", "passport_registration"],
    "commission_percent": "1.0",
    "upload_documents_endpoint": "/api/v1/integration/checkout/payers/79001234567/documents",
    "poll_status_endpoint": "/api/v1/integration/checkout/payers/79001234567"
  }
}
Response (payment created)
{
  "success": true,
  "data": {
    "payment_uid": "01J8...",
    "external_payment_id": "MM-1234",
    "status": "created",
    "amount": "5000.00",
    "amount_to_pay": "5000.00",
    "currency": "RUB",
    "invoice_fee_mode": "deduct_from_amount",
    "commission_percent": "1.0",
    "commission_amount": "50.00"
  }
}

amount_to_pay is the sum actually encoded in the SBP QR (what the payer must send). With invoice_fee_mode=payer it is greater than amount by exactly commission_amount; with deduct_from_amount (as in the example above) it equals amount, and commission_amount will be deducted from the credited balance.

POST/api/v1/integration/checkout/payers/{phone}/documents

Permission: can_invoices_create. Multipart upload of a payer document: fields doc_type (passport_main | passport_registration | selfie) and file (JPEG/PNG/PDF, up to 25 MB). Recommended anti-fraud fields (form-data): payer_ip, payer_user_agent, payer_device_id.

The response is returned as soon as the files are accepted, with processing: true — this is not the final status. Recognition runs in the background for a few seconds. The final status comes only from GET /payers/{phone} (see poll_status_endpoint): keep polling while processing is true. When kyc_status=approved, call POST /payments again. processing: false with pending / manual_review=true means an operator is reviewing the payer — do not re-upload the same files. With kyc_status=retry, on the contrary, a new upload is expected: the operator left a note in retry_comment and waits for a new set — re-sending the same photos is pointless. Do not put “+” in the phone path (it is decoded as a space): /payers/79001234567, not /payers/+79001234567.

Response
{
  "success": true,
  "data": {
    "phone": "+79001234567",
    "kyc_status": "pending",
    "expected_payer_name": null,
    "low_confidence": false,
    "processing": true,
    "poll_status_endpoint": "/api/v1/integration/checkout/payers/79001234567"
  }
}

The full name is recognized from the main passport page — once approved, show it to the payer with a warning that payment is expected exactly from {name}: the bank matches the actual sender against the passport. The name is returned by GET /payers/{phone} in expected_payer_name, masked as “И. Иван Иванович” (last-name initial + first name + patronymic), the way SBP does it. The payer recognizes themselves while the full surname is never exposed.

Retrying the request is safe: the same file uploaded again under the same doc_type does not create a duplicate document.

GET/api/v1/integration/checkout/payers/{phone}

Permission: can_invoices_read. Payer verification status by phone: not_started | pending | retry | approved | rejected. pending means the check is on our side, retry means we are waiting for new documents from the payer. Phone in the URL is 11 digits with no “+” (/payers/79001234567). Use it after uploading documents (poll while processing is true) and before retrying POST /payments. There is no webhook for KYC status changes.

processing: true — OCR is still running, the status is not final, poll again in 1–2 seconds. processing: false and kyc_status=approved — retry POST /payments to get the QR. processing: false with pending / manual_review=true — the automatic check has finished and the payer is with an operator; this is not a stuck OCR. kyc_status=retry with client_action_required=true — the documents need to be re-submitted: the status will not change on its own, show the payer the retry_comment text and upload a new set (POST /documents).

Response
{
  "success": true,
  "data": {
    "phone": "+79001234567",
    "kyc_status": "approved",
    "verified_at": "2026-07-27T10:39:26.657298+00:00",
    "expected_payer_name": "И. Иван Иванович",
    "required_documents": ["passport_main", "passport_registration"],
    "processing": false,
    "manual_review": false,
    "client_action_required": false,
    "retry_comment": null
  }
}
GET/api/v1/integration/checkout/payments/{uid}

Permission: can_invoices_read. Payment status + SBP requisites: qr_url (NSPK link) and qr_payload appear as soon as the payment system generates them (usually seconds). Terminal statuses are also delivered via invoice.paid / invoice.expired / invoice.refunded webhooks. For QR+KYC both names — payer_name and expected_payer_name — are masked as “И. Иван Иванович”.

Response
{
  "success": true,
  "data": {
    "payment_uid": "01J8...",
    "status": "success",
    "amount": "5000.00",
    "currency": "RUB",
    "qr_url": "https://qr.nspk.ru/...",
    "qr_payload": "https://qr.nspk.ru/...",
    "expected_payer_name": "И. Иван Иванович",
    "payer_name": "И. Иван Иванович"
  }
}
GET/api/v1/integration/checkout/channels

Permission: can_invoices_read. The set of SBP acceptance channels assigned to your account (code, name, fee). A specific channel can be selected per payment via the channel field — from your own set.

Response
{
  "success": true,
  "data": {
    "channels": [
      { "code": "sbp_1", "name": "SBP #1", "fee_percent": 1.0 }
    ],
    "commission_percent": "1.0"
  }
}

commission_percent (top-level) is the effective acceptance fee rate for your account. This is the authoritative value for planning amount/invoice_fee_mode in POST /payments in advance, before creating a payment; fee_percent inside channels is an informational per-channel field.

Embeddable Widget

Embed AlfaBit Checkout payment widget right on your site — modal, inline block, or iframe. User does not leave the page or get redirected. Analogous to Stripe Elements / PayPal Buttons.

How to embed
  1. Create invoice via POST /api/v2/integration/invoices — get invoice_id (uid)
  2. Load SDK from CDN or via npm @alfabit/checkout-js
  3. Open the widget: AlfaBitCheckout.open(uid) or mount(...)
  4. Listen for onSuccess / onError or subscribe via postMessage

1. Pure iframe (no JS)

The simplest option — for static landings and Telegram mini-apps. Just an <iframe>:

<iframe
  src="https://alfabit.org/{lang}/embed/invoice/order/{uid}?theme=dark&primary=9ee248"
  width="100%"
  height="640"
  frameborder="0"
  allow="payment *; camera; microphone">
</iframe>

2. JS SDK (modal popup)

Pay button next to a product — click opens modal:

<script src="https://alfabit.org/checkout/v1/checkout.umd.js"></script>
<button onclick="AlfaBitCheckout.open('{uid}')">Pay</button>

3. Inline mount

Widget embeds into the site layout, without modal:

<div id="alfabit-checkout"></div>
<script>
  AlfaBitCheckout.mount('#alfabit-checkout', {
    invoice: '{uid}',
    theme: 'auto',
    primary: '#9ee248',
    locale: 'ru',
    onSuccess: (e) => { window.location = '/thank-you' },
  });
</script>

4. React / Vue

// React
import AlfaBitCheckout from '@alfabit/checkout-js';

<button onClick={() => AlfaBitCheckout.open('{uid}', { theme: 'dark' })}>
  Pay
</button>

Events (postMessage)

EventPayloadWhen
alfabit:ready{ invoice_uid, currency, amount }iframe loaded
alfabit:resize{ height }for inline mode
alfabit:payment.completed{ invoice_uid, amount, currency }paid
alfabit:payment.failed{ invoice_uid, error, message }rejected
alfabit:payment.cancelled{ invoice_uid }user cancelled
alfabit:payment.expired{ invoice_uid }expired

Widget options

OptionType / DefaultDescription
invoicestring (required)invoice uid from POST /api/v2/integration/invoices
type'order' | 'permanent' / 'order'Single-use or permanent QR
theme'auto' | 'light' | 'dark' / 'auto'Widget theme
primaryhex / '#9ee248'Accent color (6 hex chars)
locale'ru' | 'en' / 'ru'Language
heightnumber / 640Inline-mount height
onSuccessfunctionCallback on successful payment
💡
Ready snippet in one click

After creating an invoice in the wallet, the success modal has an "Embed on website" button. It opens a modal with ready-to-paste snippets for all 5 platforms (HTML, JS SDK, React, Vue, Telegram), live preview of the widget, and theme/color controls. Just copy the code.

🔒
Security

Real /pub/invoice/* pages are protected by X-Frame-Options: SAMEORIGIN. The widget loads from a separate /embed/invoice/* prefix with CSP frame-ancestors *. SDK filters postMessage by origin (only alfabit.org or your baseUrl). primary is validated by regex (6 hex chars), theme/locale — strict enum. User SSO for "Pay from AlfaBit balance" happens in a popup window, not inside the iframe.

Load the SDK from CDN: https://alfabit.org/checkout/v1/checkout.umd.js or via npm: @alfabit/checkout-js

Travel

Book flights and hotels with crypto, fiat or Wallet balance. The API supports the full cycle from search to order management.

💳
Payment methods

Crypto (USDT, BTC, ETH), fiat (RUB) via invoice, or instant payment from Wallet balance.

🚧
Sandbox environment

This section is under development. Please use Production environment for integration.

EnvironmentBase URLAPI key
Productionhttps://alfabit.orgCreate API key in Developer Console
🔐
API key permissions

Reads (search, orders) — can_travel_read. Booking and payments — can_travel_book. Money refunds — can_travel_refund (for refund endpoints and cancellation of paid orders). Existing keys with can_travel_book automatically get refund access too — backward compatible.

🔁
Idempotency (Idempotency-Key)

All mutating endpoints support the Idempotency-Key header. Send a unique UUID per operation — repeated requests with the same key within 24 hours return the previously stored response without double charging/booking.

Supported on:

  • Hotels: book, pay/crypto, pay/wallet, cancel, create-order, create-order-with-view, create-order-and-select-room, select-room
  • Avia: book, pay/crypto, pay/wallet, order/{uuid}/cancel, order/{uuid}/refund
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000

Key format: A-Z, a-z, 0-9, hyphen, underscore (1-128 chars). Spaces, colons, and special chars → 400 INVALID_IDEMPOTENCY_KEY.

📡
Travel webhook events

Instead of polling /order/status, subscribe to events via POST /api/v1/integration/webhooks. Available hotel events:

  • travel.hotel_order.created — order draft created
  • travel.hotel_order.booked — booked at provider, awaiting payment
  • travel.hotel_order.paid — order paid
  • travel.hotel_order.confirmed — hotel confirmed reservation
  • travel.hotel_order.cancelled — order cancelled
  • travel.hotel_order.refunded — refund completed
  • travel.hotel_order.failed — booking/payment error

For avia same events: travel.avia_order.created/booked/paid/cancelled/refunded/failed.

Booking Flow (same for flights and hotels)
SearchSelectOrderBookPay

Each step returns data needed for the next. For example, search returns recommendation_id used in create-order.

General

GET/api/v1/integration/travel/rates

Current USDT/RUB and USDT/KGS rates for price calculation.

Response
{ "success": true, "data": { "usdt_rub": 92.5, "usdt_kgs": 89.1 }, "ts": 1706000000 }

✈️ Flights

POST/api/v1/integration/travel/avia/search

Search flights with recommendations. Specify route segments, passenger counts by type and cabin class. Returns recommendation_id used in /create-order.

Request body
{
  "adt": 1,                               // adults: 1-9
  "chd": 0,                               // children: 0-9
  "inf": 0,                               // infants: 0-9
  "trip_class": "e",                      // e / b / f
  "segments": [                           // массив сегментов (1 = OW, 2 = RT, 3+ = multi-city)
    { "from": "SVO", "to": "LED", "date": "2026-06-15" },
    { "from": "LED", "to": "SVO", "date": "2026-06-20" }
  ],
  "lang": "ru"
}
Response 200 (важные поля)
{
  "success": true,
  "data": {
    "flights": [
      {
        "id": "21DKEASYOWE100...",              // длинный ID рейса
        "recommendation_id": "21DKEASYOWE100...", // алиас на id (для удобства)
        "duration": 185,                         // общая длительность в минутах
        "segments_direction": [
          {
            "direction": 0,
            "segments": [
              {
                "dep": { "city": "Frankfurt", "airport_code": "FRA", "datetime": "15.06.2026 17:35:00", "ts": 1781534100 },
                "arr": { "city": "Istanbul", "airport_code": "SAW", "datetime": "15.06.2026 21:40:00", "ts": 1781548800 },
                "flight_info": { "flight_number": "996", "airline": "Pegasus Airlines", "airline_code": "PC" },
                "baggage": { "is_baggage": false, "baggage_pieces": 0 }
              }
            ]
          }
        ],
        "short_result": {
          "duration": 185,
          "isBaggage": true, "baggageWeight": 20, "baggagePiece": 1,
          "transfers": []
        },
        "price": { /* цены в разных валютах */ },
        "hash": "ce7069..."
      }
    ]
  },
  "ts": 1714063200
}

// Используйте flights[N].recommendation_id (или flights[N].id) для:
//   GET  /avia/fare-families?recommendation_id=...
//   POST /avia/create-order  { recommendation_id, ... }
//   POST /avia/book          { recommendation_id, ... }
GET/api/v1/integration/travel/avia/fare-families?recommendation_id=...&lang=ru

Fare families for selected flight: baggage, refund, exchange.

POST/api/v1/integration/travel/avia/create-order

Create flight order from selected recommendation.

POST/api/v1/integration/travel/avia/select-tariff

Select tariff (economy, business, etc.) for order.

POST/api/v1/integration/travel/avia/book

Book flight with passenger data. Send Idempotency-Key — repeat will not create double booking.

Request body
{
  "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "recommendation_id": "REC_12345",        // ОБЯЗАТЕЛЬНО. Если выбирали тариф — nonUpgradedId /
                                           // upgradeId из /avia/fare-families, иначе тот же id,
                                           // что в /avia/create-order
  "client_email": "user@example.com",
  "client_phone": "+79001234567",
  "passengers": [
    {
      "first_name": "IVAN",                // КАК В ПАСПОРТЕ — латиницей для международных рейсов
      "last_name": "IVANOV",
      "type": "adt",                       // adt / chd / inf
      "birth_date": "1990-01-15",          // YYYY-MM-DD
      "document_type": "passport",         // passport / national_id / birth_certificate (для inf)
      "document_number": "1234567890",
      "document_expire": "2030-01-15",
      "citizenship": "RU",
      "sex": "M"                           // M / F
    }
  ],
  "lang": "ru"
}
Header (рекомендуется)
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Response 200
{
  "success": true,
  "data": {
    "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "billing_number": "BN-AVIA-2026-78901",
    "status": "booked",
    "ticket_time_limit": "2026-04-25T22:00:00Z"   // до этого времени надо оплатить
  },
  "ts": 1714063200
}
POST/api/v1/integration/travel/avia/pay/crypto

Pay with crypto/fiat via invoice. Returns address/QR. Supports Idempotency-Key.

Request body
{
  "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "currency_in_code": "USDT_TRC20"   // USDT_TRC20 / BTC / ETH / RUB / ...
}
Response 200
{
  "success": true,
  "data": {
    "invoice_id": "INV-AVIA-12345",
    "deposit_address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
    "qr_code_url": "https://...",
    "amount": "95.50",
    "currency": "USDT_TRC20",
    "expires_at": "2026-04-25T22:00:00Z"
  },
  "ts": 1714063200
}
POST/api/v1/integration/travel/avia/pay/wallet

Instant payment from Wallet balance. Funds debited immediately. CRITICAL to send Idempotency-Key.

Request body
{
  "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "currency": "USDT"
}
Header (КРИТИЧНО)
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Response 200
{
  "success": true,
  "data": {
    "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "status": "paid",
    "paid_at": "2026-04-25T20:05:00Z",
    "amount_charged": "95.50",
    "currency": "USDT",
    "balance_after": "1054.50",
    "ticket_status": "issued"           // issued / pending / failed
  },
  "ts": 1714063200
}
GET/api/v1/integration/travel/avia/wallet/balance?currency=USDT

Wallet balance for flight payment.

GET/api/v1/integration/travel/avia/orders

All your flight orders.

GET/api/v1/integration/travel/avia/order/{order_uuid}

Order details: route, passengers, status, cost.

GET/api/v1/integration/travel/avia/order/{order_uuid}/status

Order status: created, booked, pay_waiting, ticketed, cancelled, refunded.

GET/api/v1/integration/travel/avia/order/{order_uuid}/pdf

Download PDF itinerary receipt / e-ticket.

POST/api/v1/integration/travel/avia/order/{order_uuid}/cancel

Cancel flight booking.

POST/api/v1/integration/travel/avia/order/{order_uuid}/refund

Flight refund (voluntary or involuntary).

GET/api/v1/integration/travel/avia/passengers

Saved passengers for quick booking.

🏨 Hotels

Dictionaries

GET/api/v1/integration/travel/hotels/destination?part=Mosc&lang=ru

City/region autocomplete for hotel search.

GET/api/v1/integration/travel/hotels/cities/search?q=Mosc&limit=15

Search canonical cities via our dictionary (fast, no provider call). Separate dictionary: its hotel_city_id is NOT valid for the city field in hotel search — use the id from /hotels/destination.

GET/api/v1/integration/travel/hotels/avia-bridge?hotel_id=12345

Bridge "hotel → city IATA → nearby airports" — for cross-selling flights after hotel selection.

Search

POST/api/v1/integration/travel/hotels/search

Synchronous hotel search by city, dates and guest count. May take 30-60 sec. For better UX use /search/async.

Request body
{
  "city": "7000546",                  // числовой id из /hotels/destination.
                                      // НЕ название города и НЕ hotel_city_id из /hotels/cities/search
  "check_in": "15.05.2026",           // строго dd.mm.yyyy — с точками, иначе 400
  "check_out": "20.05.2026",
  "adults": 2,                        // 1-6
  "children": [{ "child_age": 5 }],   // optional
  "lang": "ru"
}
Response 200
{
  "success": true,
  "data": {
    "search": { "city": "7000546", "check_in": "15.05.2026", ... },
    "hotels": [
      {
        "id": "12345",
        "name": "Hotel Example",
        "stars": 4,
        "city": "Москва",
        "country": "Россия",
        "address": "...",
        "photos": [{ "url": "...", "thumb": "...", "is_default": true }],
        "rooms": [...],
        "hs": "search-hash-for-view"
      }
    ],
    "is_completed": true
  },
  "ts": 1714063200
}
POST/api/v1/integration/travel/hotels/search/async

Async search with Redis cache and gzip stream. Returns first batch + agent_hash for polling. Recommended for progressive UX.

Request
{
  "city": "7000546",                  // числовой id из /hotels/destination
  "check_in": "15.05.2026",           // строго dd.mm.yyyy
  "check_out": "20.05.2026",
  "adults": 2,
  "children": [],
  "lang": "ru"
}
GET/api/v1/integration/travel/hotels/search/async-by-hash?search_hash=...&search_params_hash=...&lang=ru

Polling for async search. Call until response contains is_completed: true. Body is delivered as gzip stream.

POST/api/v1/integration/travel/hotels/search/create-hash

Low-level: create only agent_hash without starting search. Normally /search/async is enough.

POST/api/v1/integration/travel/hotels/search/by-ids

Search by list of known hotel_ids — for favorites, viewing history.

POST/api/v1/integration/travel/hotels/search/view

Available rooms in specific hotel with prices and cancellation conditions.

Request body
{
  "hotel_id": "12345",
  "check_in": "15-05-2026",           // dd-mm-yyyy
  "check_out": "20-05-2026",
  "adults": 2,
  "children": [{ "child_age": 5 }],
  "lang": "ru"
}
Response 200 (важные поля)
{
  "success": true,
  "data": {
    "hs": "eyJob3RlbF9pZCI6IjQxODUzMyIsImhzIjpudWxsLCJjaGVja19pbiI6...",
                                        // передавайте строку как есть в /check-rate и /book.
                                        // Это base64 параметров просмотра; при поиске по hotel_id
                                        // внутри лежит "hs": null — это нормально, не блокирует бронь
    "hotel": {
      "id": "12345",
      "name": "Hotel Example",
      "rooms": [
        {
          "id": "149e71dd-3257-43e3-8f06-9bb51208a270",  // UUID, внутренний ключ.
                                             // НЕ передавайте его в rate_id — провайдер его не знает
          "identifier": "418533..roomOnly..Улучшенный..19082026..835828f9..30,30",
                                             // ЭТО и есть идентификатор тарифа:
                                             // → recommendation_id в /select-room
                                             // → rate_id в /check-rate и /book
          "name": "Standard Double",
          "type": "DBL",
          "price": 12500.00,                 // в валюте провайдера
          "totalAmount": 12500.00,
          "mealType": "BB",                  // BB / HB / FB / AI
          "isFreeCancellation": true,
          "freeCancellationBefore": "...",
          "is_non_refundable": false,
          "cancelationRules": [...],         // → cancellation_policy_rules для /book
          "available_count": 3
        }
      ]
    }
  },
  "ts": 1714063200
}
GET/api/v1/integration/travel/hotels/search/view-by-hash?hotel_id=...&hs=...&lang=ru

Quick room view by hs from search step (no repeat /view call).

GET/api/v1/integration/travel/hotels/hotel/{hotel_id}?lang=ru

Hotel data: description, photos, rating, amenities.

POST/api/v1/integration/travel/hotels/check-rate

Mandatory final check of price and cancellation rules BEFORE /book. If price/rules changed — show user and ask confirmation.

Request body (поля из /search/view)
{
  "hs": "rooms-search-hash",
  "hotel_id": "12345",
  "rate_id": "418533..roomOnly..Улучшенный..19082026..835828f9..30,30",   // rooms[].identifier
  "price": 12500.00,
  "is_not_refundable": false,
  "free_cancellation_before": "2026-05-13T12:00:00+00:00",
  "cancellation_policy_rules": [
    {
      "isPossible": true,
      "amount": 0,
      "UTCDateFrom": "2026-04-25T00:00:00+00:00",
      "UTCDateTo": "2026-05-13T12:00:00+00:00"
    }
  ],
  "lang": "ru"
}
Response 200
{
  "success": true,
  "data": {
    "rate_id": "ROOM_ID",
    "check_price_changes": {
      "old": { "totalAmount": 12500, "price": 12500 },
      "new": { "totalAmount": 12500, "price": 12500 }
    },
    "cancellation_policy_rules_changes": {
      "old": [...],
      "new": [...]
    }
  },
  "ts": 1714063200
}

Order & Booking

POST/api/v1/integration/travel/hotels/create-order

Create empty hotel order draft. Supports Idempotency-Key.

Request body
{
  "order_uuid": "550e8400-e29b-41d4-a716-446655440000",  // сгенерируйте UUID v4
  "hotel_id": "12345",
  "search_hash": "rooms-search-hash",   // optional, hs из /view
  "hotel_name": "Hotel Example",        // для отображения
  "lang": "ru"
}
Response 200
{
  "success": true,
  "data": {
    "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "order_id": 78901,
    "status": "created",
    "datetime": "2026-04-25T20:00:00Z"
  },
  "ts": 1714063200
}
POST/api/v1/integration/travel/hotels/create-order-with-view

Atomic: create order + get hotel view (rooms, prices) in one call. Convenient for "selected hotel by name" flow.

Request body
{
  "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "view": {
    "hotel_id": "12345",
    "check_in": "15-05-2026",
    "check_out": "20-05-2026",
    "adults": 2,
    "children": [],
    "lang": "ru"
  }
}
Response 200
{
  "success": true,
  "data": {
    "order": { "order_uuid": "...", "order_id": 78901, "status": "created" },
    "view": { /* такой же формат, как у /search/view */ }
  },
  "ts": 1714063200
}
POST/api/v1/integration/travel/hotels/select-room

Select specific room and rate in existing order.

Request body
{
  "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "recommendation_id": "ROOM_IDENTIFIER",   // identifier из /view → rooms[].identifier
  "payload": "{\"...full room JSON from /view...\"}"   // payload номера, как пришёл из /view
}
Response 200
{
  "success": true,
  "data": {
    "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "status": "room_selected"
  },
  "ts": 1714063200
}
POST/api/v1/integration/travel/hotels/create-order-and-select-room

Atomic: create order and select room at once. If room selection fails, order is auto-deleted.

Request body (объединяет поля create-order + select-room)
{
  "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "hotel_id": "12345",
  "search_hash": "rooms-search-hash",
  "hotel_name": "Hotel Example",
  "lang": "ru",
  "recommendation_id": "ROOM_IDENTIFIER",
  "payload": "{\"...room JSON...\"}"
}
Response 200
{
  "success": true,
  "data": {
    "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "order_id": 78901,
    "status": "room_selected",
    "myagent_price": 12500.00,
    "alfabit_price": 13125.00            // включая нашу маржу
  },
  "ts": 1714063200
}
POST/api/v1/integration/travel/hotels/book

Final booking with guests, contacts and confirmed cancellation rules. ALWAYS call /check-rate first. Send Idempotency-Key — repeat will not create double booking.

Request body
{
  "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "client_email": "user@example.com",
  "client_phone": "+79001234567",
  "guests": [
    { "first_name": "Ivan", "last_name": "Ivanov", "type": "adt" },
    { "first_name": "Anna", "last_name": "Ivanova", "type": "chd", "age": 5 }  // age обязателен для chd/inf
  ],
  "hs": "rooms-search-hash",
  "hotel_id": "12345",
  "hotel_name": "Hotel Example",
  "rate_id": "418533..roomOnly..Улучшенный..19082026..835828f9..30,30",
                                       // тот же rooms[].identifier, что в /select-room и /check-rate
  "price": 12500.00,                   // из /check-rate
  "is_not_refundable": false,
  "free_cancellation_before": "2026-05-13T12:00:00+00:00",
  "cancellation_policy_rules": [        // из /check-rate
    {
      "isPossible": true,
      "amount": 0,
      "UTCDateFrom": "2026-04-25T00:00:00+00:00",
      "UTCDateTo": "2026-05-13T12:00:00+00:00"
    }
  ],
  "lang": "ru"
}
Header (рекомендуется)
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Response 200
{
  "success": true,
  "data": {
    "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "billing_number": "BN-2026-78901",
    "status": "booked"               // booked / failed
  },
  "ts": 1714063200
}

Payment

POST/api/v1/integration/travel/hotels/pay/crypto

Creates invoice for crypto/fiat payment. Returns wallet address, QR code and invoice lifetime. Supports Idempotency-Key.

Request body
{
  "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "currency_in_code": "USDT_TRC20",   // USDT_TRC20 / BTC / ETH / RUB / ...
  "use_miles": false,
  "miles_to_redeem": null              // если use_miles=true
}
Response 200
{
  "success": true,
  "data": {
    "invoice_id": "INV-12345",
    "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "currency": "USDT_TRC20",
    "amount": "100.00",
    "deposit_address": "TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE",
    "qr_code_url": "https://...",
    "expires_at": "2026-04-25T21:00:00Z",
    "status": "pending"
  },
  "ts": 1714063200
}
POST/api/v1/integration/travel/hotels/pay/wallet

Instant hotel payment from Wallet balance (deducted from balance). CRITICAL to send Idempotency-Key, otherwise repeat will double-charge.

Request body
{
  "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "currency": "USDT",                  // валюта баланса
  "use_miles": false,
  "miles_to_redeem": null
}
Header (КРИТИЧНО)
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Response 200
{
  "success": true,
  "data": {
    "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "status": "paid",
    "paid_at": "2026-04-25T20:05:00Z",
    "amount_charged": "100.00",
    "currency": "USDT",
    "balance_after": "1150.00"
  },
  "ts": 1714063200
}
GET/api/v1/integration/travel/hotels/wallet/balance?currency=USDT

Wallet balance for hotel payment. Check before /pay/wallet.

Query params
currency=USDT     // USDT / BTC / ETH / RUB
Response 200
{
  "success": true,
  "data": {
    "user_id": "sso-id-of-user",
    "currency": "USDT",
    "balance": "1250.00"
  },
  "ts": 1714063200
}

Miles (loyalty)

GET/api/v1/integration/travel/hotels/miles/preview?order_uuid=...

How many miles can be spent on this order (limits and balance considered).

GET/api/v1/integration/travel/hotels/miles/balance

Loyalty miles balance and public accrual settings.

Order management

GET/api/v1/integration/travel/hotels/orders

All your hotel orders.

GET/api/v1/integration/travel/hotels/order/{order_uuid}

Hotel order details: room, dates, guests, cost, voucher URL.

Response 200
{
  "success": true,
  "data": {
    "order_id": 78901,
    "status": "paid",
    "expire": "2026-04-25T21:00:00Z",
    "guests": [
      { "first_name": "Ivan", "last_name": "Ivanov", "type": "adt" },
      { "first_name": "Anna", "last_name": "Ivanova", "type": "chd", "age": 5 }
    ],
    "hotel": { /* такой же формат, как у /search/view */ },
    "price": 12500.00,
    "billing_number": "BN-2026-78901",
    "receipt": "https://...voucher.pdf",
    "client_email": "user@example.com"
  },
  "ts": 1714063200
}
GET/api/v1/integration/travel/hotels/order/{order_uuid}/status

Hotel order status. Possible values: created, room_selected, booked, paid, confirmed, cancelled, refunded, failed.

Response 200
{
  "success": true,
  "data": {
    "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "status": "paid",
    "updated_at": "2026-04-25T20:05:00Z"
  },
  "ts": 1714063200
}
POST/api/v1/integration/travel/hotels/order/{order_uuid}/cancel

Cancel hotel booking. Body is optional. If order is paid and refund applies, can_travel_refund is also required. Supports Idempotency-Key.

Request body (опционально)
{
  "reason": "hotel_other_reason",        // optional
  "reason_comment": "Изменились планы"   // обязателен только если reason='hotel_other_reason'
}
Response 200
{
  "success": true,
  "data": {
    "order_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "status": "cancelled",
    "refund_amount": "12500.00",         // если был paid
    "refund_currency": "RUB"
  },
  "ts": 1714063200
}
GET/api/v1/integration/travel/hotels/guests

Saved guests for quick booking.

Webhooks

A single webhook system for all products. The URL is set once per account (not in operation bodies). After you subscribe, events for wallet, trading, cards, invoices, service payments and Travel are delivered to your URL.

Source of truth — GET /api/v1/integration/webhooks/events

Always verify event names against this endpoint — it returns the live catalog. The table below matches it 1:1. Dots are part of the name (e.g. service_payment.created, card.transaction), NOT a wildcard like service.payment.*.

POST/api/v1/integration/webhooks

Create a subscription: pass url and an events array (names from the table below). The response returns secret once — store it to verify HMAC.

Request
{
  "url": "https://your-domain.com/webhook",
  "events": [
    "invoice.paid",
    "card.transaction",
    "service_payment.confirmed",
    "travel.avia_order.paid"
  ]
}

Manage via GET / PATCH / DELETE /api/v1/integration/webhooks/{id}. Change subscribed events with PATCH events. Delivery logs: GET .../webhooks/{id}/logs.

Signature verification (HMAC)

Every request carries X-Webhook-Event (event name) and X-Webhook-Signature (HMAC-SHA256 hex computed with your subscription secret — the one returned once when the webhook was created, not the API secret). The same value is duplicated in the signature field inside the body: compare against either one.

Headers
X-Webhook-Event: invoice.paid
X-Webhook-Signature: <hmac-sha256 hex>
Content-Type: application/json

The signature covers the event envelope {event, timestamp, data} WITHOUT the signature field, re-serialized as canonical JSON. Do not hash the raw request body: it already contains signature and its key order differs from the canonical one. Parse the body, drop signature and re-serialize.

Formula
message   = canonical_json({"event": ..., "timestamp": ..., "data": {...}})
signature = HMAC-SHA256(webhook_secret, message) → hex
canonical_json rules
  • The signature field is excluded from the signed object.
  • Keys are sorted alphabetically and recursively — including keys inside data. At the top level the order becomes data, event, timestamp.
  • Compact separators, no spaces: "," and ":".
  • Non-ASCII characters are escaped as \uXXXX (equivalent to json.dumps with ensure_ascii=True).
Python
import hashlib, hmac, json

WEBHOOK_SECRET = "your_webhook_secret"

def verify(body: dict) -> bool:
    received = body.get("signature", "")
    payload = {k: v for k, v in body.items() if k != "signature"}
    message = json.dumps(payload, separators=(",", ":"), sort_keys=True)
    expected = hmac.new(
        WEBHOOK_SECRET.encode(), message.encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, received)
⚠️
Common causes of a signature mismatch
  • Hashing the raw request body as-is — its key order is not sorted.
  • Hashing only the data object — the whole envelope is signed, including event and timestamp.
  • The signature field was left inside the signed object.
  • JSON serialized with spaces after "," and ":".
  • The API key secret was used instead of the webhook secret.

Full event catalog

EventDescription
Wallet
deposit.pendingDeposit detected (crypto only)
deposit.confirmedDeposit confirmed (crypto and RUB)
deposit.failedRUB deposit failed or refunded
withdrawal.processingWithdrawal processing (crypto only; not sent for SBP)
withdrawal.completedWithdrawal completed (crypto and RUB)
withdrawal.failedWithdrawal failed, funds returned (crypto and RUB)
transfer.receivedInternal transfer received
conversion.completedConversion completed
Trading (Spot)
order.createdOrder created
order.filledOrder filled
order.partially_filledOrder partially filled
order.cancelledOrder cancelled
trade.executedTrade executed (fill on order)
trading.deposit.completedTrading account deposit completed (Spot Fiat)
trading.withdraw.completedTrading account withdraw completed (Spot Fiat)
trading.balance.updatedTrading balance updated (Spot Fiat)
Invoices
invoice.createdInvoice created
invoice.paidInvoice paid
invoice.expiredTTL expired with empty txid (no payment). An underpay that already arrived does not go here
invoice.cancelledInvoice cancelled by merchant
invoice.refundedProvider refunded the payer
invoice.hedgedInvoice auto-conversion finished, funds credited in the target coin
invoice.template.deactivatedPermanent invoice template deactivated
Cards
card.transactionCard transaction (incl. issue/top-up)
Service payments
service_payment.createdService payment created
service_payment.confirmedService payment confirmed
service_payment.failedService payment failed
service_payment.cancelledService payment cancelled
Travel — Hotels
travel.hotel_order.createdHotel order draft created
travel.hotel_order.bookedHotel booked (awaiting payment)
travel.hotel_order.paidHotel order paid
travel.hotel_order.confirmedHotel confirmed (final)
travel.hotel_order.cancelledHotel order cancelled
travel.hotel_order.refundedHotel order refunded
travel.hotel_order.failedHotel order failed
Travel — Avia
travel.avia_order.createdAvia order draft created
travel.avia_order.bookedAvia ticket booked (awaiting payment)
travel.avia_order.paidAvia order paid (ticket issued)
travel.avia_order.cancelledAvia order cancelled
travel.avia_order.refundedAvia order refunded
travel.avia_order.failedAvia order failed

There is NO giftcard.* event — track gift card purchases via GET /giftcards/orders. Wildcards like service.payment.* / card.* are not supported: subscribe to exact names from the table.

Deposits and payouts: crypto and RUB share the same events

The deposit.confirmed, withdrawal.completed and withdrawal.failed events are sent both for crypto operations and for RUB (SBP) requests. Their field sets differ, so data carries a kind discriminator: crypto or fiat. The deposit.failed event is fiat-only. withdrawal.processing and deposit.pending are crypto-only: SBP has no in-flight webhook — poll GET /funding/deposit/fiat/{id} or GET /funding/withdraw/fiat/{id} while the request is open.

Payload — deposit.confirmed (kind: fiat)
{
  "event": "deposit.confirmed",
  "timestamp": "2026-08-28T09:14:02Z",
  "data": {
    "kind": "fiat",
    "transaction_id": "6a2a0425-d681-4b8c-9792-4ce15d46ef0c",
    "status": "success",
    "currency": "RUB",
    "amount": "10000.00",
    "credited_amount": "9820.00",
    "fee": "180.00",
    "payment_provider_alias_code": "SBER",
    "error": null,
    "error_code": null
  },
  "signature": "..."
}
Payload — withdrawal.failed (kind: fiat)
{
  "event": "withdrawal.failed",
  "timestamp": "2026-08-28T09:41:55Z",
  "data": {
    "kind": "fiat",
    "transaction_id": "6f05ad6f-6cd2-47f9-880b-87ed1b58abdc",
    "status": "failed",
    "currency": "RUB",
    "amount": "500.00",
    "amount_fact": "0.00",
    "fee": "10.00",
    "total": "510.00",
    "bank_code": "GAZPROM",
    "recipient": "7900****67",
    "error": "Проведение операции невозможно",
    "error_code": "generic_refund"
  },
  "signature": "..."
}
FieldDescription
kindcrypto or fiat. Use it to tell the two apart instead of probing for individual keys.
transaction_idThe same identifier returned by the create request and accepted by GET /funding/deposit/fiat/{id} and GET /funding/withdraw/fiat/{id}. Deduplicate redeliveries by it.
credited_amountThe RUB amount actually credited. null when no RUB were credited — e.g. for an invoice paid for conversion: the credited crypto arrives with conversion.completed.
amount_factThe amount actually sent to the recipient. 0.00 on failure.
error / error_codeFailure reason and its code. Filled only on a terminal failure; when no reason is available they are null rather than repeating the word failed.
recipientRecipient's details, masked the same way as in the withdrawal GET response.

The event is sent only for a terminal outcome: intermediate statuses are not duplicated over webhooks. Amounts come with two decimals — the same format the deposit and withdrawal GET endpoints return.

transfer.received

An incoming internal transfer credited to your balance. The format is identical for crypto and RUB.

Payload
{
  "event": "transfer.received",
  "timestamp": "2026-07-30T10:37:15Z",
  "data": {
    "transfer_id": "2b8bc516-e49a-40eb-b8c1-1a0fa02c2cfe",
    "transaction_uid": "2b8bc516-e49a-40eb-b8c1-1a0fa02c2cfe",
    "symbol": "RUB",
    "amount": "3872.01",
    "status": "credited",
    "user_comment": "AB-7K3QF9XM",
    "sender_profile_id": 904016,
    "sender_email": "payer@example.com",
    "recipient_profile_id": 7504,
    "method": null
  },
  "signature": "..."
}
FieldDescription
transfer_idTransfer identifier. Equals transaction_uid and the operation uid in GET /integration/transactions — use it to deduplicate redeliveries.
statusAlways credited: the event is sent only after the funds are actually credited and available to the recipient.
user_commentSender's comment (up to 500 characters), null when not provided.
sender_emailSender's email, null if the profile has none.
methodAddressing method of a crypto transfer (tg / email / phone). Always null for RUB transfers — the method is not stored.

A redelivery is NOT byte-identical: the timestamp is regenerated on retry, so the signature changes too, while the data object stays the same. Deduplicate by transfer_id, not by a body hash.

A non-2xx response and a timeout (10s) trigger a retry: 3 attempts in total — immediately, after 30 seconds and after 2 minutes.

Pre-filled transfer link

The link opens the internal transfer form already filled in. It does not send anything: the recipient is verified by the platform, the amount is validated by the field rules, and the user presses confirm themselves.

URL
https://alfabit.org/ru/user/transfer?to=user%40example.com&method=email&symbol=USDT&amount=25&comment=Order%20A-1042
ParameterDescription
toRecipient: email, @telegram_username or a phone number in +71234567890 format.
methodemail, telegram_username or phone. Optional — the method is derived from the format of to.
symbolAsset to transfer, e.g. USDT or RUB. If omitted, the user picks it themselves.
amountAmount without thousand separators, dot as the decimal separator. It is placed into the field and goes through the usual balance and limit checks.
commentSender's comment, up to 140 characters. Reaches the recipient in the history and in the transfer.received webhook as user_comment.

All parameters are optional and invalid ones are silently ignored — the form simply opens with an empty field. The link requires authentication: an unauthenticated user is sent to the login page first and lands on the filled form afterwards.

Currencies & networks directory

The single source of truth for coins and networks: amount precision, limits, fees and operation availability. Read it before a withdrawal or before issuing an invoice — parameters differ per network and change without notice (a network can be temporarily halted on the blockchain side).

GET/api/v1/integration/market/currencies

An array of records, one per coin+network pair. No API key required.

Response
{
  "success": true,
  "data": [
    {
      "symbol": "USDT",
      "bch_code": "TRX",
      "withdraw_amount_decimals": "6",
      "min_withdraw_amount": "3",
      "max_withdraw_amount": "150000",
      "withdraw_service_fee": "2.5",
      "is_withdraw_active": true,
      "is_deposit_active": true,
      "min_deposit_amount": "1",
      "is_memo_tag_required": false,
      "net_confirmations": 20
    },
    {
      "symbol": "USDT",
      "bch_code": "BSC",
      "withdraw_amount_decimals": "8",
      "min_withdraw_amount": "10",
      "max_withdraw_amount": "150000",
      "withdraw_service_fee": "1.2",
      "is_withdraw_active": true
    }
  ],
  "ts": 1706000000
}
  • symbol + bch_code — coin and network. Pass the same pair to withdrawals and invoices.
  • withdraw_amount_decimals — how many decimals the network accepts on withdrawal. Extra decimals are truncated down.
  • min_withdraw_amount / max_withdraw_amount — per-request withdrawal limits in this network.
  • withdraw_service_fee — withdrawal fee denominated in the coin.
  • is_withdraw_active / is_deposit_active — whether operations in this network are open right now.
  • is_memo_tag_required — whether the recipient requires a memo/tag (XRP, TON and similar).

The payload is large — cache it on your side and refresh every few minutes rather than before every operation.

GET/api/v1/integration/market/rates

Coin prices denominated in USDT — one record per symbol. No API key required.

Response
{
  "success": true,
  "data": [
    { "symbol": "USDT", "rate_usdt": "1" },
    { "symbol": "BTC", "rate_usdt": "78635.6" },
    { "symbol": "ETH", "rate_usdt": "2470.64" },
    { "symbol": "TRX", "rate_usdt": "0.3438" }
  ],
  "ts": 1706000000
}
📊
These are reference quotes, not a deal price

Use these values for display, portfolio valuation and rough estimates. The price an operation actually executes at comes from a product quote — /converter/crypto/estimate or /converter/fiat/estimate: it accounts for direction, size and fee, and is valid for a limited time. Do not compute the charged amount from rate_usdt.

Tariffs & Fees

Current tariff plan info and fee rates for all operation types.

GET/api/v1/integration/market/tariffs

Fee schedule: withdrawal, exchange, card top-up and other operation fees. No API key required.

Response
{
  "tariff": "standard",
  "fees": {
    "withdraw_btc": "0.0001",
    "withdraw_usdt_trc20": "1",
    "exchange_fee": "0.1%",
    "card_topup_fee": "2%"
  }
}

Transactions

Unified history of all operations: deposits, withdrawals, transfers, exchanges, purchases. Crypto and fiat (RUB) operations are merged into a single feed — tell them apart by the source field.

GET/api/v1/integration/transactions

All transactions with filters by currency and period. Supports pagination and search.

ParameterTypeDescription
symbolstringFilter by currency (USDT, BTC, RUB). RUB leaves only fiat operations in the feed, any other currency — only crypto ones.
limitnumberRecords count (default: 50, max: 100)
pagenumberPage number (default: 1)
start_periodnumberPeriod start (Unix timestamp)
end_periodnumberPeriod end (Unix timestamp)
search_termstringSearch transactions
Response
{
  "success": true,
  "data": {
    "page": 1,
    "limit": 50,
    "count": 156,
    "results": [
      {
        "source": "fiat",
        "uid": "0f3c1a7e-1b9d-4f2a-9c11-6a0d5e7b8c34",
        "type": "deposit",
        "symbol": "RUB",
        "amount": "5000.00",
        "status_for_client": "success",
        "is_final_state": true,
        "method": null,
        "fee": "0.0",
        "sender_id": 7504,
        "receiver_id": 9001,
        "user_comment": "order-12345",
        "sender": { "email": "client@example.com", "telegram_username": null },
        "receiver": { "email": "shop@example.com", "telegram_username": null },
        "created_at": 1753812345.0
      },
      {
        "source": "crypto",
        "uid": "a1b2c3d4-...",
        "type": "deposit",
        "symbol": "USDT",
        "amount": "500.00",
        "status_for_client": "success",
        "is_final_state": true,
        "method": "email",
        "created_at": 1753810000.0
      }
    ]
  },
  "ts": 1706000000
}

Record fields

FieldDescription
source"crypto" for crypto operations, "fiat" for RUB ones.
uidOperation identifier. For internal transfers it equals transfer_id from the transfer.received webhook — use it as the idempotency key.
typeType from the key owner point of view: incoming is deposit, outgoing is withdraw. Internal transfers are resolved by role: the recipient sees deposit.
status_for_clientwait | success | failed | blocked | aml.
is_final_stateThe status is terminal. While false the state may still change — do not settle yet.
methodTransfer addressing method (email, telegram, phone). Always null for RUB operations — the field is kept for uniform parsing.
sender / receiverTransfer participants (email, telegram_username). Match the payer by sender.email.
user_commentSender comment — a convenient place to ask the client for an order number.
created_atCreation time, Unix timestamp (number, not a string).
ℹ️
Accepting payments via internal transfer

To accept top-ups via an internal AlfaBit transfer, subscribe to the transfer.received webhook — it is sent only once the funds are credited and carries transfer_id, amount, sender_email and the sender comment. Use this feed as a reconciliation fallback: filter records with type = deposit and is_final_state = true, deduplicate by uid.

Deep pagination of the merged feed is limited: page × limit must not exceed 2000, otherwise the API returns 400 DEEP_PAGINATION_NOT_SUPPORTED. To export history, narrow the range with start_period / end_period.

GET/api/v1/integration/transactions/{transaction_id}

Specific transaction details: amount, status, fee, date, participants. Pass the uid from the feed (same value as the webhook transfer_id) as transaction_id; the endpoint looks the operation up among both crypto and RUB records.

Response
{
  "success": true,
  "data": {
    "source": "fiat",
    "uid": "0f3c1a7e-1b9d-4f2a-9c11-6a0d5e7b8c34",
    "type": "deposit",
    "symbol": "RUB",
    "amount": "5000.00",
    "status_for_client": "success",
    "is_final_state": true,
    "fee": "0.0",
    "sender_id": 7504,
    "receiver_id": 9001,
    "user_comment": "order-12345",
    "created_at": 1753812345.0
  },
  "ts": 1706000000
}