API · Documentation
API Documentation
Accept deposits, send withdrawals, create invoices and verify signed webhooks across USDT (BEP20/TRC20), BNB and BTC through a single REST API.
https://waltix.io/apiIntroduction
The Waltix API lets you accept and send crypto programmatically: create deposit addresses and invoices, initiate withdrawals, fetch fees and process signed webhooks. Every endpoint lives under a single base URL.
Base URL for all requests:
https://waltix.io/apiA signature is required for every public endpoint (GET, POST, etc.). The algorithm is described in the “Authentication” section.
Supported networks
USDT on BEP20 and TRC20, BNB (BEP20) and BTC. The full up-to-date list of symbol identifiers is returned by GET /symbol/.
Authentication & keys
Keys
| Key | Where to get it | Purpose |
|---|---|---|
| API key ID + secret | Dashboard → API keys | Signing API requests |
| callback_key | Dashboard → profile | Verifying webhooks |
An API key is created once: the secret is shown only at creation time. For webhooks use callback_key, not the API key secret.
Request headers
| Header | Description |
|---|---|
| X-API-Key-ID | Key UUID |
| X-Timestamp | Unix timestamp, seconds |
| X-Signature | HMAC-SHA256 hex |
Signing algorithm
- Build an object from params, body and path:
{
"params": "url_encoded_query_string",
"body": {},
"path": "/api/user/balance/"
}- params — query string as in the URL (search_query=foo, without ?)
- body — JSON request body; {} if there is no body
- path — full request path with the /api/ prefix, e.g. /api/user/balance/
- Serialize: json.dumps(data, separators=(",", ":"), sort_keys=True)
- Compute the signature:
signature = HMAC-SHA256(key=API_KEY_SECRET, message=timestamp + json_string)The timestamp must be within ±15 seconds of server time. If the key allowlist is empty, the first successful request records the current IP into the allowlist, and subsequent requests are accepted only from those IPs.
Example: GET balance
# params пустые, body {}
# path = /api/user/balance/
# data = {"body":{},"params":"","path":"/api/user/balance/"}
curl "https://waltix.io/api/user/balance/" \
-H "X-API-Key-ID: <UUID>" \
-H "X-Timestamp: 1718659200" \
-H "X-Signature: <hex>"Example: POST address
curl "https://waltix.io/api/user/address/" \
-X POST \
-H "Content-Type: application/json" \
-H "X-API-Key-ID: <UUID>" \
-H "X-Timestamp: 1718659200" \
-H "X-Signature: <hex>" \
-d '{"symbol_id":"USDTBEP20","callback_url":"https://example.com/cb","description":"shop"}'Python: generating the signature
import hashlib, hmac, json, time
def sign(key: str, timestamp: int, data: dict) -> str:
s = json.dumps(data, separators=(",", ":"), sort_keys=True)
return hmac.new(key.encode(), f"{timestamp}{s}".encode(), hashlib.sha256).hexdigest()
data = {"params": "", "body": {"symbol_id": "USDTBEP20"}, "path": "/api/user/address/"}
ts = int(time.time())
signature = sign(API_KEY_SECRET, ts, data)PHP: generating the signature
$data = ["params" => "", "body" => ["symbol_id" => "USDTBEP20"], "path" => "/api/user/address/"];
ksort($data);
$json = json_encode($data, JSON_UNESCAPED_SLASHES);
$signature = hash_hmac("sha256", time() . $json, $API_KEY_SECRET);Webhooks
A POST is delivered to your callback_url. The signature is verified with callback_key using the X-Signature and X-Timestamp headers. Allowed timestamp drift: ±15 seconds.
| Header | Description |
|---|---|
| X-Signature | HMAC-SHA256 hex |
| X-Timestamp | Unix timestamp |
Signing payload
{
"params": "",
"body": { }
}- params — query string of your endpoint
- body — parsed JSON body of the POST
- the path field is not used
- callback_key is used for the signature
Deposit / withdrawal webhook
The POST body carries the transaction JSON object (fields described in the relevant sections). For signature verification the object is placed into body, while params stays empty.
Invoice webhook
{
"params": "",
"body": {
"type": "invoice",
"id": "5ca38db1-00c5-4aba-91e5-6a57bed60aee",
"status": "executed"
}
}The POST body matches the signing object: params and body at the top level. Verify using the parsed request body as-is, without any extra wrapper.
Python: verifying a webhook
# Webhook депозита и вывода / Deposit & withdrawal webhook
body = await request.json()
data = {"params": request.url.query, "body": body}
verify(request.headers["X-Signature"], CALLBACK_KEY, int(request.headers["X-Timestamp"]), data)
# Webhook счёта / Invoice webhook
data = await request.json()
verify(request.headers["X-Signature"], CALLBACK_KEY, int(request.headers["X-Timestamp"]), data)def verify(signature: str, key: str, timestamp: int, data: dict) -> bool:
if abs(int(time.time()) - timestamp) > 15:
return False
expected = sign(key, timestamp, data)
return hmac.compare_digest(expected, signature)Webhook delivery
If your server responds with an error, the webhook is retried automatically. If your server already returned 2xx for the first webhook, no retry is sent when the status changes to executed. With AML or pending confirmations the first webhook may arrive with status pending. Query the current status via GET /user/transaction/.
Deposits
A deposit is registered after an on-chain transfer to an address obtained via the API. The blockchain scanner creates a transaction with type deposit.
- Create an address: POST /user/address/ or an invoice: POST /invoice/.
- The payer sends an on-chain transfer to the issued address.
- The scanner records the deposit. If callback_url is set, a webhook is sent.
Endpoints
- POST
/user/address/ - GET
/user/address/ - GET
/user/address/balance/{symbol_id}/ - POST
/invoice/ - GET
/invoice/ - GET
/invoice/my/{invoice_id}/ - GET
/invoice/{invoice_id}/transactions/ - GET
/symbol/ - GET
/user/transaction/ - GET
/user/transaction/{encoded_id}/
POST /user/address/ — request
| Field | Type | Description |
|---|---|---|
| symbol_id * | string | Symbol identifier: currency on a specific network, e.g. USDTBEP20, BNBBEP20. List: GET /symbol/ |
| callback_url | string | URL for the deposit webhook |
| description | string | Address label |
| external_id | string | Your ID |
Virtual symbols (USDT without a network) are not accepted. Specify a network-bound symbol.
POST /user/address/ — response
| Field | Description |
|---|---|
| id | Address ID |
| value | Wallet address |
| symbol_id | Symbol identifier |
| callback_url | Webhook URL |
| external_id | Your ID |
| created_at | Creation date |
POST /invoice/ — request
| Field | Type | Description |
|---|---|---|
| name * | string | Name |
| symbol_id | string | Symbol identifier for payment |
| target_value_fiat | decimal | Amount in fiat |
| target_currency_fiat | string | Fiat currency, default USD |
| target_value_symbol | decimal | Amount in crypto |
| deadline_at | datetime | Invoice deadline |
| callback_url | string | Invoice status webhook URL |
| external_id | string | Your ID |
| is_payer_fee | bool | Payer covers the fee |
| description | string | Description |
You must provide either target_value_fiat or target_value_symbol. Response: payment address in target_wallet.value, current status, and the payment link in pay_url.
GET /user/transaction/ — deposit filters
| Parameter | Description |
|---|---|
| type | deposit |
| symbol_id | Symbol identifier |
| external_id | Your ID |
| search_query | Search by symbol, hash, addresses |
Deposit webhook — body
| Field | Description |
|---|---|
| type | deposit |
| status | Transaction status |
| symbol_id | Symbol identifier |
| amount | Amount credited to balance |
| service_fee | Service fee |
| net_amount | On-chain transfer amount (amount + service_fee) |
| blockchain_data.hash | Tx hash |
| blockchain_data.from_address | Sender |
| blockchain_data.to_address | Recipient (your address) |
| external_id | Your ID from the address |
| invoice | Invoice data if the deposit is for an invoice |
| aml_order | AML check: status, data.level, data.risk_score (if enabled) |
| callback_order | Last webhook delivery status (last_status_code, last_response_data) |
{
"user_id": 1,
"type": "deposit",
"created_at": "2025-11-20T16:08:10.180830+00:00",
"symbol_id": "USDTBEP20",
"status": "executed",
"amount": "4.696700000000000000",
"service_fee": "0.803300000000000000",
"net_amount": "5.500000000000000000",
"blockchain_data": {
"hash": "0xabc...",
"from_address": "0x123",
"to_address": "0x456"
},
"external_id": null,
"callback_url": "https://example.com/cb"
}Deposit statuses (on-chain / invoice)
| status | Description |
|---|---|
| pending | Awaiting AML or network confirmations |
| executed | Credited to balance |
| dirty_lock | Address flagged dirty, deposit locked |
| cancelled | Cancelled |
If the AML check fails, the transaction status becomes risky. AML fields: aml_order.status (created, pending_check, completed, risky, failed, manual_completed), aml_order.data.level (undefined, none, low, medium, high, severe), aml_order.data.risk_score (0–100).
Invoice statuses
| status | Description |
|---|---|
| active | Awaiting payment |
| executed | Paid |
| expired | Expired |
| cancelled | Cancelled |
Deposits below min_deposit_amount are ignored.
Withdrawals
Endpoints
- POST
/user/balance/withdrawal/ - GET
/user/transaction/?type=withdrawal - GET
/user/transaction/{encoded_id}/ - GET
/symbol/
There is no separate GET /withdrawal/{id} — use the transaction API.
POST /user/balance/withdrawal/ — query
| Parameter | Description |
|---|---|
| totp_code | Email OTP. Not required when authenticating with an API key |
POST /user/balance/withdrawal/ — request
| Field | Type | Description |
|---|---|---|
| symbol_id * | string | Symbol identifier, e.g. USDTTRC20. List: GET /symbol/ |
| to_address * | string | Recipient address |
| amount * | decimal | Amount to recipient (net) |
| external_id | string | Your ID |
| callback_url | string | Webhook URL |
| comment | string | Comment (up to 255 chars) |
| extra.memo | string | Memo/tag (if the network supports it) |
| extra.fee_mode | string | Network fee priority for BTC (see below) |
The fee is charged on top of amount: amount + service_fee is deducted from the balance. With API key auth the withdrawal_via_api_* fees apply (see GET /setting/fees/). Rate limit: 5 requests per second.
BTC fee priority (extra.fee_mode)
| fee_mode | Description |
|---|---|
| fastestFee | Highest priority, fastest |
| economyFee | Economy mode |
| minimumFee | Minimum priority (server default) |
If fee_mode is omitted or unknown, the server default (economyFee) is used. Per-mode markup is available in GET /blockchain/ → settings.service_fee_markup_by_fee_mode for the BTC network. For networks other than BTC the field is ignored.
Errors
| detail | Reason |
|---|---|
| Insufficient balance | Not enough funds |
| Invalid address | Invalid address |
| Minimum withdrawal amount is … | Below minimum |
| TOTP code is required | totp_code required without an API key |
| MEMO doesn't supported on this chain! | Memo is not supported |
Withdrawal webhook — body
Sent once when the withdrawal transitions to executed. Transaction JSON object with type: withdrawal.
| Field | Description |
|---|---|
| amount | Transaction amount (recipient + fee) |
| service_fee | Fee |
| net_amount | Amount to recipient (amount - service_fee) |
| status | executed |
| blockchain_data.hash | Tx hash |
| to_address | Recipient address |
| memo | Memo, if any |
| external_id | Your ID |
| fee_mode | BTC fee priority, if specified |
Withdrawal statuses
Withdrawal status comes from the withdrawal lifecycle. AML statuses (risky etc.) are not used here, unlike deposits. Terminal states: executed, cancelled, failed.
| status | Description |
|---|---|
| compliance_approve_required | Awaiting manual approval |
| created | Queued |
| aml_order_pending | AML check |
| prepare_pending | Preparing the transaction |
| ready_to_transfer | Ready to transfer |
| transfer_lock | Broadcasting to the network |
| pending | Awaiting confirmations |
| swap_lock | Awaiting autoswap |
| executed | Executed |
| cancelled | Cancelled |
| failed | Failed |
Limits
| Parameter | Description |
|---|---|
| min_withdrawal_amount | Minimum per symbol and tariff |
| withdrawal_fee_amount | Fixed fee (UI / without API key) |
| withdrawal_fee_percentage | Percentage fee (UI / without API key) |
| withdrawal_via_api_fee_amount | Fixed fee for API key requests |
| withdrawal_via_api_fee_percentage | Percentage fee for API key requests |
| totp_code | Required without an API key (email OTP, 10 min) |
Repeating a request with the same external_id is not idempotent.
Fees & pricing
Endpoints
- GET
/setting/fees/ - GET
/setting/fees/my/
GET /setting/fees/ is available without a session, using an API key. The user_id and tariff_id query params are for admin viewing of specific overrides (usually not needed by integrators).
FeesSettingsDTO response fields
| Field | Description |
|---|---|
| symbol_id | Symbol identifier |
| user_id | Per-user override (null — global) |
| tariff_id | Per-tariff override (null — global) |
| min_deposit_amount | Minimum deposit amount |
| min_withdrawal_amount | Minimum withdrawal amount |
| deposit_fee_amount | Fixed deposit fee (UI) |
| deposit_fee_percentage | Deposit fee % (UI) |
| withdrawal_fee_amount | Fixed withdrawal fee (UI) |
| withdrawal_fee_percentage | Withdrawal fee % (UI) |
| deposit_via_api_fee_amount | Fixed deposit fee via API key |
| deposit_via_api_fee_percentage | Deposit fee % via API key |
| withdrawal_via_api_fee_amount | Fixed withdrawal fee via API key |
| withdrawal_via_api_fee_percentage | Withdrawal fee % via API key |
| swap_fee_amount | Fixed swap fee |
| swap_fee_percentage | Swap fee % |
| swap_from_min_amount | Min. “from” amount for swap |
| swap_to_min_amount | Min. “to” amount for swap |
Resolution priority: per-user override → tariff → global settings. For API key integration use the *_via_api_* fields. For dashboard operations use deposit_fee_* / withdrawal_fee_*.