VexoraGate merchant API
The VexoraGate API is a drop-in compatible crypto payment API: the same paths, request fields, request signing, response envelope, statuses and webhook format your integration already uses. To migrate from your current crypto gateway (its SDKs, WooCommerce/WHMCS/OpenCart modules or your own code), change the base URL and keys — nothing else.
Migrate from your current crypto gateway in minutes
- Create a merchant in Business, wait for moderation, then generate the payment key (and a payout key if you send payouts) under API keys.
- Replace your current gateway's API base URL with https://api.vexoragate.com, then set the merchant UUID and the keys.
- Webhook IP allowlist: replace your previous gateway's sender IP with 2.27.154.167. Signature verification code stays the same.
| Integration | Base URL | What to change |
|---|---|---|
| Node SDK | configurable | pass { baseUrl: 'https://api.vexoragate.com' } in the client options |
| PHP SDK | configurable | pass the base URL in Config |
| Python SDK | configurable | ClientOptions(base_url="https://api.vexoragate.com") |
| Go SDK | configurable | Config.BaseURL |
| WooCommerce module | hard-coded | edit src/RequestBuilder.php: const API_URL = "https://api.vexoragate.com/"; (re-apply after plugin updates) |
| WHMCS / OpenCart / PrestaShop / XenForo / Invision | hard-coded | search the module for the old API host and replace it with https://api.vexoragate.com |
| Your own code | — | replace the host; signing and parsing stay identical |
Authentication & signature
Every request is a POST with a JSON body and two headers:
merchant: <your merchant UUID> sign: md5( base64_encode(<raw request body>) + API_KEY ) Content-Type: application/json
- Use the payment key for payment, wallet, test-webhook and discount methods, the payout key for payouts, refunds and transfers.
- No parameters: send an empty body and sign the empty string — md5(base64_encode('') . KEY). {} and [] bodies are accepted too.
- Sign exactly the bytes you send. PHP's json_encode escapes / as \/; both forms are accepted as long as signature and body match.
- Optional per-key IP allowlists, per-merchant rate limits (HTTP 429) and idempotent order_id protect against replays of a captured request.
curl https://api.vexoragate.com/v1/payment \
-X POST -H 'merchant: YOUR_MERCHANT_UUID' -H 'sign: YOUR_SIGNATURE' -H 'Content-Type: application/json' \
-d '{"amount":"15","currency":"USD","order_id":"order-1001"}'Success: {"state":0,"result":{...}}. Responses use the standard field names and value types of compatible gateways (amounts are strings, expired_at is a Unix timestamp, network codes tron/bsc).
Payment API
Currencies: invoices in USD (1 USD = 1 USDT), USDT or TRX. Payer methods: USDT on tron and bsc, TRX on tron (USD → TRX uses a rate locked at invoice creation from two independent price sources). Each invoice gets its own address. Without network the payer chooses on the payment page. Late payments after expiry are still credited and reported by webhook.
| Method | Path | Key | Description · request · result |
|---|---|---|---|
| POST | /v1/payment | payment | Create an invoice (idempotent per order_id). Body: amount*, currency* (USD|USDT|TRX), order_id*, network (tron|bsc), url_return, url_success, url_callback, is_payment_multiple, lifetime (300–43200), to_currency, subtract, accuracy_payment_percent, additional_data, currencies[], except_currencies[], course_source, from_referral_code, discount_percent, is_refresh, payer_email, theme result: payment object + address_qr_code |
| POST | /v1/payment/info | payment | Invoice by uuid or order_id (order_id wins if both). Body: uuid | order_id result: payment object |
| POST | /v1/payment/list | payment | Invoice history, 15 per page, cursor pagination (?cursor=). Body: date_from, date_to (YYYY-MM-DD H:mm:ss) result: {items, paginate{count, hasPages, nextCursor, previousCursor, perPage}} |
| POST | /v1/payment/services | payment/payout | Supported currencies, networks, limits and your fee. result: [{network, currency, is_available, limit{min_amount,max_amount}, commission{fee_amount,percent}}] |
| POST | /v1/payment/qr | payment | QR code of an invoice address. Body: merchant_payment_uuid result: {image} |
| POST | /v1/payment/resend | payment | Resend the last webhook of a final invoice (max 10). Body: uuid | order_id result: [] |
| POST | /v1/payment/refund | payout | Refund a paid invoice (manual payout queue). Body: uuid | order_id, address*, is_subtract*, amount result: [] |
| POST | /v1/payment/discount/list | payment | Discount/surcharge per payment method. result: [{currency, network, discount}] |
| POST | /v1/payment/discount/set | payment | Set discount (+) or surcharge (−) for a method. Body: currency*, network*, discount_percent* (−99..100) result: {currency, network, discount} |
| POST | /v1/payment/aml-links | payment | AML questionnaire links (always empty: no AML locks). Body: uuid | order_id result: [] |
| POST | /v1/wallet | payment | Create a static wallet for a customer/order (idempotent). Body: currency*, network*, order_id*, url_callback result: {wallet_uuid, uuid, order_id, address, network, currency, url} |
| POST | /v1/wallet/qr | payment | QR code of a static wallet address. Body: wallet_address_uuid result: {image} |
| POST | /v1/wallet/block-address | payment | Block a static wallet; later payments are held, not credited. Body: uuid | order_id, is_force_refund result: {uuid, status} |
| POST | /v1/wallet/blocked-address-refund | payment | Refund funds held on a blocked wallet (once). Body: uuid | order_id, address* result: {commission, amount} |
| POST | /v1/test-webhook/payment | payment | Send a signed test webhook. Body: url_callback*, currency*, network*, status*, uuid, order_id result: [] |
| POST | /v1/test-webhook/wallet | payment | Send a signed test static-wallet webhook. Body: same as above result: [] |
| POST | /v1/test-webhook/payout | payment | Send a signed test payout webhook. Body: same as above result: [] |
| POST | /v1/balance | payment/payout | Business and personal balances. result: [{balance:{merchant:[{uuid,balance,currency_code}], user:[…]}}] |
| GET | /v1/exchange-rate/{currency}/list | — | Exchange rates (USD, USDT, TRX). Public. result: [{from, to, course}] |
Payment object
{"state":0,"result":{"uuid":"4b605bbd-…","order_id":"order-1001","amount":"15.00","payment_amount":null,"payment_amount_usd":"0.00",
"payer_amount":"15.00","payer_amount_exchange_rate":"1.00000000","discount_percent":0,"discount":"0.00","payer_currency":"USDT",
"currency":"USD","comments":null,"merchant_amount":"14.85000000","network":"tron","address":"TQ…","from":null,"txid":null,
"payment_status":"check","url":"https://pay.vexoragate.com/pay/4b605bbd-…","expired_at":1792058400,"status":"check","is_final":false,
"additional_data":null,"created_at":"2026-10-31T13:00:00+00:00","updated_at":"2026-10-31T13:00:00+00:00",
"commission":"0.15000000","address_qr_code":"data:image/png;base64,…"}}Payout API
Payouts are paid from your business balance and always reviewed manually: a new payout has status process, becomes check when our operator broadcasts it and paid once the transaction is verified on chain (or fail if rejected — funds return to your balance). Generating a new payout key blocks payouts for 24 hours.
| Method | Path | Key | Description · request · result |
|---|---|---|---|
| POST | /v1/payout | payout | Create a payout from the business balance. Goes to the manual review queue (status process). Body: amount*, currency*, order_id*, address*, is_subtract*, network*, url_callback, to_currency, from_currency, course_source, priority, memo result: {uuid, amount, currency, network, address, txid, status, is_final, balance, payer_currency, payer_amount, commission, merchant_amount} |
| POST | /v1/payout/info | payout | Payout by uuid or order_id. Body: uuid | order_id result: payout object |
| POST | /v1/payout/list | payout | Payout history (cursor pagination). Body: date_from, date_to result: {merchant_uuid, items, paginate} |
| POST | /v1/payout/services | payment/payout | Payout methods, limits and fixed fees. result: same shape as payment services |
| POST | /v1/payout/calc | payout | Fee and amounts before paying out (also /v1/payout/calculate). Body: amount*, address*, currency*, network, is_subtract* result: {commission, merchant_amount, payout_amount} |
| POST | /v1/transfer/to-personal | payout | Business balance → your personal wallet (instant). Body: amount*, currency* result: {user_wallet_transaction_uuid, user_wallet_balance, merchant_transaction_uuid, merchant_balance} |
| POST | /v1/transfer/to-business | payout | Personal wallet → business balance (instant). Body: amount*, currency* result: same as above |
Webhooks
On every status change we POST JSON to url_callback (invoice / wallet / payout, or the merchant default). Body and signature are byte-compatible with what existing integrations expect: the payload is PHP-encoded (/ escaped as \/) and sign is the last field.
{"type":"payment","uuid":"0b9c5c3e-...","order_id":"order-1001","amount":"15.00","payment_amount":"15.00000000",
"payment_amount_usd":"15.00","merchant_amount":"14.85000000","commission":"0.15000000","is_final":true,"status":"paid",
"from":"TXyz...","wallet_address_uuid":null,"network":"tron","currency":"USD","payer_currency":"USDT",
"additional_data":null,"txid":"9f2c...","sign":"8d3f0c..."}sign = md5( base64_encode( json_encode($data_without_sign, JSON_UNESCAPED_UNICODE) ) . API_KEY ) // payment & wallet webhooks: payment key · payout webhooks: payout key · test webhooks: payment key
- Respond with HTTP 2xx. Otherwise we retry up to 10 times (after 1 min, 5 min, 15 min, 30 min, 1 h, 2 h, 4 h, 8 h, 12 h).
- Allow our sender IP 2.27.154.167 and always verify the signature. Treat webhooks idempotently (order_id + status).
- Every delivery is logged in Business → Webhooks; resend with /v1/payment/resend or from the dashboard.
- Callback URLs must be public http(s) addresses; private, loopback and link-local targets are refused, redirects are not followed, timeout 10 s.
Statuses
| Payment status | Final | Meaning |
|---|---|---|
| check | no | Waiting for the payment |
| wrong_amount_waiting | no | Underpaid; payer may still top up until expiry |
| paid | yes | Paid exactly (or within accuracy_payment_percent) |
| paid_over | yes | Paid more than required |
| wrong_amount | yes | Underpaid and expired (or is_payment_multiple=false) |
| cancel | yes | Expired without payment |
| refund_process | no | Refund queued for payout |
| refund_paid | yes | Refund sent and verified on chain |
| refund_fail | yes | Refund rejected |
| confirm_check, process, fail, system_fail, locked | — | Defined for compatibility; not emitted |
Payout statuses: process (in review), check (sent, verifying), paid, fail, cancel. Static wallets: active, blocked.
Errors
HTTP 422 {"state":1,"errors":{"amount":["validation.required"],"order_id":["validation.alpha_dash"]}}
HTTP 400 {"state":1,"message":"The currency was not found"}
HTTP 401 {"state":1,"message":"Unauthorized"} // unknown merchant or bad sign
HTTP 429 {"state":1,"message":"Too Many Attempts."}
HTTP 500 {"message":"Server error, #1","code":500,"error":null}Messages follow the compatible API exactly: The currency was not found, The network was not found, The service was not found, Not found service to_currency, Error convert to_currency, Minimum amount 1 USDT, Maximum amount …, You are forbidden, Payment not found, Notification not found, Too much resend, Not enough funds, The terminal was not found, Refunds are made only for completed payments, You can withdraw only once, Nothing to withdraw.
Code examples
Node.js
// Node.js 18+ — no dependencies
import crypto from "node:crypto";
const BASE = "https://api.vexoragate.com";
const MERCHANT = process.env.VEXORA_MERCHANT_UUID;
const PAYMENT_KEY = process.env.VEXORA_PAYMENT_KEY;
const sign = (body, key) => crypto.createHash("md5").update(Buffer.from(body).toString("base64") + key).digest("hex");
async function call(path, params, key = PAYMENT_KEY) {
const body = params ? JSON.stringify(params) : ""; // sign exactly the bytes you send
const res = await fetch(BASE + path, {
method: "POST",
headers: { merchant: MERCHANT, sign: sign(body, key), "Content-Type": "application/json" },
body: body || undefined,
});
const json = await res.json();
if (json.state !== 0) throw new Error(json.message ?? JSON.stringify(json.errors));
return json.result;
}
const invoice = await call("/v1/payment", { amount: "15", currency: "USD", order_id: "order-1001", url_callback: "https://shop.example/vexora/webhook" });
console.log(invoice.url, invoice.payment_status);
// Webhook handler (Express): verify the signature on the RAW body
app.post("/vexora/webhook", express.raw({ type: "*/*" }), (req, res) => {
const raw = req.body.toString("utf8");
const data = JSON.parse(raw);
const stripped = raw.replace(new RegExp(',?"sign":"' + data.sign + '"'), "");
if (sign(stripped, PAYMENT_KEY) !== data.sign) return res.status(400).end();
// alternative: JSON.stringify(dataWithoutSign).replace(/\//g, "\\/")
if (data.status === "paid" || data.status === "paid_over") { /* deliver order data.order_id once */ }
res.status(200).end();
});PHP
<?php
const BASE = 'https://api.vexoragate.com';
$merchant = getenv('VEXORA_MERCHANT_UUID');
$paymentKey = getenv('VEXORA_PAYMENT_KEY');
function vexora(string $path, array $params, string $merchant, string $key): array {
$body = $params ? json_encode($params, JSON_UNESCAPED_UNICODE) : '';
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['merchant: ' . $merchant, 'sign: ' . md5(base64_encode($body) . $key), 'Content-Type: application/json'],
]);
$res = json_decode(curl_exec($ch), true);
if (($res['state'] ?? 1) !== 0) throw new RuntimeException($res['message'] ?? json_encode($res['errors'] ?? $res));
return $res['result'];
}
$invoice = vexora('/v1/payment', ['amount' => '15', 'currency' => 'USD', 'order_id' => 'order-1001',
'url_callback' => 'https://shop.example/vexora/webhook'], $merchant, $paymentKey);
header('Location: ' . $invoice['url']);
// webhook.php — signature verification
$data = json_decode(file_get_contents('php://input'), true);
$sign = $data['sign']; unset($data['sign']);
$hash = md5(base64_encode(json_encode($data, JSON_UNESCAPED_UNICODE)) . $paymentKey);
if (!hash_equals($hash, $sign)) { http_response_code(400); exit; }
if (in_array($data['status'], ['paid', 'paid_over'], true)) { /* mark order $data['order_id'] as paid */ }
http_response_code(200);Python
import base64, hashlib, json, os, re, requests
BASE = "https://api.vexoragate.com"
MERCHANT = os.environ["VEXORA_MERCHANT_UUID"]
PAYMENT_KEY = os.environ["VEXORA_PAYMENT_KEY"]
def sign(body: str, key: str) -> str:
return hashlib.md5((base64.b64encode(body.encode()).decode() + key).encode()).hexdigest()
def call(path: str, params: dict | None, key: str = PAYMENT_KEY):
body = json.dumps(params, separators=(",", ":")) if params else ""
r = requests.post(BASE + path, data=body.encode(), timeout=30,
headers={"merchant": MERCHANT, "sign": sign(body, key), "Content-Type": "application/json"})
data = r.json()
if data.get("state") != 0:
raise RuntimeError(data.get("message") or data.get("errors"))
return data["result"]
invoice = call("/v1/payment", {"amount": "15", "currency": "USD", "order_id": "order-1001"})
print(invoice["url"])
# Webhook (Flask): hash the raw body without the "sign" field
def verify(raw: bytes) -> dict:
body = raw.decode()
data = json.loads(body)
stripped = re.sub(r',?"sign"\s*:\s*"' + data["sign"] + '"', "", body, count=1)
if sign(stripped, PAYMENT_KEY) != data["sign"]:
raise ValueError("bad signature")
return dataLimits & security
- Invoice lifetime 300–43 200 s (default 3 600). Default processing fee 2% (your own rate is shown in /v1/payment/services); the subtract field (0–100) moves that share of the fee to the payer.
- API keys are shown once and stored encrypted; regenerate them any time. Private keys of payment addresses never touch our servers.
- Not available yet: automatic conversion (convert), recurring payments, referral programme, coins other than USDT/TRX.