Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.xpaylabs.com/llms.txt

Use this file to discover all available pages before exploring further.

This guide walks through the complete flow of accepting a crypto payment using XPayLabs — from creating a collection order to confirming the on-chain payment via webhook.

The Payment Flow

  1. Your server creates a collection order via the API.
  2. The gateway returns a unique deposit address and checkout URL.
  3. Your customer sends USDT (or another supported token) to the deposit address.
  4. The blockchain scanner detects the transaction.
  5. The gateway sends a webhook to your server when payment is confirmed.

Step 1: Create a Collection Order

Call POST /v1/order/createCollection with the payment details:
import crypto from "crypto";

const GATEWAY_URL = "http://your-gateway:3010";
const MERCHANT_TOKEN = process.env.XPAYLABS_TOKEN;

function signRequest(data) {
  return crypto
    .createHmac("sha256", MERCHANT_TOKEN)
    .update(JSON.stringify(data), "utf8")
    .digest("hex");
}

async function createCollection(amount, orderId) {
  const data = {
    amount: amount,         // e.g. "100.00"
    symbol: "USDT",
    chain: "TRON",
    orderId: orderId,       // your internal order ID
  };

  const payload = {
    sign: signRequest(data),
    timestamp: Math.floor(Date.now() / 1000),
    nonce: crypto.randomUUID(),
    data,
  };

  const response = await fetch(`${GATEWAY_URL}/v1/order/createCollection`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });

  const result = await response.json();
  if (result.code !== 200) throw new Error(result.msg);

  return result.data; // PaymentAddress object
}

// Usage
const payment = await createCollection("100.00", "order_1042");
console.log(`Send USDT to: ${payment.address}`);
console.log(`Checkout page: ${payment.paymentUrl}`);

Response

{
  "code": 200,
  "msg": "success",
  "data": {
    "address": "TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR",
    "amount": "100.00",
    "symbol": "USDT",
    "chain": "TRON",
    "orderId": "order_1042",
    "expiredTime": 1717086400,
    "paymentUrl": "http://your-gateway:3010/checkout?orderId=order_1042"
  }
}

Step 2: Present the Payment to Your Customer

Direct your customer to the paymentUrl — a hosted checkout page that displays the deposit address, QR code, and payment instructions. Or embed the address directly in your own interface:
<div class="payment-info">
  <p>Send <strong>100 USDT</strong> on TRON to:</p>
  <code>TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR</code>
  <p>Expires: <span id="countdown"></span></p>
  <img src="https://chart.googleapis.com/chart?chs=250x250&cht=qr&chl=TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR" />
</div>

Step 3: Handle the Webhook Notification

Configure your callback URL in the gateway. When payment is detected and confirmed, the gateway sends a signed POST request:
// Express.js webhook handler
import express from "express";
import crypto from "crypto";

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.XPAYLABS_WEBHOOK_SECRET;

app.post("/webhooks/xpaylabs", (req, res) => {
  const payload = req.body;

  // Verify signature
  const dataJson = JSON.stringify(payload.data);
  const expectedSign = crypto
    .createHmac("sha256", WEBHOOK_SECRET)
    .update(dataJson, "utf8")
    .digest("hex");

  if (expectedSign !== payload.sign) {
    return res.status(400).send("Invalid signature");
  }

  // Route by event type
  switch (payload.notifyType) {
    case "ORDER_PENDING":
      console.log("Order created, awaiting payment");
      break;

    case "ORDER_PENDING_CONFIRMATION":
      console.log("Payment detected, awaiting confirmations");
      // Update order status in your database
      break;

    case "ORDER_SUCCESS":
      console.log(`Payment confirmed for order ${payload.data.orderId}`);
      // Fulfill the order — release goods, update database
      break;

    case "ORDER_EXPIRED":
      console.log(`Order ${payload.data.orderId} expired`);
      // Mark as expired in your system
      break;
  }

  res.status(200).send("ok");
});

app.listen(3000);

Step 4: Poll Order Status (Alternative to Webhooks)

If you cannot receive webhooks, poll the status endpoint:
async function getOrderStatus(orderId, token) {
  const sign = crypto
    .createHmac("sha256", token)
    .update(orderId, "utf8")
    .digest("hex");

  const response = await fetch(
    `http://your-gateway:3010/v1/order/getOrderStatus?orderId=${orderId}&sign=${sign}`
  );
  const result = await response.json();
  return result.data.status;
}

// Poll every 3 seconds until confirmed or expired
async function waitForPayment(orderId, token) {
  return new Promise((resolve) => {
    const interval = setInterval(async () => {
      const status = await getOrderStatus(orderId, token);
      if (status === "SUCCESS") {
        clearInterval(interval);
        resolve(true);
      } else if (status === "EXPIRED" || status === "FAILED") {
        clearInterval(interval);
        resolve(false);
      }
    }, 3000);
  });
}

Supported Chains

When creating a collection, specify the chain parameter:
ChainValueTokens
TRONTRONUSDT (TRC20), USDC
EthereumETHUSDT (ERC20), USDC, DAI
BNB ChainBSCUSDT (BEP20), USDC, BUSD
PolygonPOLYGONUSDT, USDC, DAI
AvalancheAVAX_C_CHAINUSDT, USDC
SUISUIUSDC
During development, use testnet chains (e.g., TRON_TEST for Shasta, ETH_SEPOLIA for Sepolia) to avoid real gas costs. See the Testing guide for details.