Drop-in compatible API · v1

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.

API base URL
https://api.vexoragate.com
Payment pages
https://pay.vexoragate.com/pay/<uuid>
Networks
USDT TRC20 · USDT BEP20 · TRX

Migrate from your current crypto gateway in minutes

  1. Create a merchant in Business, wait for moderation, then generate the payment key (and a payout key if you send payouts) under API keys.
  2. Replace your current gateway's API base URL with https://api.vexoragate.com, then set the merchant UUID and the keys.
  3. Webhook IP allowlist: replace your previous gateway's sender IP with 2.27.154.167. Signature verification code stays the same.
IntegrationBase URLWhat to change
Node SDKconfigurablepass { baseUrl: 'https://api.vexoragate.com' } in the client options
PHP SDKconfigurablepass the base URL in Config
Python SDKconfigurableClientOptions(base_url="https://api.vexoragate.com")
Go SDKconfigurableConfig.BaseURL
WooCommerce modulehard-codededit src/RequestBuilder.php: const API_URL = "https://api.vexoragate.com/"; (re-apply after plugin updates)
WHMCS / OpenCart / PrestaShop / XenForo / Invisionhard-codedsearch 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
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.

MethodPathKeyDescription · request · result
POST/v1/paymentpayment
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/infopayment
Invoice by uuid or order_id (order_id wins if both).
Body: uuid | order_id
result: payment object
POST/v1/payment/listpayment
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/servicespayment/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/qrpayment
QR code of an invoice address.
Body: merchant_payment_uuid
result: {image}
POST/v1/payment/resendpayment
Resend the last webhook of a final invoice (max 10).
Body: uuid | order_id
result: []
POST/v1/payment/refundpayout
Refund a paid invoice (manual payout queue).
Body: uuid | order_id, address*, is_subtract*, amount
result: []
POST/v1/payment/discount/listpayment
Discount/surcharge per payment method.
result: [{currency, network, discount}]
POST/v1/payment/discount/setpayment
Set discount (+) or surcharge (−) for a method.
Body: currency*, network*, discount_percent* (−99..100)
result: {currency, network, discount}
POST/v1/payment/aml-linkspayment
AML questionnaire links (always empty: no AML locks).
Body: uuid | order_id
result: []
POST/v1/walletpayment
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/qrpayment
QR code of a static wallet address.
Body: wallet_address_uuid
result: {image}
POST/v1/wallet/block-addresspayment
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-refundpayment
Refund funds held on a blocked wallet (once).
Body: uuid | order_id, address*
result: {commission, amount}
POST/v1/test-webhook/paymentpayment
Send a signed test webhook.
Body: url_callback*, currency*, network*, status*, uuid, order_id
result: []
POST/v1/test-webhook/walletpayment
Send a signed test static-wallet webhook.
Body: same as above
result: []
POST/v1/test-webhook/payoutpayment
Send a signed test payout webhook.
Body: same as above
result: []
POST/v1/balancepayment/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.

MethodPathKeyDescription · request · result
POST/v1/payoutpayout
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/infopayout
Payout by uuid or order_id.
Body: uuid | order_id
result: payout object
POST/v1/payout/listpayout
Payout history (cursor pagination).
Body: date_from, date_to
result: {merchant_uuid, items, paginate}
POST/v1/payout/servicespayment/payout
Payout methods, limits and fixed fees.
result: same shape as payment services
POST/v1/payout/calcpayout
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-personalpayout
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-businesspayout
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

Statuses

Payment statusFinalMeaning
checknoWaiting for the payment
wrong_amount_waitingnoUnderpaid; payer may still top up until expiry
paidyesPaid exactly (or within accuracy_payment_percent)
paid_overyesPaid more than required
wrong_amountyesUnderpaid and expired (or is_payment_multiple=false)
cancelyesExpired without payment
refund_processnoRefund queued for payout
refund_paidyesRefund sent and verified on chain
refund_failyesRefund 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 data

Limits & security