AvaSettle

AvaSettle API

Accept crypto on any site. Create an invoice, send your customer to the payment link, and mark the order paid when the signed webhook arrives. Every payment is credited to your balance in USDT (BEP20).

Overview

The API speaks JSON over HTTPS. The base URL is https://pay.avasettle.com. Every request is signed with your API secret, so it can't be changed or replayed on the way.

  • Hosted invoice (recommended): you create an invoice and redirect the customer. AvaSettle shows coins, addresses and QR codes, follows the blockchain and sends the customer back to your success_url.
  • Direct payment: you pick the coin yourself and show the address in your own page.

Get your API key, API secret and webhook secret in your AvaSettle account under API keys and Payment notifications. Keys can be limited to your server's IP addresses.

Authentication

Send these four headers with every request:

HeaderTypeValue
X-AVS-KeystringYour public API key, pk_ + 32 hex characters.
X-AVS-TimestampintegerUnix time in seconds. Requests more than 5 minutes off are refused.
X-AVS-Noncestring16 to 64 letters or digits, new for every request.
X-AVS-SignaturehexHMAC-SHA256 of the string below, keyed with your API secret.
Signed string
{timestamp}.{nonce}.{METHOD}.{path with query}.{raw body}

The path includes the query string (for example /v1/payments?order_id=1001). For GET requests the body is empty, so the string ends with a dot. Sign the exact bytes you send.

PHP
<?php
function avasettle(string $method, string $path, ?array $body = null): array
{
    $base   = 'https://pay.avasettle.com';
    $key    = getenv('AVASETTLE_KEY');      // pk_...
    $secret = getenv('AVASETTLE_SECRET');   // sk_...
    $raw    = $body === null ? '' : json_encode($body, JSON_UNESCAPED_SLASHES);
    $ts     = (string) time();
    $nonce  = bin2hex(random_bytes(12));
    $sig    = hash_hmac('sha256', "$ts.$nonce.$method.$path.$raw", $secret);

    $ch = curl_init($base . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST  => $method,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_POSTFIELDS     => $body === null ? null : $raw,
        CURLOPT_HTTPHEADER     => [
            'Content-Type: application/json',
            "X-AVS-Key: $key",
            "X-AVS-Timestamp: $ts",
            "X-AVS-Nonce: $nonce",
            "X-AVS-Signature: $sig",
        ],
    ]);
    $res = json_decode((string) curl_exec($ch), true);
    curl_close($ch);
    return $res;
}
Node.js
import crypto from 'node:crypto';

export async function avasettle(method, path, body) {
  const raw = body === undefined ? '' : JSON.stringify(body);
  const ts = Math.floor(Date.now() / 1000).toString();
  const nonce = crypto.randomBytes(12).toString('hex');
  const sig = crypto.createHmac('sha256', process.env.AVASETTLE_SECRET)
    .update(`${ts}.${nonce}.${method}.${path}.${raw}`).digest('hex');

  const res = await fetch('https://pay.avasettle.com' + path, {
    method,
    body: body === undefined ? undefined : raw,
    headers: {
      'content-type': 'application/json',
      'x-avs-key': process.env.AVASETTLE_KEY,
      'x-avs-timestamp': ts,
      'x-avs-nonce': nonce,
      'x-avs-signature': sig,
    },
  });
  return res.json();
}
Python
import hashlib, hmac, json, os, secrets, time, urllib.request

def avasettle(method, path, body=None):
    raw = '' if body is None else json.dumps(body, separators=(',', ':'))
    ts, nonce = str(int(time.time())), secrets.token_hex(12)
    sig = hmac.new(os.environ['AVASETTLE_SECRET'].encode(),
                   f'{ts}.{nonce}.{method}.{path}.{raw}'.encode(), hashlib.sha256).hexdigest()
    req = urllib.request.Request('https://pay.avasettle.com' + path, method=method,
                                 data=None if body is None else raw.encode(), headers={
        'Content-Type': 'application/json', 'X-AVS-Key': os.environ['AVASETTLE_KEY'],
        'X-AVS-Timestamp': ts, 'X-AVS-Nonce': nonce, 'X-AVS-Signature': sig})
    with urllib.request.urlopen(req) as r:
        return json.load(r)
Shell
KEY=pk_your_key; SECRET=sk_your_secret
BODY='{"order_id":"1001","amount":"49.90","currency":"USD"}'
TS=$(date +%s); NONCE=$(openssl rand -hex 12)
SIG=$(printf '%s' "$TS.$NONCE.POST./v1/invoices.$BODY" | openssl dgst -sha256 -hmac "$SECRET" -hex | sed 's/^.* //')

curl -s https://pay.avasettle.com/v1/invoices \
  -H "Content-Type: application/json" -H "X-AVS-Key: $KEY" \
  -H "X-AVS-Timestamp: $TS" -H "X-AVS-Nonce: $NONCE" -H "X-AVS-Signature: $SIG" \
  -d "$BODY"

Quick start

  1. Create an invoice when the customer chooses crypto at checkout: POST /v1/invoices.
  2. Redirect the customer to invoice.url. They choose a coin and pay.
  3. Receive the webhook payment.paid, check its signature, read the payment again with GET /v1/payments/{id} and mark the order paid.
  4. Also check on return: when the customer lands on your success_url, call GET /v1/payments?order_id=…. This covers the rare case where your webhook endpoint was down.

Create an invoice

POST/v1/invoices

Creates a hosted payment page for one order. Calling it again with the same order_id and amount while the invoice is open returns the same invoice, so retries are safe.

FieldTypeDescription
order_idstring, requiredYour order number. Letters, digits and _ . : -, up to 120 characters. Use a prefix if several stores share one account.
amountdecimal string, requiredAmount to charge, for example "49.90". Up to 8 decimals.
currencystring, requiredThe amount's currency: USD, EUR and other fiat codes.
descriptionstringShown to the customer, up to 200 characters.
success_urlhttps URLWhere the customer goes after paying.
cancel_urlhttps URLWhere the "back to store" link points.
expires_in_minutesinteger15 to 10080. Default: the payment window set in your account.
metadataobjectYour own data, returned on every payment and webhook. Up to 4 KB.
Request
{
  "order_id": "1001",
  "amount": "49.90",
  "currency": "USD",
  "description": "Order #1001 at Example Store",
  "success_url": "https://store.example/checkout/thanks?order=1001",
  "cancel_url": "https://store.example/cart",
  "expires_in_minutes": 60,
  "metadata": { "customer": 42 }
}
Response 201
{
  "invoice": {
    "id": "inv_3f0c1b8e2a9d4c7f8e1a2b3c4d5e6f70",
    "url": "https://pay.avasettle.com/i/inv_3f0c1b8e2a9d4c7f8e1a2b3c4d5e6f70",
    "order_id": "1001",
    "amount": "49.90",
    "currency": "USD",
    "description": "Order #1001 at Example Store",
    "status": "open",
    "success_url": "https://store.example/checkout/thanks?order=1001",
    "cancel_url": "https://store.example/cart",
    "metadata": { "customer": 42 },
    "created_at": "2026-10-08T09:00:00.000Z",
    "expires_at": "2026-10-08T10:00:00.000Z",
    "payment": null,
    "payments": []
  }
}

Get an invoice

GET/v1/invoices/{id}

Returns the invoice with its current status: open, processing (a payment arrived and is confirming), paid, partial or expired. payment is the latest payment and payments lists every coin the customer tried.

Available coins

GET/v1/options

Coins and networks switched on in your account, for building your own coin picker.

Response 200
{"options":{"USDT":{"name":"Tether","networks":[{"code":"TRON","label":"TRON (TRC20)","token":true},{"code":"BSC","label":"BNB Smart Chain (BEP20)","token":true}]},"BTC":{"name":"Bitcoin","networks":[{"code":"BTC","label":"Bitcoin","token":false}]}}}

Create a payment

POST/v1/payments

For your own payment page: AvaSettle returns a fresh deposit address and the exact crypto amount for the coin you choose. Show address, amount and qr to the customer.

FieldTypeDescription
order_idstring, requiredAs above.
amountdecimal string, requiredFiat amount.
currencystring, requiredFiat currency.
coinstring, requiredFor example BTC, USDT, XMR.
networkstring, requiredA network code from /v1/options, for example TRON or BSC.
metadataobjectYour own data.
Request
{"order_id":"1001","amount":"49.90","currency":"USD","coin":"USDT","network":"TRON"}

The response is {"payment": {…}} with the payment object below. The quoted amount is valid until expires_at.

Get a payment

GET/v1/payments/{id}

Add ?refresh=1 to check the blockchain right now instead of waiting for the next scan (at most once every 10 seconds per payment). If the network is slow the response carries "warning": "chain_unreachable" and the last known state.

Payments for an order

GET/v1/payments?order_id=1001

Every payment created for that order, newest first, as {"payments": [ … ]}.

The payment object

JSON
{
  "id": "pay_6c2f9a1e0b7d4e3f8a9b0c1d",
  "order_id": "1001",
  "status": "paid",
  "coin": "USDT",
  "network": "TRON",
  "network_label": "TRON (TRC20)",
  "address": "TQ5n8kWmRbV3yLx2Jd9HfPc7Gs4Ae6Tu1Z",
  "memo": null,
  "amount": "49.90",
  "received": "49.90",
  "remaining": "0",
  "confirmations": 20,
  "required_confirmations": 20,
  "fiat_amount": "49.90",
  "fiat_currency": "USD",
  "paid_fiat": "49.90",
  "txid": "9f2c...e41a",
  "tx_url": "https://tronscan.org/#/transaction/9f2c...e41a",
  "qr": "TQ5n8kWmRbV3yLx2Jd9HfPc7Gs4Ae6Tu1Z",
  "metadata": { "checkout": "inv_3f0c1b8e2a9d4c7f8e1a2b3c4d5e6f70" },
  "created_at": "2026-10-08T09:01:12.000Z",
  "expires_at": "2026-10-08T09:31:12.000Z",
  "final_at": "2026-10-08T09:05:40.000Z"
}
FieldMeaning
amountCrypto amount the customer has to send.
received / remainingWhat has arrived so far and what is still missing.
fiat_amount, fiat_currencyThe amount you asked for. Compare these with your order before marking it paid.
paid_fiatValue received, in your currency, once the payment is final.
memoRequired destination tag or memo on some networks. Show it when it is not null.
qrText for a QR code (a wallet URI when the network supports one).
txid, tx_urlThe customer's transaction and a block explorer link.

Payment statuses

StatusMeaning
pendingWaiting for the customer to send.
confirmingSeen on the blockchain, waiting for confirmations.
underpaidLess than the amount arrived. The customer can send the rest to the same address.
paidFinal. The full amount (within your tolerance) is confirmed. Fulfil the order.
partialFinal, but short. Decide yourself: paid_fiat tells you how much arrived.
expiredNothing arrived before the quote ran out.

paid and partial never change again. Payments that arrive late are still followed and credited.

Webhooks

Set your endpoint in your account under Payment notifications. AvaSettle sends a POST for each event:

EventWhen
payment.detectedThe transaction is on the blockchain.
payment.underpaidLess than the amount arrived.
payment.paidFinal and complete.
payment.partialFinal and short.
payment.expiredNothing arrived in time.
payment.testSent by the "Send test" button.
Request
POST /your/webhook
X-AVS-Event: payment.paid
X-AVS-Delivery: 1842
X-AVS-Timestamp: 1791450340
X-AVS-Signature: 5b1f…(64 hex chars)

{
  "event": "payment.paid",
  "created_at": "2026-10-08T09:05:40.000Z",
  "data": { "id": "pay_6c2f9a1e0b7d4e3f8a9b0c1d", "order_id": "1001", "status": "paid", "...": "the payment object" }
}

Verify X-AVS-Signature = hex(HMAC-SHA256(webhook secret, {timestamp}.{raw body})) and refuse timestamps older than 5 minutes. Then read the payment again with your API key and act on that copy. Answer with any 2xx status. Failed deliveries are retried at the interval and number of times set in your account.

PHP
<?php
$raw = file_get_contents('php://input');
$ts  = $_SERVER['HTTP_X_AVS_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_AVS_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $ts . '.' . $raw, getenv('AVASETTLE_WEBHOOK_SECRET'));

if (!ctype_digit($ts) || abs(time() - (int) $ts) > 300 || !hash_equals($expected, $sig)) {
    http_response_code(401);
    exit;
}
$event = json_decode($raw, true);
if ($event['event'] === 'payment.test') exit('ok');

// Never trust the body alone: read the payment again with your API key.
$payment = avasettle('GET', '/v1/payments/' . $event['data']['id'])['payment'];
if ($payment['status'] === 'paid'
    && $payment['fiat_amount'] === $order->total            // the amount you asked for
    && $payment['fiat_currency'] === $order->currency) {
    $order->markPaid($payment['txid']);
}
echo 'ok';
Node.js (Express)
import crypto from 'node:crypto';

app.post('/avasettle/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const ts = req.get('x-avs-timestamp') ?? '';
  const expected = crypto.createHmac('sha256', process.env.AVASETTLE_WEBHOOK_SECRET)
    .update(`${ts}.${req.body}`).digest('hex');
  const sig = req.get('x-avs-signature') ?? '';
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) <= 300;
  if (!fresh || sig.length !== 64 || !crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) return res.sendStatus(401);

  const event = JSON.parse(req.body);
  if (event.event !== 'payment.test') {
    const { payment } = await avasettle('GET', `/v1/payments/${event.data.id}`);
    if (payment.status === 'paid') await markOrderPaid(payment.order_id, payment);
  }
  res.send('ok');
});

Errors and limits

Error response
{"error":{"code":"invalid_request","message":"Invalid or missing field: amount (decimal string)"}}
HTTPCodeMeaning
400invalid_requestA field is missing or has the wrong format.
401unauthorizedMissing headers, bad signature, old timestamp, reused nonce or revoked key.
403forbiddenThe request came from an IP address not allowed for this key.
404not_foundNo such invoice or payment in your account.
409variesThe invoice is already paid, funded or expired.
429rate_limitedMore than 300 requests a minute for one key.

Plugins and libraries

Ready-made modules for WHMCS, WooCommerce, PrestaShop, OpenCart, Magento 2 and Drupal Commerce are on the integrations page. Each one includes AvaSettleApi.php, a small PHP client you can reuse in your own code.

Updated