Vaeloq
Home Features Pricing Docs Security About Blog
Log in Start free

API Documentation

Everything you need to accept TON, BNB, ETH, TRX, SOL, BTC, and USDT (on BEP-20, ERC-20, TRC-20, and SPL) payments: authentication, payments, wallet balance, withdrawals, webhooks, API keys, and KYC verification. All endpoints return JSON.

Base URL https://api.vaeloq.tonbaz.ir

Authentication

There are two ways to authenticate, depending on the endpoint:

  • API key (recommended for server-to-server integration) - send your key in the X-Api-Key header on every request to the Payments, Wallet, Withdrawals, and Webhooks endpoints below. Create a key from your dashboard's API Keys page, or via POST /api/merchant/api-keys.
  • Bearer token (JWT) - obtained from POST /api/auth/merchant/login, sent as Authorization: Bearer <token>. Required for account-management endpoints (profile, payout wallet, password, KYC) and also accepted anywhere an API key is accepted.

Money and units

Every response uses the same two fields for every asset: amountSmallestUnit (the exact on-chain integer - nanotons for TON, wei for BNB/ETH/USDT-BEP20/USDT-ERC20, sun for TRX/USDT-TRC20, lamports for SOL/USDT-SPL, satoshis for BTC) and amount (a decimal in the asset's native display unit, e.g. TON or BNB rather than nanotons/wei). Requests that accept an amount use a plain amount field; check each endpoint below for exact field names.

Pagination

List endpoints accept pageNumber (default 1) and pageSize (default 20) query parameters and return a paginated envelope:

{
  "items": [ /* ... */ ],
  "pageNumber": 1,
  "totalPages": 3,
  "totalCount": 47,
  "hasPreviousPage": false,
  "hasNextPage": true
}

Register a merchant account

POST /api/auth/merchant/register

No authentication required. Creates your account in PendingVerification status - you'll need to complete KYC (see below) before you can create API keys or accept payments.

Request

POST /api/auth/merchant/register
Content-Type: application/json

{
  "businessName": "Acme Store",
  "email": "owner@acmestore.example",
  "password": "a-strong-password",
  "payoutWalletAddress": "EQDWPJ1jEzOlQfdFfn_XxmheIMDPhFKtSvzecTEozMJ7KDdY"
}

payoutWalletAddress here is your TON payout address - optional at registration, can be set/changed later (see below).

Response 201 Created

{
  "id": "3cfb72a7-da0d-457f-af8d-8f975763b7e9"
}

Log in

POST /api/auth/merchant/login

Returns a JWT access token, valid for account-management endpoints and as an alternative to an API key everywhere else.

Request

POST /api/auth/merchant/login
Content-Type: application/json

{
  "email": "owner@acmestore.example",
  "password": "a-strong-password"
}

Response 200 OK

{
  "isSuccess": true,
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "failureReason": null
}

On bad credentials, isSuccess is false and failureReason explains why - the HTTP status is still 200.

Forgot / reset password

POST /api/auth/merchant/forgot-password

Always returns 204, whether or not the email is registered, to avoid leaking which addresses have accounts. Sends a reset email if the account exists.

POST /api/auth/merchant/forgot-password
Content-Type: application/json

{ "email": "owner@acmestore.example" }

POST /api/auth/merchant/reset-password

POST /api/auth/merchant/reset-password
Content-Type: application/json

{
  "token": "<token from the emailed reset link>",
  "newPassword": "a-new-strong-password"
}

Response: 204 No Content on success, 400 with a validation error if the token is invalid or expired.

Get your profile

GET /api/merchant/profile (Bearer token)

Response 200 OK

{
  "id": "3cfb72a7-da0d-457f-af8d-8f975763b7e9",
  "businessName": "Acme Store",
  "email": "owner@acmestore.example",
  "tonPayoutWalletAddress": "EQDWPJ1jEzOlQfdFfn_XxmheIMDPhFKtSvzecTEozMJ7KDdY",
  "bscPayoutWalletAddress": null,
  "tronPayoutWalletAddress": null,
  "status": "Active",
  "feeOverridePercent": null,
  "feePayer": "Merchant",
  "amountTolerancePercent": 0,
  "created": "2026-07-15T09:12:00Z"
}

status is one of PendingVerification, Active, Suspended. You must be Active (KYC-approved) before you can create API keys or accept live payments. feePayer is Merchant (default) or Buyer - see below. amountTolerancePercent is covered under Amount tolerance.

Update your payout wallet addresses

Each network has its own payout wallet field, and each is independent - you only need to set the ones for the networks you want to accept. A network only appears as an option on your customers' universal payment pages once its payout wallet is set.

PUT /api/merchant/payout-wallet (Bearer token)

PUT /api/merchant/payout-wallet
Content-Type: application/json

{ "payoutWalletAddress": "EQDWPJ1jEzOlQfdFfn_XxmheIMDPhFKtSvzecTEozMJ7KDdY" }

Response: 204 No Content. TON withdrawals (and TON-priced fee revenue) are sent here.

PUT /api/merchant/payout-wallet/bsc (Bearer token)

PUT /api/merchant/payout-wallet/bsc
Content-Type: application/json

{ "bscPayoutWalletAddress": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94" }

Response: 204 No Content. BNB and USDT-BEP20 withdrawals are sent here.

PUT /api/merchant/payout-wallet/eth (Bearer token)

PUT /api/merchant/payout-wallet/eth
Content-Type: application/json

{ "payoutWalletAddress": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94" }

Response: 204 No Content. ETH and USDT-ERC20 withdrawals are sent here.

PUT /api/merchant/payout-wallet/tron (Bearer token)

PUT /api/merchant/payout-wallet/tron
Content-Type: application/json

{ "tronPayoutWalletAddress": "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH" }

Response: 204 No Content. TRX and USDT-TRC20 withdrawals are sent here.

PUT /api/merchant/payout-wallet/sol (Bearer token)

PUT /api/merchant/payout-wallet/sol
Content-Type: application/json

{ "payoutWalletAddress": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" }

Response: 204 No Content. SOL and USDT-SPL withdrawals are sent here.

PUT /api/merchant/payout-wallet/btc (Bearer token)

PUT /api/merchant/payout-wallet/btc
Content-Type: application/json

{ "payoutWalletAddress": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq" }

Response: 204 No Content. BTC withdrawals are sent here.

Choose who pays the platform fee

PUT /api/merchant/fee-payer (Bearer token)

PUT /api/merchant/fee-payer
Content-Type: application/json

{ "feePayer": "Buyer" }

Response: 204 No Content, or 400 if feePayer isn't Merchant or Buyer. With Merchant (default), the fee is deducted from each payment before it's credited to your balance. With Buyer, new payment links are priced slightly above the amount you request so that, after the fee is deducted, you receive the full amount - see centralWalletAddress/amountTon under Create a payment. Changing this only affects payments created afterward.

Amount tolerance

Customers don't always send the exact invoiced amount. By default Vaeloq requires an exact match; you can configure a tolerance percentage so small differences still confirm automatically, and larger differences are held for you to approve or reject instead of being silently ignored.

PUT /api/merchant/amount-tolerance (Bearer token)

PUT /api/merchant/amount-tolerance
Content-Type: application/json

{ "amountTolerancePercent": 2 }

Response: 204 No Content. amountTolerancePercent is a value from 0 to 100. With the example above, a payment received within ±2% of the invoiced amount auto-confirms using the amount actually received (fees and your net credit are calculated on the actual amount, not the invoiced one). 0 (default) requires an exact match, matching behavior from before this setting existed. Amounts outside the tolerance band are never dropped - they always go to payment review for you to approve or reject. This setting doesn't apply to Pro plan purchase/renewal payments, which always require an exact match.

Change your password

PUT /api/merchant/password (Bearer token)

PUT /api/merchant/password
Content-Type: application/json

{
  "currentPassword": "a-strong-password",
  "newPassword": "an-even-stronger-password"
}

Response: 204 No Content, or 400 if the current password is wrong.

KYC verification

Your account must complete identity verification before it can create API keys or accept payments. Submit clear photos of the front and back of your national ID card; an administrator reviews and approves or rejects the submission.

POST /api/merchant/kyc (Bearer token, multipart/form-data)

Request

POST /api/merchant/kyc
Content-Type: multipart/form-data; boundary=----boundary

------boundary
Content-Disposition: form-data; name="front"; filename="id-front.jpg"
Content-Type: image/jpeg

<binary image data>
------boundary
Content-Disposition: form-data; name="back"; filename="id-back.jpg"
Content-Type: image/jpeg

<binary image data>
------boundary--

Response 200 OK

{ "id": "1091dfb4-debc-45ef-8be5-c0618f36bac9" }

JPG, PNG, or WEBP only, 10 MB max per image. You cannot submit again while a submission is pending review.

GET /api/merchant/kyc/status (Bearer token)

Response 200 OK

{
  "status": 0,
  "rejectionReason": null,
  "submittedAt": "2026-08-01T06:47:08Z",
  "reviewedAt": null
}

status: null = not submitted yet, 0 = Pending, 1 = Approved, 2 = Rejected (check rejectionReason and re-submit).

API keys

Requires an Active account (KYC-approved).

GET /api/merchant/api-keys (Bearer token)

Response 200 OK

[
  {
    "id": "2439e497-949c-453b-a598-c32bdb38e787",
    "keyPrefix": "aKPqEdx5",
    "label": "Production",
    "isActive": true,
    "lastUsedAt": "2026-08-01T09:30:00Z",
    "created": "2026-08-01T06:41:00Z"
  }
]

POST /api/merchant/api-keys (Bearer token)

Request

POST /api/merchant/api-keys
Content-Type: application/json

{ "label": "Production" }

Response 200 OK

{
  "id": "2439e497-949c-453b-a598-c32bdb38e787",
  "rawKey": "tg_aKPqEdx5d8TUje6IE0Pd77vDs8spPixE5NHVrNuHmE2xSrZJ",
  "keyPrefix": "aKPqEdx5"
}

The full key (rawKey) is shown only this once. Store it securely - use it as the X-Api-Key header value on every request below.

DELETE /api/merchant/api-keys/{'{id}'} (Bearer token)

Response: 204 No Content. Revokes the key immediately.

Supported networks

Vaeloq accepts payments on six networks and ten assets:

AssetNetworkCreation endpoint
TONTONPOST /api/v1/payments
BNBBSC (BEP-20 chain)POST /api/v1/payments/bsc
USDT (BEP-20)BSC
ETHEthereumPOST /api/v1/payments/eth
USDT (ERC-20)Ethereum
TRXTRONPOST /api/v1/payments/tron
USDT (TRC-20)TRON
SOLSolanaPOST /api/v1/payments/sol
USDT (SPL)Solana
BTCBitcoinPOST /api/v1/payments/btc

Each of the network-specific endpoints below requires you to already know which asset/network you want the payment on. If you'd rather let your customer pick at checkout time, use POST /api/v1/payments/universal instead - see Create a universal payment. All of these endpoints are independent and none of them replace or deprecate another.

TON and Solana share a central-wallet-plus-memo model (see Create a TON payment and Create a Solana payment). Every BSC/Ethereum/TRON/Bitcoin payment instead gets its own unique one-time deposit address (depositAddress in the response) - none of those networks have a broadly wallet-supported memo mechanism, so the address itself disambiguates the order. Send exactly the requested amount to that address.

Create a TON payment

POST /api/v1/payments (X-Api-Key or Bearer)

Request

POST /api/v1/payments
X-Api-Key: tg_aKPqEdx5d8TUje6IE0Pd77vDs8spPixE5NHVrNuHmE2xSrZJ
Content-Type: application/json

{
  "amount": 12.5,
  "currency": "TON",
  "orderReference": "order-1024",
  "description": "1x Premium plan",
  "returnUrl": "https://acmestore.example/checkout/success"
}

Response 201 Created

{
  "id": "d1b2c3e4-1234-4a5b-9c0d-abcdef123456",
  "asset": "Ton",
  "memo": "TG-8Fk3QpXz",
  "amountSmallestUnit": 12500000000,
  "amount": 12.5,
  "requestedCurrency": null,
  "requestedAmount": null,
  "status": "Pending",
  "expiresAt": "2026-08-01T07:17:08Z",
  "paymentPageUrl": "https://vaeloq.tonbaz.ir/pay/d1b2c3e4-1234-4a5b-9c0d-abcdef123456",
  "centralWalletAddress": "EQDWPJ1jEzOlQfdFfn_XxmheIMDPhFKtSvzecTEozMJ7KDdY"
}

The simplest integration redirects your customer to paymentPageUrl - they pay in TON there, including the memo exactly as shown (this is how Vaeloq matches the on-chain payment to this order). orderReference, description, and returnUrl are all optional.

Redirecting is optional, not required: centralWalletAddress is also returned directly, so you can show the destination address and memo on your own checkout page and let the customer pay from within your own site instead - the same transfer either way, matched the same way on our end. Poll GET /api/v1/payments/{'{id}'} or wait for the webhook to know when it's confirmed.

By default the platform fee is deducted from the payment amount, so you receive slightly less than amount. If your account is set to have the buyer pay the fee (Dashboard → Wallet Settings), amount here is already grossed up so that, once the fee is deducted, you net exactly the amount you requested.

Pricing a payment in a fiat currency

currency is optional and defaults to TON. Set it to USD, EUR, or GBP to price the payment in that currency instead - in that case amount is interpreted as an amount in currency, not TON, and is converted to TON server-side at creation time using a TON/fiat rate cached and refreshed every 30 minutes:

POST /api/v1/payments
X-Api-Key: tg_aKPqEdx5d8TUje6IE0Pd77vDs8spPixE5NHVrNuHmE2xSrZJ
Content-Type: application/json

{ "amount": 25, "currency": "USD", "orderReference": "order-1025" }
{
  "id": "e2c3d4f5-2345-5b6c-ad1e-bcdef2345678",
  "asset": "Ton",
  "memo": "TG-3Rt7VnLp",
  "amountSmallestUnit": 8474576271,
  "amount": 8.474576271,
  "requestedCurrency": "USD",
  "requestedAmount": 25,
  "status": "Pending",
  "expiresAt": "2026-08-01T07:20:00Z",
  "paymentPageUrl": "https://vaeloq.tonbaz.ir/pay/e2c3d4f5-2345-5b6c-ad1e-bcdef2345678",
  "centralWalletAddress": "EQDWPJ1jEzOlQfdFfn_XxmheIMDPhFKtSvzecTEozMJ7KDdY"
}

The customer still pays the TON amount shown (amount/amountSmallestUnit) - requestedCurrency/requestedAmount are preserved only for display/audit purposes. Returns 400 if fiat pricing is temporarily unavailable (no cached rate yet); enter the amount in TON as a fallback.

Create a BSC payment

POST /api/v1/payments/bsc (X-Api-Key or Bearer)

Request

POST /api/v1/payments/bsc
X-Api-Key: tg_aKPqEdx5d8TUje6IE0Pd77vDs8spPixE5NHVrNuHmE2xSrZJ
Content-Type: application/json

{
  "asset": "Bnb",
  "amount": 0.05,
  "orderReference": "order-1030",
  "description": "1x Premium plan",
  "returnUrl": "https://acmestore.example/checkout/success"
}

asset is "Bnb" (default) or "UsdtBep20".

Response 201 Created

{
  "id": "f3a4b5c6-3456-6c7d-be2f-cdef34567890",
  "asset": "Bnb",
  "amount": 0.05,
  "status": "Pending",
  "expiresAt": "2026-08-01T07:30:00Z",
  "paymentPageUrl": "https://vaeloq.tonbaz.ir/pay/f3a4b5c6-3456-6c7d-be2f-cdef34567890",
  "depositAddress": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94"
}

Same optional-redirect model as TON: send your customer to paymentPageUrl, or show depositAddress directly on your own checkout page. No memo/comment is needed - the address is unique to this payment and is never reused while assigned to it. Deposits are only considered final after 15 block confirmations (~45s on BSC) to protect against chain reorgs, then automatically swept to the platform's central wallet.

Create an Ethereum payment

POST /api/v1/payments/eth (X-Api-Key or Bearer)

Request

POST /api/v1/payments/eth
X-Api-Key: tg_aKPqEdx5d8TUje6IE0Pd77vDs8spPixE5NHVrNuHmE2xSrZJ
Content-Type: application/json

{
  "asset": "Eth",
  "amount": 0.01,
  "orderReference": "order-1033",
  "description": "1x Premium plan",
  "returnUrl": "https://acmestore.example/checkout/success"
}

asset is "Eth" (default) or "UsdtErc20".

Response 201 Created

{
  "id": "c6d7e8f9-6789-9f0a-eb5c-fa6789012345",
  "asset": "Eth",
  "amount": 0.01,
  "status": "Pending",
  "expiresAt": "2026-08-01T07:30:00Z",
  "paymentPageUrl": "https://vaeloq.tonbaz.ir/pay/c6d7e8f9-6789-9f0a-eb5c-fa6789012345",
  "depositAddress": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94"
}

Same shape and behavior as BSC above (its own unique deposit address, auto-swept once confirmed). Deposits finalize after 15 block confirmations, same depth as BSC - but since Ethereum's block time is roughly 12s rather than BSC's ~3s, that's about 3 minutes to finality here rather than ~45s.

Create a TRON payment

POST /api/v1/payments/tron (X-Api-Key or Bearer)

Request

POST /api/v1/payments/tron
X-Api-Key: tg_aKPqEdx5d8TUje6IE0Pd77vDs8spPixE5NHVrNuHmE2xSrZJ
Content-Type: application/json

{
  "asset": "UsdtTrc20",
  "amount": 25,
  "orderReference": "order-1031"
}

asset is "Trx" (default) or "UsdtTrc20".

Response 201 Created

{
  "id": "a4b5c6d7-4567-7d8e-cf3a-def456789012",
  "asset": "UsdtTrc20",
  "amount": 25,
  "status": "Pending",
  "expiresAt": "2026-08-01T07:30:00Z",
  "paymentPageUrl": "https://vaeloq.tonbaz.ir/pay/a4b5c6d7-4567-7d8e-cf3a-def456789012",
  "depositAddress": "TUEZSdKsoDHQMeZwihtdoBiN46zxhGWYdH"
}

Same shape and behavior as BSC above, adjusted for TRON: deposits finalize after 19 block confirmations (~57s) before sweeping.

Create a Solana payment

POST /api/v1/payments/sol (X-Api-Key or Bearer)

Request

POST /api/v1/payments/sol
X-Api-Key: tg_aKPqEdx5d8TUje6IE0Pd77vDs8spPixE5NHVrNuHmE2xSrZJ
Content-Type: application/json

{
  "asset": "Sol",
  "amount": 0.5,
  "orderReference": "order-1034",
  "description": "1x Premium plan",
  "returnUrl": "https://acmestore.example/checkout/success"
}

asset is "Sol" (default) or "UsdtSpl".

Response 201 Created

{
  "id": "d7e8f9a0-7890-0a1b-fc6d-ab7890123456",
  "asset": "Sol",
  "memo": "TG-9Yk2WpRt",
  "amount": 0.5,
  "status": "Pending",
  "expiresAt": "2026-08-01T07:30:00Z",
  "paymentPageUrl": "https://vaeloq.tonbaz.ir/pay/d7e8f9a0-7890-0a1b-fc6d-ab7890123456",
  "centralWalletAddress": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"
}

Solana shares TON's shared-central-wallet-plus-memo model rather than BSC's per-order address model (Solana's Memo Program instruction is broadly wallet-supported, unlike Ethereum/BSC/TRON/Bitcoin). Send exactly amount to centralWalletAddress with memo attached via the Memo Program, exactly the same integration shape as the TON flow above.

Create a Bitcoin payment

POST /api/v1/payments/btc (X-Api-Key or Bearer)

Request

POST /api/v1/payments/btc
X-Api-Key: tg_aKPqEdx5d8TUje6IE0Pd77vDs8spPixE5NHVrNuHmE2xSrZJ
Content-Type: application/json

{
  "amount": 0.001,
  "orderReference": "order-1035",
  "description": "1x Premium plan",
  "returnUrl": "https://acmestore.example/checkout/success"
}

Bitcoin only supports native BTC - there is no USDT-on-Bitcoin, so there's no asset field to set.

Response 201 Created

{
  "id": "e8f9a0b1-8901-1b2c-ad7e-bc8901234567",
  "asset": "Btc",
  "amount": 0.001,
  "status": "Pending",
  "expiresAt": "2026-08-01T07:30:00Z",
  "paymentPageUrl": "https://vaeloq.tonbaz.ir/pay/e8f9a0b1-8901-1b2c-ad7e-bc8901234567",
  "depositAddress": "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"
}

Same unique-deposit-address model as BSC/Ethereum/TRON, adjusted for Bitcoin's UTXO model and much slower block time: deposits finalize after 6 confirmations (~1 hour at Bitcoin's ~10-minute block time) before being swept to the central wallet.

Create a universal payment

POST /api/v1/payments/universal (X-Api-Key or Bearer)

Use this when you don't want to decide the network up front - your customer picks one on the hosted pay page, limited to whichever networks you've enabled (i.e. set a payout wallet for, see above). amount can be entered in USD, EUR, GBP, or any of the native currencies (TON, BNB, ETH, TRX, SOL, BTC) - it's converted to each enabled network's native amount using cached exchange rates, refreshed every 15-30 minutes.

Request

POST /api/v1/payments/universal
X-Api-Key: tg_aKPqEdx5d8TUje6IE0Pd77vDs8spPixE5NHVrNuHmE2xSrZJ
Content-Type: application/json

{
  "currency": "USD",
  "amount": 25,
  "orderReference": "order-1032",
  "returnUrl": "https://acmestore.example/checkout/success"
}

Response 201 Created

{
  "id": "b5c6d7e8-5678-8e9f-da4b-ef5678901234",
  "currency": "USD",
  "amount": 25,
  "expiresAt": "2026-08-01T07:30:00Z",
  "paymentPageUrl": "https://vaeloq.tonbaz.ir/pay/b5c6d7e8-5678-8e9f-da4b-ef5678901234"
}

Unlike the three endpoints above, there is no deposit address in this response - no network has been chosen yet, so no address can exist yet. You must redirect your customer to paymentPageUrl; there is no way to build a custom checkout around this endpoint alone. Once the customer picks a network on that page, Vaeloq calls the matching network-specific creation endpoint internally and the resulting payment behaves exactly like one created directly through it (webhooks, review flow, GET /api/v1/payments/{'{id}'}, all the same).

Get a payment

GET /api/v1/payments/{'{id}'} (X-Api-Key or Bearer)

Response 200 OK

{
  "id": "d1b2c3e4-1234-4a5b-9c0d-abcdef123456",
  "asset": "Ton",
  "memo": "TG-8Fk3QpXz",
  "amountSmallestUnit": 12500000000,
  "status": "Confirmed",
  "orderReference": "order-1024",
  "expiresAt": "2026-08-01T07:17:08Z",
  "confirmedAt": "2026-08-01T07:02:41Z",
  "feeAmountSmallestUnit": 312500000,
  "netCreditedSmallestUnit": 12187500000
}

status: Pending, Confirmed, Expired, Failed, Refunded (see payment review below). amountSmallestUnit/feeAmountSmallestUnit/netCreditedSmallestUnit are in the asset's smallest on-chain unit (nanotons/wei/sun) regardless of network. The platform fee is a percentage of the payment (see your dashboard for the current rate), capped at a fixed maximum on very large payments if the platform has one configured. While a payment is awaiting your review its status stays Pending.

List your payments

GET /api/v1/payments?pageNumber=1&pageSize=20&status=Confirmed (X-Api-Key or Bearer)

status is optional; omit it to list all statuses. Returns the paginated envelope described above.

Payment review

When a received payment falls outside your configured tolerance, it isn't confirmed or dropped - it's held here, and we email you. Approving confirms it at the amount actually received; rejecting sends an automatic on-chain refund to the original sender for the amount received minus the network fee, and sets the payment's status to Refunded.

GET /api/merchant/payments/awaiting-review?pageNumber=1&pageSize=20 (Bearer token)

Response 200 OK

{
  "items": [
    {
      "id": "d1b2c3e4-1234-4a5b-9c0d-abcdef123456",
      "memo": "TG-8Fk3QpXz",
      "asset": "Ton",
      "amountSmallestUnit": 12500000000,
      "actualAmountSmallestUnit": 12000000000,
      "orderReference": "order-1024",
      "created": "2026-08-01T07:00:12Z"
    }
  ],
  "pageNumber": 1,
  "totalPages": 1,
  "totalCount": 1,
  "hasPreviousPage": false,
  "hasNextPage": false
}

GET /api/merchant/payments/awaiting-review/count (Bearer token)

{ "count": 1 }

POST /api/merchant/payments/{'{id}'}/approve-review (Bearer token)

Response: 204 No Content. Confirms the payment at actualAmountSmallestUnit.

POST /api/merchant/payments/{'{id}'}/reject-review (Bearer token)

Response: 204 No Content. The refund is sent shortly after by a background process, not synchronously in this request - poll GET /api/v1/payments/{'{id}'} and watch for status to become Refunded.

Wallet balance

GET /api/v1/wallet/balance (X-Api-Key or Bearer)

Response 200 OK

{
  "balanceNanotons": 12187500000,
  "balanceTon": 12.1875
}

This is your internal Vaeloq TON balance (net of platform fees) - not an on-chain wallet. TON only (this specific endpoint keeps its original TON-only field names for backward compatibility) - see below for BNB/USDT/TRX balances.

GET /api/v1/wallet/balances (X-Api-Key or Bearer)

Response 200 OK

[
  { "asset": "Ton", "symbol": "TON", "balanceSmallestUnit": 12187500000, "balanceDisplay": 12.1875 },
  { "asset": "Bnb", "symbol": "BNB", "balanceSmallestUnit": 0, "balanceDisplay": 0 },
  { "asset": "UsdtBep20", "symbol": "USDT", "balanceSmallestUnit": 25000000000000000000, "balanceDisplay": 25 },
  { "asset": "Eth", "symbol": "ETH", "balanceSmallestUnit": 0, "balanceDisplay": 0 },
  { "asset": "UsdtErc20", "symbol": "USDT", "balanceSmallestUnit": 0, "balanceDisplay": 0 },
  { "asset": "Trx", "symbol": "TRX", "balanceSmallestUnit": 0, "balanceDisplay": 0 },
  { "asset": "UsdtTrc20", "symbol": "USDT", "balanceSmallestUnit": 0, "balanceDisplay": 0 },
  { "asset": "Sol", "symbol": "SOL", "balanceSmallestUnit": 0, "balanceDisplay": 0 },
  { "asset": "UsdtSpl", "symbol": "USDT", "balanceSmallestUnit": 0, "balanceDisplay": 0 },
  { "asset": "Btc", "symbol": "BTC", "balanceSmallestUnit": 0, "balanceDisplay": 0 }
]

Always all ten assets, zero-balance ones included. balanceSmallestUnit is the exact on-chain unit (wei for BNB/ETH/USDT-BEP20/USDT-ERC20, sun for TRX/USDT-TRC20, nanotons for TON, lamports for SOL/USDT-SPL, satoshis for BTC) as a big integer; balanceDisplay is the same value divided down for display. Withdraw any of these to the matching payout wallet address using the endpoint below.

Ledger history

GET /api/v1/wallet/ledger?pageNumber=1&pageSize=20 (X-Api-Key or Bearer)

Paginated envelope of ledger entries (payment credits, platform fee debits, withdrawal debits, refund credits) with a running balance snapshot on each row.

Request a withdrawal

POST /api/v1/withdrawals (X-Api-Key or Bearer)

Request

POST /api/v1/withdrawals
X-Api-Key: tg_aKPqEdx5d8TUje6IE0Pd77vDs8spPixE5NHVrNuHmE2xSrZJ
Content-Type: application/json

{ "asset": "Ton", "amountSmallestUnit": 12000000000 }

Response 201 Created

{ "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" }

asset is optional and defaults to "Ton" - one of "Ton", "Bnb", "UsdtBep20", "Eth", "UsdtErc20", "Trx", "UsdtTrc20", "Sol", "UsdtSpl", or "Btc". amountSmallestUnit is the amount in the chosen asset's smallest on-chain unit (nanotons/wei/sun/lamports/satoshis as applicable). Sent to the payout wallet address registered for that asset's network. Subject to the platform's minimum withdrawal amount and a network fee deducted from the requested amount before broadcast.

Get / list withdrawals

GET /api/v1/withdrawals/{'{id}'} (X-Api-Key or Bearer)

Response 200 OK

{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "asset": "Ton",
  "amountSmallestUnit": 12000000000,
  "destinationAddress": "EQDWPJ1jEzOlQfdFfn_XxmheIMDPhFKtSvzecTEozMJ7KDdY",
  "status": "Sent",
  "transactionHash": "a1b2c3...",
  "requestedAt": "2026-08-01T08:00:00Z",
  "processedAt": "2026-08-01T08:00:14Z",
  "failedReason": null
}

transactionHash is the on-chain transaction hash on whichever network asset is on. status: Pending, Processing, Sent, Confirmed, Failed (in which case your balance is automatically refunded - check failedReason). GET /api/v1/withdrawals?pageNumber=1&pageSize=20&status=Sent lists them with the same paginated envelope (status optional).

Webhooks

Register a URL to be notified when a payment is confirmed. Vaeloq signs every delivery so you can verify it actually came from Vaeloq.

PUT /api/v1/webhooks (X-Api-Key or Bearer)

Request

PUT /api/v1/webhooks
Content-Type: application/json

{ "url": "https://acmestore.example/webhooks/vaeloq" }

Response 200 OK

{
  "id": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
  "url": "https://acmestore.example/webhooks/vaeloq",
  "secretKey": "whsec_3f7a9c2e8b1d4f6a0c9e2b5d8f1a4c7e"
}

The secretKey is shown only this once - save it, you need it to verify incoming deliveries. Calling this again replaces your existing webhook URL and secret.

What Vaeloq sends

On a confirmed payment, Vaeloq sends a POST to your URL:

POST https://acmestore.example/webhooks/vaeloq
Content-Type: application/json
X-Vaeloq-Signature: sha256=<hex-encoded HMAC-SHA256 of the raw body>
X-Vaeloq-Timestamp: 1785566336

{
  "eventType": "payment.confirmed",
  "eventId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "timestamp": "2026-08-01T07:02:41.0000000Z",
  "data": {
    "paymentId": "d1b2c3e4-1234-4a5b-9c0d-abcdef123456",
    "orderReference": "order-1024",
    "asset": "Ton",
    "amountSmallestUnit": "12500000000",
    "feeAmountSmallestUnit": "312500000",
    "netCreditedSmallestUnit": "12187500000",
    "status": "Confirmed",
    "confirmedAt": "2026-08-01T07:02:41Z"
  }
}

amountSmallestUnit/feeAmountSmallestUnit/netCreditedSmallestUnit are sent as strings (not JSON numbers) since wei/lamport amounts on the 18-/9-decimal networks can exceed the precision a JSON number safely round-trips through in some languages/runtimes - parse them as a big integer.

Verifying the signature

Compute HMAC-SHA256(yourSecretKey, rawRequestBody) and compare it (in constant time) to the hex digest after sha256= in the X-Vaeloq-Signature header. Example in Node.js:

const crypto = require('crypto');

function isValidSignature(rawBody, signatureHeader, secretKey) {
  const expected = crypto.createHmac('sha256', secretKey).update(rawBody).digest('hex');
  const provided = signatureHeader.replace('sha256=', '');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(provided));
}

Respond with any 2xx status to acknowledge receipt. Non-2xx responses (or timeouts) are retried with backoff (roughly 1 min, 5 min, 30 min, 2 h, 12 h) up to 8 attempts before the delivery is abandoned - check delivery history below if you suspect a miss.

Webhook delivery history

GET /api/v1/webhooks/deliveries?pageNumber=1&pageSize=20 (X-Api-Key or Bearer)

Paginated list of every delivery attempt (event type, status, attempt count, last response code) for debugging your integration.

Support tickets

Open a support ticket from your account and reply to it, without leaving the API.

POST /api/merchant/support-tickets (Bearer token)

Request

POST /api/merchant/support-tickets
Content-Type: application/json

{
  "subject": "Withdrawal stuck in Processing",
  "body": "My withdrawal 7c9e6679-... has been Processing for 2 hours."
}

Response 200 OK

{ "id": "b3f1a2c4-5566-4d7e-8f90-1234567890ab" }

GET /api/merchant/support-tickets?pageNumber=1&pageSize=20&status=Open (Bearer token)

status is optional: Open, AwaitingMerchant, or Closed. Returns the paginated envelope described above.

GET /api/merchant/support-tickets/{'{id}'} (Bearer token)

Response 200 OK

{
  "id": "b3f1a2c4-5566-4d7e-8f90-1234567890ab",
  "subject": "Withdrawal stuck in Processing",
  "status": "AwaitingMerchant",
  "created": "2026-08-02T10:00:00Z",
  "messages": [
    { "id": "...", "body": "My withdrawal 7c9e6679-... has been Processing for 2 hours.", "isFromAdmin": false, "created": "2026-08-02T10:00:00Z" },
    { "id": "...", "body": "Thanks for reaching out - we're looking into it now.", "isFromAdmin": true, "created": "2026-08-02T10:15:00Z" }
  ]
}

POST /api/merchant/support-tickets/{'{id}'}/reply (Bearer token)

POST /api/merchant/support-tickets/b3f1a2c4-5566-4d7e-8f90-1234567890ab/reply
Content-Type: application/json

{ "body": "Thanks, still waiting - any update?" }

Response: 204 No Content. Replying to a Closed ticket reopens it. You'll receive an email whenever support replies.

Public payment page (for reference)

The paymentPageUrl returned from payment creation points to a hosted page your customer pays from directly - you generally don't need to call these yourself, but they're public (no authentication) if you want to build a custom checkout experience:

  • GET /api/v1/public/payments/{'{id}'} - amount, memo and central wallet address (TON/Solana) or deposit address (BSC/Ethereum/TRON/Bitcoin), expiry, status
  • GET /api/v1/public/payments/{'{id}'}/status - lightweight status poll, {'{'} "status": "Pending" {'}'}
  • GET /api/v1/public/payment-links/{'{id}'} - a universal payment link's network quotes, for building your own network-picker UI
  • POST /api/v1/public/payment-links/{'{id}'}/select-network - picks a network for a universal link and creates the underlying payment

Error responses

Validation and business-rule errors use a standard ProblemDetails shape:

{
  "type": "https://httpstatuses.io/400",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "Amount": ["'Amount' must be greater than 0."]
  }
}
StatusMeaning
400Validation error - see errors
401Missing/invalid API key or expired token
403Authenticated, but not allowed (e.g. account not yet KYC-verified)
404Resource not found, or not owned by your account
409Conflict (e.g. insufficient balance for a withdrawal)
An unhandled error has occurred. Reload 🗙