# HMAC-SHA256: XPayLabs API Authentication Reference Source: https://docs.xpaylabs.com/api-reference/authentication Complete reference for computing HMAC-SHA256 signatures to authenticate XPayLabs API requests, including the signing algorithm and per-request fields. Every API request to XPayLabs must include a cryptographic signature. This page details the exact signing algorithm, the purpose of each field, and common pitfalls. ## Signing Algorithm The `sign` field is computed as: ``` sign = HEX(HMAC-SHA256(JSON.stringify(data), merchant_token)) ``` Where: * `data` is the request body payload (the `data` field of `ReqPayload`) * `merchant_token` is your configured secret token * `HMAC-SHA256` produces a binary digest * `HEX` converts it to a lowercase hexadecimal string (64 characters) ## Important: JSON Serialization The `data` object must be serialized to JSON **with no extra whitespace**. Different JSON libraries may produce different output: | Library | Output | Valid? | | --------------------------------------- | ---------------------------------------------------- | ------ | | `JSON.stringify(obj)` | `{"amount":"100.00","symbol":"USDT","chain":"TRON"}` | ✅ Yes | | `json.dumps(obj, separators=(",",":"))` | `{"amount":"100.00","symbol":"USDT","chain":"TRON"}` | ✅ Yes | | `json.dumps(obj, indent=2)` | Formatted with spaces/newlines | ❌ No | The signature is computed over the `data` field only — not over the full `ReqPayload` envelope. The `sign`, `timestamp`, and `nonce` fields are excluded from signing. ## Request Envelope Each request must include these four fields as a JSON object: ```json theme={null} { "sign": "a1b2c3d4e5f6...", "timestamp": 1717000000, "nonce": "550e8400-e29b-41d4-a716-446655440000", "data": { "amount": "100.00", "symbol": "USDT", "chain": "TRON" } } ``` ## Language Examples ### Node.js ```javascript theme={null} import crypto from "crypto"; function sortKeys(obj) { return Object.keys(obj) .sort() .reduce((acc, k) => { acc[k] = obj[k]; return acc; }, {}); } function signRequest(data, token) { return crypto .createHmac("sha256", token) .update(JSON.stringify(sortKeys(data)), "utf8") .digest("hex"); } ``` ### Python ```python theme={null} import hmac, hashlib, json def sign_request(data: dict, token: str) -> str: json_str = json.dumps(data, separators=(",", ":"), sort_keys=True) return hmac.new(token.encode(), json_str.encode(), hashlib.sha256).hexdigest() ``` ### Java ```java theme={null} import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; public static String signRequest(Object data, String token) throws Exception { ObjectMapper mapper = new ObjectMapper(); mapper.enable(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS); String json = mapper.writeValueAsString(data); Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(token.getBytes("UTF-8"), "HmacSHA256")); byte[] digest = mac.doFinal(json.getBytes("UTF-8")); return bytesToHex(digest); } ``` ### Go ```go theme={null} import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" ) func SignRequest(data interface{}, token string) string { jsonBytes, _ := json.Marshal(data) // compact JSON mac := hmac.New(sha256.New, []byte(token)) mac.Write(jsonBytes) return hex.EncodeToString(mac.Sum(nil)) } ``` ## Verification XPayLabs verifies each request by: 1. Recomputing the HMAC-SHA256 signature from the `data` field and your stored merchant token. 2. Checking that the computed signature matches the provided `sign` value. 3. Validating that the `timestamp` is within 5 minutes of the server clock. 4. Ensuring the `nonce` has not been used before. If any check fails, the gateway returns a `401 Unauthorized` response: ```json theme={null} { "code": 401, "msg": "Sign verification failed", "data": null } ``` ## GET Request Signing For `GET` requests that require authentication (e.g., `/v1/order/status/{orderId}`, `/v1/order/pay`, `/v1/order/getOrderStatus`), the `sign` parameter is passed as a query string value. The signature is computed over the relevant parameters (typically `orderId`): ``` sign = HEX(HMAC-SHA256(orderId, merchant_token)) ``` # XPayLabs API Error Codes and How to Handle Them Source: https://docs.xpaylabs.com/api-reference/errors XPayLabs returns structured error responses with HTTP-compatible status codes. Learn the error format, common codes, and how to handle failures. XPayLabs returns errors using a consistent envelope with an HTTP-compatible `code` field and a human-readable `msg` field. ## Error Response Format ```json theme={null} { "code": 400, "msg": "The amount cannot be left blank.", "data": null } ``` | Field | Type | Description | | ------ | ------- | --------------------------------- | | `code` | integer | HTTP-compatible status code | | `msg` | string | Human-readable error description | | `data` | null | Always `null` for error responses | ## HTTP Status Codes | Code | Meaning | Typical Cause | | ----- | --------------------- | ------------------------------------------------------ | | `400` | Bad Request | Missing or invalid request parameters | | `401` | Unauthorized | Invalid HMAC signature or expired timestamp | | `422` | Unprocessable Entity | Business logic error (e.g., invalid address for chain) | | `429` | Too Many Requests | Rate limit exceeded | | `500` | Internal Server Error | Unexpected server error | ## Common Error Codes ### Sign Verification Errors (`401`) | Message | Cause | | --------------------------------------- | ---------------------------------------------------- | | `"Sign verification failed"` | The HMAC signature does not match the computed value | | `"The sign cannot be left blank."` | The `sign` field is missing | | `"The timestamp cannot be left blank."` | The `timestamp` field is missing | | `"The nonce cannot be left blank."` | The `nonce` field is missing | ### Validation Errors (`400`) | Message | Cause | | -------------------------------------------- | ----------------------------------------------------- | | `"The amount cannot be left blank."` | Amount is missing or null | | `"The symbol cannot be left blank."` | Symbol is missing or empty | | `"The chain cannot be left blank."` | Chain is missing or invalid | | `"The receiveAddress cannot be left blank."` | Payout address is missing | | `"ReceiveAddress error"` | The payout address is invalid for the specified chain | | `"The orderId cannot be left blank."` | orderId is required for this merchant version | | `"The uid cannot be left blank."` | uid is required for this merchant version | | `"The uid cannot be zero."` | uid cannot be the string "0" | ### Rate Limit Errors (`429`) ```json theme={null} { "code": 429, "msg": "Too many requests. Please slow down.", "data": null } ``` ## Handling Errors ```javascript theme={null} async function createCollection(payload) { const response = await fetch("http://your-gateway:180/v1/order/createCollection", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const result = await response.json(); if (result.code !== 200) { console.error(`Request failed: ${result.msg}`); // Handle specific error codes: switch (result.code) { case 401: // Recompute signature and retry break; case 400: // Fix request parameters break; case 429: // Back off and retry break; } return null; } return result.data; } ``` # Create a Collection Order | POST /v1/order/createCollection Source: https://docs.xpaylabs.com/api-reference/order/create-collection Create a collection order to receive crypto payments. Returns a unique deposit address and checkout URL for the customer to send funds. Creates a new collection order. The gateway generates a unique deposit address for the specified blockchain and token. Your customer sends funds to this address, and the blockchain scanner detects the transaction automatically. ## Request **`POST http://your-gateway:180/v1/order/createCollection`** ### Headers | Header | Value | | -------------- | ------------------ | | `Content-Type` | `application/json` | ### Body Parameters (`data` field) The payment amount as a decimal string, e.g. `"100.00"`. Must be greater than zero. The token symbol to accept, e.g. `"USDT"`. Must be a supported symbol on the specified chain. The blockchain network: * `TRON` — TRC20 tokens (USDT, USDC) * `ETH` — Ethereum ERC20 * `BSC` — BNB Smart Chain BEP20 * `POLYGON` — Polygon * `AVAX_C_CHAIN` — Avalanche C-Chain * `SUI` — SUI Network Your unique order identifier. Required for V3 merchants. Used to correlate the collection with your internal order system. Your internal user identifier. Required for V2 merchants. Cannot be `"0"`. ### cURL ```bash theme={null} curl -X POST http://your-gateway:180/v1/order/createCollection \ -H "Content-Type: application/json" \ -d '{ "sign": "a1b2c3d4e5f6...", "timestamp": 1717000000, "nonce": "550e8400-e29b-41d4-a716-446655440000", "data": { "amount": "250.00", "symbol": "USDT", "chain": "TRON", "orderId": "order_1042" } }' ``` ### Node.js ```javascript theme={null} import crypto from "crypto"; const MERCHANT_TOKEN = process.env.XPAYLABS_TOKEN; function signRequest(data) { return crypto .createHmac("sha256", MERCHANT_TOKEN) .update(JSON.stringify(data), "utf8") .digest("hex"); } const data = { amount: "250.00", symbol: "USDT", chain: "TRON", orderId: "order_1042" }; const payload = { sign: signRequest(data), timestamp: Math.floor(Date.now() / 1000), nonce: crypto.randomUUID(), data, }; const response = await fetch("http://your-gateway:180/v1/order/createCollection", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const result = await response.json(); console.log(result.data.address); // Deposit address console.log(result.data.paymentUrl); // Checkout URL ``` *** ## Response A successful request returns HTTP `200` with the `R` envelope. ### PaymentAddress Object The generated deposit address where the customer sends funds. Example: `"TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR"`. The collection amount as a decimal string. Matches the request amount. The token symbol for this collection. Example: `"USDT"`. The blockchain network. Example: `"TRON"`. Your order identifier, echoed back from the request. Your user identifier, echoed back from the request. Unix timestamp when this collection expires. After expiry, the deposit address is no longer valid for new transactions. URL to the hosted checkout page where the customer can view payment instructions and a QR code. ### Example Response ```json theme={null} { "code": 200, "msg": "success", "data": { "address": "TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR", "amount": "250.00", "symbol": "USDT", "chain": "TRON", "orderId": "order_1042", "uid": null, "expiredTime": 1717086400, "paymentUrl": "http://your-gateway:180/checkout?orderId=order_1042" } } ``` *** ## Error Responses ### `400` — Validation Error | Message | Cause | | ------------------------------------- | -------------------------------------------------- | | `"The amount cannot be left blank."` | `amount` is missing or null | | `"The symbol cannot be left blank."` | `symbol` is missing or empty | | `"The chain cannot be left blank."` | `chain` is missing or invalid | | `"The orderId cannot be left blank."` | `orderId` is required for V3 merchants but missing | | `"The uid cannot be left blank."` | `uid` is required for V2 merchants but missing | | `"The uid cannot be zero."` | `uid` is set to `"0"` | ### `429` — Rate Limit Exceeded ```json theme={null} { "code": 429, "msg": "Too many requests. Please slow down.", "data": null } ``` # Create a Payout Order | POST /v1/order/createPayout Source: https://docs.xpaylabs.com/api-reference/order/create-payout Initiate a payout from your gateway wallet to an external blockchain address. Requires a valid receive address for the specified chain. Creates a new payout order, sending funds from your gateway-managed wallet to an external blockchain address. The gateway validates the receive address format for the specified chain before processing. ## Request **`POST http://your-gateway:180/v1/order/createPayout`** ### Headers | Header | Value | | -------------- | ------------------ | | `Content-Type` | `application/json` | ### Body Parameters (`data` field) The payout amount as a decimal string, e.g. `"50.00"`. Must be greater than zero. The token symbol to send, e.g. `"USDT"`. Must be a supported symbol on the specified chain. The blockchain network for the payout. Must match the network of the receive address: * `TRON` — TRC20 * `ETH` — ERC20 * `BSC` — BEP20 * `POLYGON` — Polygon * `AVAX_C_CHAIN` — Avalanche * `SUI` — SUI Network The destination blockchain address. The gateway validates the address format for the specified chain. Example: `"TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR"` (TRON) or `"0x..."` (EVM). Your unique order identifier. Required for V3 merchants. Your internal user identifier. Required for V2 merchants. Cannot be `"0"`. ### cURL ```bash theme={null} curl -X POST http://your-gateway:180/v1/order/createPayout \ -H "Content-Type: application/json" \ -d '{ "sign": "a1b2c3d4e5f6...", "timestamp": 1717000000, "nonce": "550e8400-e29b-41d4-a716-446655440000", "data": { "amount": "50.00", "symbol": "USDT", "chain": "TRON", "receiveAddress": "TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR", "orderId": "payout_001" } }' ``` *** ## Response A successful request returns HTTP `200` with the `R` envelope. ### PaymentAddress Object The source address the payout will be sent from. Example: `"TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR"`. The payout amount. Matches the request amount. The token symbol. Example: `"USDT"`. The blockchain network. Example: `"TRON"`. Your order identifier, echoed back from the request. Unix timestamp when this payout request expires. ### Example Response ```json theme={null} { "code": 200, "msg": "success", "data": { "address": "TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR", "amount": "50.00", "symbol": "USDT", "chain": "TRON", "orderId": "payout_001", "expiredTime": 1717086400 } } ``` *** ## Error Responses | Code | Message | Cause | | ----- | -------------------------------------------- | ----------------------------------------------------- | | `400` | `"The receiveAddress cannot be left blank."` | `receiveAddress` is missing or empty | | `400` | `"ReceiveAddress error"` | The address format is invalid for the specified chain | | `400` | `"The amount cannot be left blank."` | `amount` is missing | | `400` | `"The symbol cannot be left blank."` | `symbol` is missing | | `400` | `"The chain cannot be left blank."` | `chain` is missing | Payouts move real funds from your gateway wallet. Always verify the receive address before submitting a payout request. Blockchain transactions cannot be reversed. # Get Compact Order Status | GET /v1/order/getOrderStatus Source: https://docs.xpaylabs.com/api-reference/order/order-status Quickly retrieve just the status of an order by orderId and sign. Returns only the PaymentOrderStatus object for lightweight polling. A lightweight endpoint that returns only the order's current status. Use this for polling scenarios where you need to check whether a payment has been confirmed without retrieving the full order details. ## Request **`GET http://your-gateway:180/v1/order/getOrderStatus?orderId={orderId}&sign={sign}`** ### Query Parameters The order identifier. Example: `order_1042`. HMAC-SHA256 signature computed over the `orderId` value. ### cURL ```bash theme={null} curl "http://your-gateway:180/v1/order/getOrderStatus?orderId=order_1042&sign=a1b2c3d4e5f6..." ``` *** ## Response A successful request returns HTTP `200` with the `R` envelope. ### PaymentOrderStatus Object Current order status: * `INIT` — Awaiting payment * `PENDING` — Transaction detected * `PENDING_CONFIRMATION` — Awaiting block confirmations * `SUCCESS` — Payment confirmed * `EXPIRED` — Order expired * `FAILED` — Payment failed ### Example Response ```json theme={null} { "code": 200, "msg": "success", "data": { "status": "SUCCESS" } } ``` *** ## Usage Example ```javascript theme={null} // Polling loop for payment confirmation async function waitForPayment(orderId, token) { const sign = computeSign(orderId, token); for (let i = 0; i < 120; i++) { const response = await fetch( `http://your-gateway:180/v1/order/getOrderStatus?orderId=${orderId}&sign=${sign}` ); const result = await response.json(); if (result.data.status === "SUCCESS") { console.log("Payment confirmed!"); return true; } if (result.data.status === "EXPIRED" || result.data.status === "FAILED") { console.log("Payment failed or expired"); return false; } await new Promise(r => setTimeout(r, 2000)); // poll every 2 seconds } return false; } ``` # Get Pay Link Details | GET /v1/order/pay Source: https://docs.xpaylabs.com/api-reference/order/pay-link Retrieve checkout details for a collection order, including the deposit address, amount, and current status. Used to render the customer-facing checkout page. Returns the checkout details for a collection order. This is the endpoint used by the hosted checkout page to render payment instructions, but you can also call it directly to retrieve order information for a customer-facing interface. ## Request **`GET http://your-gateway:180/v1/order/pay?orderId={orderId}&sign={sign}`** ### Query Parameters The order identifier. Example: `order_1042`. HMAC-SHA256 signature computed over the `orderId` value. See [Authentication](/api-reference/authentication). ### cURL ```bash theme={null} curl "http://your-gateway:180/v1/order/pay?orderId=order_1042&sign=a1b2c3d4e5f6..." ``` *** ## Response A successful request returns HTTP `200` with the `R` envelope. ### PayinOrderVo Object The order identifier. The blockchain network for this payment. Example: `"TRON"`. The token symbol. Example: `"USDT"`. The deposit address where the customer should send funds. Example: `"TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR"`. The requested amount as a decimal string. Example: `"250.00"`. The actual amount received once payment is confirmed. `null` before payment. Unix timestamp when this payment request expires. Current order status. See [Order Status](/api-reference/order/status) for possible values. Failure or expiry reason. `null` if the order is still valid. ### Example Response ```json theme={null} { "code": 200, "msg": "success", "data": { "orderId": "order_1042", "chain": "TRON", "symbol": "USDT", "address": "TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR", "amount": "250.00", "actualAmount": null, "expiredTime": 1717086400, "status": "PENDING", "reason": null } } ``` # Get Order Status | GET /v1/order/status/{orderId} Source: https://docs.xpaylabs.com/api-reference/order/status Retrieve the full status and details of an order by its orderId. Returns the NotifyOrder object with status, amount, transaction info, and fee. Fetch the current status and full details of any order (collection or payout) by its `orderId`. This endpoint is authenticated and intended for merchant server-to-server use. ## Request **`GET http://your-gateway:180/v1/order/status/{orderId}`** ### Path Parameters The order identifier you specified when creating the collection or payout. Example: `order_1042`. ### cURL ```bash theme={null} curl http://your-gateway:180/v1/order/status/order_1042 \ -H "Content-Type: application/json" ``` *** ## Response A successful request returns HTTP `200` with the `R` envelope. ### NotifyOrder Object The order identifier that was used when creating the collection or payout. The user identifier associated with the order, if provided. The type of order: `COLLECTION` or `PAYOUT`. Current order status: * `INIT` — Order created, awaiting customer payment * `PENDING` — Transaction detected, awaiting confirmations * `PENDING_CONFIRMATION` — Waiting for block confirmations * `SUCCESS` — Payment confirmed successfully * `EXPIRED` — Order expired without payment * `FAILED` — Transaction failed If the order failed or expired, this field contains the reason. `null` otherwise. The requested amount as a decimal string (e.g., `"250.00"`). The actual amount received/paid. May differ from `amount` due to network fees or partial fills. `null` until confirmed. The platform handling fee deducted. The platform operator configures the fee structure; when set to `"0.00"`, no fee is charged. Blockchain transaction details. `null` until a transaction is detected. See below. ### Transaction Object The blockchain network (e.g., `"TRON"`, `"ETH"`, `"BSC"`). The token symbol (e.g., `"USDT"`). The blockchain transaction hash. The sender's blockchain address. The receiver's blockchain address. The transfer amount in decimal format. The block number where the transaction was included. The number of block confirmations received. Blockchain transaction status: `PENDING`, `SUCCESS`, or `FAILED`. Unix timestamp of when the transaction was included in a block. ### Example Response ```json theme={null} { "code": 200, "msg": "success", "data": { "orderId": "order_1042", "uid": "user_42", "orderType": "COLLECTION", "status": "SUCCESS", "reason": null, "amount": "250.00", "actualAmount": "249.50", "fee": "0.00", "transaction": { "chain": "TRON", "symbol": "USDT", "txid": "a1b2c3d4e5f6...", "from": "TXyz...", "to": "TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR", "amount": "250.00", "blockNum": 12345678, "confirmedNum": 3, "status": "SUCCESS", "timestamp": 1717000123 } } } ``` # XPayLabs REST API: Crypto Payment Endpoints Reference Source: https://docs.xpaylabs.com/api-reference/overview The XPayLabs API is a REST API with JSON request/response bodies secured by HMAC-SHA256 signing. Base URL, request format, response shape, and rate limits. XPayLabs exposes a RESTful API over HTTP. Every request uses a standard signed envelope format (`ReqPayload`), and every response follows a consistent structure (`R`). This page covers the foundational concepts that apply across all endpoints. ## Base URL All API requests target your self-hosted gateway via the nginx gateway. The default external port is `180`: ``` http://your-server:180/v1 ``` If you are running behind a reverse proxy (Nginx, Caddy), use your configured HTTPS domain. ## Request Format Every `POST` request must use the standard `ReqPayload` envelope: | Field | Type | Description | | ----------- | ------- | ------------------------------------------------------ | | `sign` | string | HMAC-SHA256 signature of the stringified `data` object | | `timestamp` | integer | Unix timestamp (seconds) | | `nonce` | string | Unique request identifier | | `data` | object | The actual request parameters | ```json theme={null} { "sign": "a1b2c3d4e5f6...", "timestamp": 1717000000, "nonce": "550e8400-e29b-41d4-a716-446655440000", "data": { "amount": "100.00", "symbol": "USDT", "chain": "TRON" } } ``` See the [Authentication](/api-reference/authentication) page for details on computing the `sign` value. ## Response Format All responses use the standard `R` envelope: ```json theme={null} { "code": 200, "msg": "success", "data": { ... } } ``` | Field | Type | Description | | ------ | ------- | ------------------------------------------------- | | `code` | integer | HTTP status code (200 on success) | | `msg` | string | Status message (`"success"` or error description) | | `data` | object | The response payload (varies by endpoint) | ### Error Response ```json theme={null} { "code": 400, "msg": "The amount cannot be left blank.", "data": null } ``` ## Authentication All requests must be signed with your merchant token using HMAC-SHA256. There are no Bearer tokens or cookies involved. The merchant token is configured in your gateway and never transmitted over the network. See the [Authentication](/api-reference/authentication) page for the full signing reference. ## HTTP Methods | Method | Use | | ------ | -------------------------------------- | | `POST` | Create a resource (collection, payout) | | `GET` | Retrieve a resource or list | ## Rate Limits Rate limits are configurable per merchant in the gateway configuration. Defaults: | Endpoint | Limit | | --------------------------------- | --------------------------------- | | `POST /v1/order/createCollection` | 100 requests per 10 seconds | | `POST /v1/order/createPayout` | 100 requests per 10 seconds | | `GET /v1/order/status/{orderId}` | 1000 requests per 10 seconds | | `GET /v1/order/pay` | 2 requests per second per orderId | | `GET /v1/order/getOrderStatus` | 2 requests per second per orderId | | `GET /v1/symbol/supportSymbols` | 50 requests per 10 seconds | Exceeding the limit returns a `429 Too Many Requests` response. ## Amounts All monetary amounts are expressed as **string-formatted decimals** (e.g., `"100.00"` for 100 USDT). The number of decimal places depends on the token's configured decimals — USDT uses 2 decimal places on most chains (shown as `"100.00"`), while the internal representation uses the token's native decimals. # List Supported Symbols | GET /v1/symbol/supportSymbols Source: https://docs.xpaylabs.com/api-reference/symbols/support-symbols Retrieve the list of supported blockchain tokens configured on your gateway. Filter by chain or symbol to narrow results. Returns the list of supported symbols (tokens) configured on your gateway. The gateway administrator configures which tokens are available for each blockchain. ## Request **`GET http://your-gateway:180/v1/symbol/supportSymbols`** ### Query Parameters Filter by blockchain network. Example: `TRON`, `ETH`, `BSC`, `POLYGON`. Omit to return symbols for all chains. Filter by token symbol. Example: `USDT`, `USDC`. Omit to return all supported symbols. ### cURL ```bash theme={null} # All supported symbols curl http://your-gateway:180/v1/symbol/supportSymbols # Filter by chain curl "http://your-gateway:180/v1/symbol/supportSymbols?chain=TRON" # Filter by symbol curl "http://your-gateway:180/v1/symbol/supportSymbols?symbol=USDT" ``` *** ## Response A successful request returns HTTP `200` with the `R>` envelope. ### SupportSymbol Object The token symbol, e.g. `"USDT"`, `"USDC"`, `"ETH"`. The blockchain network, e.g. `"TRON"`, `"ETH"`, `"BSC"`. The token contract address on this chain. `null` for native coins (e.g., ETH on Ethereum). The number of decimal places for this token. Example: `6` for ERC20 USDT, `6` for TRC20 USDT, `18` for BEP20 USDT. ### Example Response ```json theme={null} { "code": 200, "msg": "success", "data": [ { "symbol": "USDT", "chain": "TRON", "contractAddress": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", "decimals": 6 }, { "symbol": "USDT", "chain": "ETH", "contractAddress": "0xdAC17F958D2ee523a2206206994597C13D831ec7", "decimals": 6 }, { "symbol": "USDC", "chain": "ETH", "contractAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "decimals": 6 }, { "symbol": "USDT", "chain": "BSC", "contractAddress": "0x55d398326f99059fF775485246999027B3197955", "decimals": 18 }, { "symbol": "USDC", "chain": "SUI", "contractAddress": null, "decimals": 6 } ] } ``` # XPayLabs Webhook Event Types — Complete Reference Source: https://docs.xpaylabs.com/api-reference/webhooks/events Complete reference for all XPayLabs webhook event types — order events (PENDING, SUCCESS, EXPIRED, FAILED) and collection events (PENDING, SUCCESS, FAILED). XPayLabs sends webhook notifications based on the `NotifyType` enum. Each notification corresponds to a change in state of an order or a collection sweep. ## Event Types | Event Type | Triggered When | Payload | | ---------------------------- | --------------------------------------------------------------- | ------------- | | `ORDER_PENDING` | A new order is created and is awaiting customer payment | NotifyOrder | | `ORDER_PENDING_CONFIRMATION` | A blockchain transaction is detected but awaiting confirmations | NotifyOrder | | `ORDER_SUCCESS` | Payment confirmed and order completed successfully | NotifyOrder | | `ORDER_EXPIRED` | Order expired before payment was received | NotifyOrder | | `ORDER_FAILED` | Payment transaction failed | NotifyOrder | | `COLLECT_PENDING` | Collection sweep (hot-to-cold transfer) is initiated | NotifyCollect | | `COLLECT_SUCCESS` | Collection sweep completed successfully | NotifyCollect | | `COLLECT_FAILED` | Collection sweep failed | NotifyCollect | *** ## Order Events (`ORDER_*`) ### `ORDER_PENDING` Sent when a collection or payout order is created and is awaiting the next action (customer payment for collections, processing for payouts). ```json theme={null} { "sign": "a1b2c3d4e5f6...", "timestamp": 1717000000, "nonce": "nonce_001", "notifyType": "ORDER_PENDING", "data": { "orderId": "order_1042", "uid": "user_42", "orderType": "COLLECTION", "status": "PENDING", "reason": null, "amount": "250.00", "actualAmount": null, "fee": "0.00", "transaction": null } } ``` ### `ORDER_PENDING_CONFIRMATION` Sent when a blockchain transaction is detected but has not yet reached the required number of block confirmations. The `transaction` field is populated with the detected transaction details. ```json theme={null} { "sign": "a1b2c3d4e5f6...", "timestamp": 1717000050, "nonce": "nonce_002", "notifyType": "ORDER_PENDING_CONFIRMATION", "data": { "orderId": "order_1042", "orderType": "COLLECTION", "status": "PENDING_CONFIRMATION", "amount": "250.00", "actualAmount": "250.00", "fee": "0.00", "transaction": { "chain": "TRON", "symbol": "USDT", "txid": "a1b2c3d4e5f6...", "from": "TXyz...", "to": "TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR", "amount": "250.00", "blockNum": 12345678, "confirmedNum": 1, "status": "PENDING", "timestamp": 1717000045 } } } ``` ### `ORDER_SUCCESS` Sent when the payment is fully confirmed (required block confirmations reached) or a payout completes successfully. ```json theme={null} { "sign": "a1b2c3d4e5f6...", "timestamp": 1717000100, "nonce": "nonce_003", "notifyType": "ORDER_SUCCESS", "data": { "orderId": "order_1042", "orderType": "COLLECTION", "status": "SUCCESS", "amount": "250.00", "actualAmount": "250.00", "fee": "0.00", "transaction": { "chain": "TRON", "symbol": "USDT", "txid": "a1b2c3d4e5f6...", "from": "TXyz...", "to": "TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR", "amount": "250.00", "confirmedNum": 19, "status": "SUCCESS", "timestamp": 1717000045 } } } ``` ### `ORDER_EXPIRED` Sent when an order reaches its expiry time without receiving the required payment. ```json theme={null} { "sign": "a1b2c3d4e5f6...", "timestamp": 1717086401, "nonce": "nonce_004", "notifyType": "ORDER_EXPIRED", "data": { "orderId": "order_1042", "orderType": "COLLECTION", "status": "EXPIRED", "reason": "Order expired", "amount": "250.00", "actualAmount": null, "fee": "0.00", "transaction": null } } ``` ### `ORDER_FAILED` Sent when a payout transaction fails or a collection transaction is rejected by the blockchain. ```json theme={null} { "sign": "a1b2c3d4e5f6...", "timestamp": 1717000200, "nonce": "nonce_005", "notifyType": "ORDER_FAILED", "data": { "orderId": "payout_001", "orderType": "PAYOUT", "status": "FAILED", "reason": "Insufficient balance", "amount": "50.00", "actualAmount": "0.00", "fee": "0.00", "transaction": null } } ``` *** ## Collection Events (`COLLECT_*`) Collection events are sent when the gateway performs automatic hot-to-cold wallet sweeps (settlement). These are distinct from order events — they relate to the internal movement of funds between the gateway's wallets. | Event Type | Triggered When | | ----------------- | ----------------------------------------------------------- | | `COLLECT_PENDING` | A sweep from the hot wallet to the cold wallet is initiated | | `COLLECT_SUCCESS` | The sweep transaction confirmed on-chain | | `COLLECT_FAILED` | The sweep transaction failed | *** ## Best Practices 1. **Respond with `200` quickly.** Return `200 OK` immediately and process the event asynchronously in a background worker. 2. **Verify the signature.** Always validate the `sign` field using your `webhook-secret` before acting on any event. 3. **Deduplicate with `nonce`.** The same event may be delivered more than once. Store processed nonces in your database and skip duplicates. 4. **Check `notifyType` first.** Route to different handlers based on the event type. 5. **Handle `ORDER_PENDING_CONFIRMATION`.** Use this event to show "payment detected" in your UI before the transaction is fully confirmed. # Webhook Notifications — XPayLabs Event Callbacks Reference Source: https://docs.xpaylabs.com/api-reference/webhooks/overview XPayLabs delivers signed webhook callbacks to your configured endpoint URL when order or collection status changes. Learn how to configure endpoints and verify payloads. XPayLabs sends HTTP POST callbacks to your configured endpoint URL whenever an order or collection changes state. Instead of polling the API for status changes, register your callback URL and receive signed notifications in real-time. ## Configuration Set your webhook endpoint URL and shared secret in the gateway configuration: ```yaml theme={null} xpay: merchant: callback-url: "https://yourapp.com/webhooks/xpaylabs" webhook-secret: "your-webhook-secret-here" ``` The `webhook-secret` is used to compute the `sign` field in each webhook payload, allowing you to verify the notification originated from your gateway. ## Payload Format Every webhook callback is a `POST` request with a JSON body following the `NotifyPayload` structure: | Field | Type | Description | | ------------ | ------- | ------------------------------------------------------------------ | | `sign` | string | HMAC-SHA256 signature for payload verification | | `timestamp` | integer | Unix timestamp of when the notification was generated | | `nonce` | string | Unique event identifier | | `notifyType` | string | The event type (see [Event Types](/api-reference/webhooks/events)) | | `data` | object | The event payload (NotifyOrder object) | ### Example Payload ```json theme={null} { "sign": "a1b2c3d4e5f6...", "timestamp": 1717000123, "nonce": "550e8400-e29b-41d4-a716-446655440000", "notifyType": "ORDER_SUCCESS", "data": { "orderId": "order_1042", "uid": "user_42", "orderType": "COLLECTION", "status": "SUCCESS", "reason": null, "amount": "250.00", "actualAmount": "249.50", "fee": "0.00", "transaction": { "chain": "TRON", "symbol": "USDT", "txid": "a1b2c3d4e5f6...", "from": "TXyz...", "to": "TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR", "amount": "250.00", "blockNum": 12345678, "confirmedNum": 3, "status": "SUCCESS", "timestamp": 1717000123 } } } ``` ## Signature Verification Verify the webhook signature using your `webhook-secret`: ### Algorithm 1. Serialize the `data` object (the `data` field from the payload) as compact JSON. 2. Compute `HMAC-SHA256(data_json, webhook_secret)`. 3. Convert to lowercase hex and compare with the `sign` field. ### Node.js ```javascript theme={null} import crypto from "crypto"; export function verifyWebhook(payload, secret) { const dataJson = JSON.stringify(payload.data); // compact JSON const expectedSign = crypto .createHmac("sha256", secret) .update(dataJson, "utf8") .digest("hex"); if (!crypto.timingSafeEqual( Buffer.from(expectedSign, "hex"), Buffer.from(payload.sign, "hex") )) { throw new Error("Invalid webhook signature"); } return true; } // Express.js example app.post("/webhooks/xpaylabs", express.json(), (req, res) => { try { verifyWebhook(req.body, process.env.XPAYLABS_WEBHOOK_SECRET); } catch (err) { return res.status(400).send("Verification failed"); } const event = req.body; // Process event... res.status(200).send("ok"); }); ``` ### Python ```python theme={null} import hmac, hashlib, json from flask import Flask, request app = Flask(__name__) WEBHOOK_SECRET = "your-webhook-secret" @app.route("/webhooks/xpaylabs", methods=["POST"]) def handle_webhook(): payload = request.get_json() data_json = json.dumps(payload["data"], separators=(",", ":")) expected_sign = hmac.new( WEBHOOK_SECRET.encode(), data_json.encode(), hashlib.sha256, ).hexdigest() if not hmac.compare_digest(expected_sign, payload["sign"]): return "Invalid signature", 400 notify_type = payload["notifyType"] # Process event... return "", 200 ``` ## Retry Policy If your endpoint does not return a `2xx` status code within 10 seconds, XPayLabs retries the webhook with exponential backoff: | Attempt | Delay | | --------- | ------------ | | 1st retry | \~1 second | | 2nd retry | \~5 seconds | | 3rd retry | \~30 seconds | | 4th retry | \~5 minutes | After 4 failed attempts, the notification is marked as failed. Failed webhooks are logged and can be replayed from the gateway dashboard. ## Best Practices * **Respond immediately.** Return `200` as fast as possible, then process the event asynchronously. * **Verify every payload.** Always check the HMAC signature before acting on a webhook. * **Use `nonce` for deduplication.** Store processed nonces to handle at-least-once delivery. * **Check `notifyType`.** Route events based on the notification type to handle each event correctly. # HMAC-SHA256 Authentication: XPayLabs API Security Source: https://docs.xpaylabs.com/authentication XPayLabs authenticates every API request with HMAC-SHA256 request signing. Compute the sign field with your merchant token for secure crypto payment API calls. XPayLabs authenticates every API request using HMAC-SHA256 request signing. Unlike traditional Bearer token authentication, XPayLabs requires each request to include a cryptographic signature computed over the request payload. This ensures request integrity and prevents replay attacks. *** ## What is the HMAC-SHA256 signing format? Every API request includes a `ReqPayload` wrapper with four fields: | Field | Type | Description | | ----------- | ------- | -------------------------------------------------------- | | `sign` | string | HMAC-SHA256 signature of the stringified `data` object | | `timestamp` | integer | Unix timestamp (seconds) of when the request was created | | `nonce` | string | A unique random string, never reused | | `data` | object | The actual request payload | The `sign` field is computed using your merchant token as the HMAC secret key. *** ## How to compute the HMAC signature ### Algorithm 1. Serialize the `data` object as a JSON string (no pretty-printing, no extra whitespace). 2. Compute `HMAC-SHA256(data_json, merchant_token)`. 3. Convert the result to a lowercase hex string. 4. Set this value as the `sign` field. ### Node.js ```javascript theme={null} import crypto from "crypto"; const MERCHANT_TOKEN = process.env.XPAYLABS_MERCHANT_TOKEN; function signRequest(data) { const json = JSON.stringify(data); // compact JSON, no extra whitespace return crypto .createHmac("sha256", MERCHANT_TOKEN) .update(json, "utf8") .digest("hex"); } const request = { sign: signRequest({ amount: "100.00", symbol: "USDT", chain: "TRON" }), timestamp: Math.floor(Date.now() / 1000), nonce: crypto.randomUUID(), data: { amount: "100.00", symbol: "USDT", chain: "TRON" }, }; ``` ### Python ```python theme={null} import hmac import hashlib import json import time import uuid MERCHANT_TOKEN = "your-merchant-token" def sign_request(data: dict) -> str: json_str = json.dumps(data, separators=(",", ":")) # compact JSON return hmac.new( MERCHANT_TOKEN.encode("utf-8"), json_str.encode("utf-8"), hashlib.sha256, ).hexdigest() request = { "sign": sign_request({"amount": "100.00", "symbol": "USDT", "chain": "TRON"}), "timestamp": int(time.time()), "nonce": str(uuid.uuid4()), "data": {"amount": "100.00", "symbol": "USDT", "chain": "TRON"}, } ``` ### Go ```go theme={null} package main import ( "crypto/hmac" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "time" "github.com/google/uuid" ) func signRequest(data interface{}, token string) string { jsonBytes, _ := json.Marshal(data) mac := hmac.New(sha256.New, []byte(token)) mac.Write(jsonBytes) return hex.EncodeToString(mac.Sum(nil)) } func main() { data := map[string]interface{}{ "amount": "100.00", "symbol": "USDT", "chain": "TRON", } request := map[string]interface{}{ "sign": signRequest(data, "your-merchant-token"), "timestamp": time.Now().Unix(), "nonce": uuid.New().String(), "data": data, } fmt.Printf("%+v\n", request) } ``` The JSON serialization **must be compact** — no spaces, no newlines between key-value pairs. Different JSON serializers may produce different output; always test your signature computation against a known working example. *** ## What are the timestamp and nonce requirements? The `timestamp` and `nonce` fields prevent replay attacks: * **Timestamp**: Your server clock must be within **5 minutes** of XPayLabs server time. Requests with timestamps older than 5 minutes are rejected. * **Nonce**: Each request must use a unique nonce. XPayLabs tracks used nonces and rejects duplicates. UUIDs or cryptographically random strings work well. *** ## How does XPayLabs verify signatures server-side? XPayLabs verifies every request by recomputing the HMAC-SHA256 signature using your stored merchant token. If the signatures don't match, or if the timestamp is outside the tolerance window, the request is rejected with a `401 Unauthorized` response. *** ## How to keep your merchant token secure * Store your merchant token in an environment variable or secrets manager. * Never hardcode the token in source code or client-side applications. * Rotate the token periodically and update your configuration. * The token is a shared secret between your merchant server and the XPayLabs gateway. It is not sent over the network in API requests. # XPayLabs API Errors: Codes, Causes, and Solutions Source: https://docs.xpaylabs.com/concepts/errors XPayLabs returns structured error responses with HTTP status codes and validation error messages. Handle blockchain payment errors gracefully with our troubleshooting guide. XPayLabs returns errors using a standard `R` envelope with an HTTP-compatible `code` and a human-readable `msg`. Unlike traditional payment processors, XPayLabs errors are primarily validation and authentication errors — since blockchain transactions are final, there are no "card declined" or "insufficient funds" errors at the API level (those are blockchain-level issues). ## What is the error response format? ```json theme={null} { "code": 400, "msg": "The amount cannot be left blank.", "data": null } ``` | Field | Type | Description | | ------ | ------- | -------------------------------- | | `code` | integer | HTTP-compatible status code | | `msg` | string | Human-readable error description | | `data` | null | Always `null` in error responses | ## Status Codes | Code | Meaning | Typical Cause | | ----- | ----------------- | ------------------------------------------- | | `400` | Bad Request | Missing or invalid parameters | | `401` | Unauthorized | Invalid HMAC signature or expired timestamp | | `422` | Unprocessable | Invalid address format for chain | | `429` | Too Many Requests | Rate limit exceeded | | `500` | Internal Error | Unexpected gateway error | ## Common Validation Errors ### Signature Errors (`401`) | Message | Cause | | --------------------------------------- | ------------------------------------------- | | `"The sign cannot be left blank."` | `sign` field is missing from the request | | `"Sign verification failed"` | HMAC signature doesn't match computed value | | `"The timestamp cannot be left blank."` | `timestamp` is missing | | `"The nonce cannot be left blank."` | `nonce` is missing | ### Order Errors (`400`) | Message | Cause | | -------------------------------------------- | ------------------------------------------------- | | `"The amount cannot be left blank."` | Amount is missing or null | | `"The symbol cannot be left blank."` | Token symbol is missing | | `"The chain cannot be left blank."` | Blockchain network is missing | | `"The receiveAddress cannot be left blank."` | Payout destination address is missing | | `"ReceiveAddress error"` | Address format is invalid for the specified chain | | `"The orderId cannot be left blank."` | Required for V3 merchants | | `"The uid cannot be left blank."` | Required for V2 merchants | | `"The uid cannot be zero."` | uid is set to `"0"` | ## Handling Errors in Your Integration ```javascript theme={null} async function createCollection(payload) { const response = await fetch("http://your-gateway:180/v1/order/createCollection", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); const result = await response.json(); if (result.code !== 200) { switch (result.code) { case 401: // Signature issue — recompute sign and retry console.error("Auth error:", result.msg); break; case 400: // Validation error — fix request parameters console.error("Validation error:", result.msg); break; case 429: // Rate limited — back off and retry await sleep(1000); return createCollection(payload); default: console.error("Unexpected error:", result.msg); } return null; } return result.data; } ``` ## Rate Limiting XPayLabs enforces configurable rate limits per endpoint. Exceeding the limit returns a `429` response. Default limits: | Endpoint | Limit | | ------------------------- | ---------------------- | | `/order/createCollection` | 100 req / 10s per IP | | `/order/createPayout` | 100 req / 10s per IP | | `/order/status/{orderId}` | 1000 req / 10s | | `/order/pay` | 2 req / 1s per orderId | | `/order/getOrderStatus` | 2 req / 1s per orderId | | `/symbol/supportSymbols` | 50 req / 10s | # Collection Orders: How Crypto Payments Work in XPayLabs Source: https://docs.xpaylabs.com/concepts/payments XPayLabs collection orders generate unique deposit addresses for each transaction. Track the lifecycle from creation to on-chain confirmation with order statuses and webhook events. A **Collection Order** is the core payment primitive in XPayLabs. When a customer wants to pay you in cryptocurrency, your server creates a collection order through the API. The gateway generates a unique deposit address for that specific transaction, monitors the blockchain for incoming funds, and notifies your server when payment is confirmed. ## What is the collection order lifecycle? Your server calls `POST /v1/order/createCollection` with the amount, symbol, and chain. The gateway creates an order with status `INIT` and returns a unique deposit address and checkout URL. The order moves to `PENDING`. The customer sees the deposit address and a QR code on the checkout page. The order has a configurable expiry time. The blockchain scanner detects an incoming transaction matching the amount and deposit address. The status changes to `PENDING_CONFIRMATION`. A webhook (`ORDER_PENDING_CONFIRMATION`) is sent with the transaction details. After the required number of block confirmations (configurable per chain), the order status changes to `SUCCESS`. A webhook (`ORDER_SUCCESS`) is sent. The funds are now in your gateway hot wallet. If no payment arrives before the expiry time, the order status moves to `EXPIRED`. If the transaction fails validation, it moves to `FAILED`. ## What order statuses does XPayLabs use? | Status | Description | | ---------------------- | ----------------------------------------------------- | | `INIT` | Order created, deposit address generated | | `PENDING` | Awaiting customer payment | | `PENDING_CONFIRMATION` | Transaction detected, waiting for block confirmations | | `SUCCESS` | Payment confirmed, funds available | | `EXPIRED` | Order expired without receiving payment | | `FAILED` | Collection or payout failed | ## How do collections flow from server to blockchain? ``` Merchant Server XPayLabs Gateway Blockchain Customer | | | | |-- POST /createCollection -->| | | |<-- PaymentAddress ---------| | | | | | | | | |<-- sends funds ----| | |<-- scanner detects ---| | |<-- Webhook: PENDING_CONFIRMATION | | | | | | | |<-- block confirmed ---| | |<-- Webhook: ORDER_SUCCESS | | ``` ## PaymentAddress Object When you create a collection, the gateway returns a `PaymentAddress` object: | Field | Description | | ------------- | ------------------------------------------ | | `address` | Unique deposit address for this collection | | `amount` | The requested amount | | `symbol` | Token symbol (e.g., USDT) | | `chain` | Blockchain network | | `orderId` | Your order identifier | | `uid` | Your user identifier | | `expiredTime` | Order expiry timestamp | | `paymentUrl` | Checkout page URL with QR code | ## Amount Format All amounts are **string-formatted decimals** (e.g., `"100.00"`). This avoids floating-point precision issues common in JavaScript when dealing with large numbers. ## Deposit Addresses Each collection order gets a **unique, single-use deposit address**. This provides clear on-chain attribution — you can always identify which customer paid for which order by looking at the deposit address in the blockchain transaction. Addresses are generated deterministically from your gateway's HD wallet seed, so they never need to be pre-generated or stored externally. # Settlements: Auto-Sweep Hot Wallet Funds to Cold Storage Source: https://docs.xpaylabs.com/concepts/settlements XPayLabs automatically sweeps received funds from hot wallets to your cold storage. Configure settlement rules and thresholds for each supported blockchain. Settlement refers to the automatic transfer of received funds from your gateway's **hot wallet** (where customer payments arrive) to your **cold storage wallet** (where funds are secured for long-term holding). This process is known as a "sweep" or "collection." Unlike traditional payment processors that batch settlements on a T+2 schedule, XPayLabs settlement is a blockchain-native process — funds move directly between your wallets on-chain within minutes, not days. ## How does settlement work? ``` Customer Payment Hot Wallet Cold Wallet | | | |--- USDT arrives --->| | | |-- sweep triggered ->| | | (auto or manual) | | | | | |<-- sweep confirmed -| |<-- Webhook: COLLECT_SUCCESS | ``` When a customer's payment is confirmed (`ORDER_SUCCESS`), the USDT (or other token) sits in the gateway's hot wallet — an online wallet used for receiving payments. Settlement can be triggered in two ways: * **Automatic:** The gateway monitors accumulated balances and initiates a sweep when configurable thresholds are met (e.g., every 1000 USDT or every hour). * **Manual:** An administrator triggers a sweep through the gateway dashboard. The gateway constructs and signs a blockchain transaction that transfers the accumulated tokens from the hot wallet to the cold storage address. This transaction pays standard blockchain gas fees. Once the transfer transaction reaches sufficient block confirmations, a `COLLECT_SUCCESS` webhook is sent. If the transaction fails, `COLLECT_FAILED` is sent. ## Configuration You configure settlement in the gateway configuration (`.env` file or environment variables): ```yaml theme={null} xpay: settlement: cold-wallet-addresses: TRON: "TYourColdWalletAddress..." ETH: "0xYourColdWalletAddress..." BSC: "0xYourColdWalletAddress..." auto-sweep: enabled: true min-amount: "1000" # Minimum USDT to trigger sweep max-amount: "10000" # Maximum USDT per sweep transaction interval: 3600 # Check interval in seconds (1 hour) sweep-on-collect: true # Sweep immediately after each collection ``` ## Settlement vs. Payout | | Settlement | Payout | | --------------- | ---------------------------- | --------------------------------- | | **Direction** | Hot wallet → Cold wallet | Gateway wallet → External address | | **Purpose** | Secure funds in cold storage | Pay vendors, suppliers, or users | | **Trigger** | Automatic by threshold | Manual via API (`/createPayout`) | | **Destination** | Your own cold wallet | Any valid blockchain address | ## Webhook Events Settlement events are delivered via the same webhook system as order events: | Event | When | | ----------------- | --------------------------------------------- | | `COLLECT_PENDING` | Sweep transaction submitted to the blockchain | | `COLLECT_SUCCESS` | Sweep transaction confirmed | | `COLLECT_FAILED` | Sweep transaction failed | Settlement is a non-custodial process — the gateway is just software that moves funds according to your rules. At no point does any third party have access to your private keys or the ability to redirect funds. All transactions are signed locally within your Docker container. # Webhooks: Real-Time Crypto Payment Notifications Source: https://docs.xpaylabs.com/concepts/webhooks XPayLabs webhooks deliver real-time notifications for order and collection status changes. Configure callback URLs and verify HMAC signatures for secure integration. Webhooks are the primary way your server learns about payment events in real-time. Instead of polling the gateway API, you register a callback URL and XPayLabs sends signed `POST` requests whenever an order or collection changes state. ## How do webhooks work? 1. You configure a `callback-url` and `webhook-secret` in the gateway configuration. 2. When an event occurs (e.g., a payment is detected), the gateway constructs a `NotifyPayload` and sends it to your URL. 3. Your server verifies the HMAC-SHA256 signature and processes the event. ## What is the webhook payload format? Every webhook follows the `NotifyPayload` format: | Field | Type | Description | | ------------ | ------- | ---------------------------------------------------------------- | | `sign` | string | HMAC-SHA256 of the `data` field, signed with your webhook secret | | `timestamp` | integer | Unix timestamp of the event | | `nonce` | string | Unique event identifier for deduplication | | `notifyType` | string | The event type identifier | | `data` | object | The event payload (NotifyOrder or related object) | ## What webhook event types are available? ### Order Events | Event | Description | | ---------------------------- | -------------------------------------------------- | | `ORDER_PENDING` | Order created, awaiting payment | | `ORDER_PENDING_CONFIRMATION` | Transaction detected, awaiting block confirmations | | `ORDER_SUCCESS` | Payment fully confirmed | | `ORDER_EXPIRED` | Order expired without payment | | `ORDER_FAILED` | Order failed | ### Collection (Settlement) Events | Event | Description | | ----------------- | ---------------------------- | | `COLLECT_PENDING` | Hot-to-cold sweep initiated | | `COLLECT_SUCCESS` | Sweep completed successfully | | `COLLECT_FAILED` | Sweep transaction failed | ## Signature Verification Every webhook includes a `sign` field computed as: ``` sign = HEX(HMAC-SHA256(JSON.stringify(data), webhook_secret)) ``` You must verify this signature before acting on any webhook. See the [Webhook Reference](/api-reference/webhooks/overview) for code examples in Node.js, Python, and other languages. ## Delivery Guarantees * **At-least-once delivery.** The same event may arrive more than once under rare conditions. Use the `nonce` field for deduplication. * **Retry with backoff.** If your endpoint returns a non-`2xx` status or times out, XPayLabs retries with exponential backoff. * **Ordering.** Events for a single order are delivered in sequence. Events across different orders may arrive out of order. ## Best Practices 1. **Always verify the signature.** Never process a webhook without validating the `sign` field. 2. **Return 200 quickly.** Acknowledge receipt immediately and process the event asynchronously. 3. **Deduplicate with `nonce`.** Store processed nonces to handle duplicate deliveries safely. 4. **Use `ORDER_PENDING_CONFIRMATION` for early UX.** Show "payment detected" in your UI before the transaction is fully confirmed. 5. **Handle `ORDER_SUCCESS` for fulfillment.** This is the signal to deliver goods or services. # XPayLabs FAQ: Self-Hosted Crypto Payment Questions Source: https://docs.xpaylabs.com/faq Answers to common questions about XPayLabs — deployment, supported blockchains, API integration, webhooks, security, and zero-fee pricing model. Answers to frequently asked questions about XPayLabs — deployment, supported blockchains, API integration, webhooks, security, and pricing. For integration-specific questions, refer to the relevant guide or API reference page. ## General XPayLabs (often called **xpay**) is a self-hosted, non-custodial crypto payment infrastructure. It enables businesses to accept, process, and settle stablecoin payments on TRON, EVM chains, and SUI without relying on a third-party payment processor. You deploy it on your own servers, your private keys stay in your infrastructure, and you pay no gateway fees — only blockchain gas costs. Fully self-hosted. You deploy the gateway on your own infrastructure using Docker Compose. XPayLabs does not operate a hosted SaaS platform. All data, keys, and funds stay within your control. Yes. Private keys are generated and stored exclusively within your Docker containers. No third party can access or move your funds. The gateway software never transmits keys over the network. Recommended minimum: 8GB RAM / 4 vCPU. For high-concurrency or clustered deployments: 16GB RAM / 8 vCPU. The gateway requires Docker and Docker Compose. Ubuntu 22.04+ is recommended. ## Blockchains & Tokens XPayLabs natively supports TRON (TRC20), Ethereum, BNB Chain, Polygon, Avalanche C-Chain, and SUI. Testnets (TRON Shasta, ETH Sepolia, BSC Testnet, Polygon Amoy, Avalanche Fuji, SUI Testnet) are supported for development. USDT and USDC are supported out of the box on most chains. Additional ERC20/TRC20/BEP20 tokens can be configured by declaring their smart contract addresses in the gateway configuration. Yes. Add any standard-compliant token by specifying its contract address, symbol, and decimals in the gateway's asset type configuration. The scanner monitors the contract's `Transfer` event logs for incoming transactions. ## API & Integration All requests are authenticated using HMAC-SHA256 request signing. You configure a merchant token in the gateway, and each API request includes a signature computed over the request payload. No merchant tokens are transmitted over the network. Call `POST /v1/order/createCollection` with the amount, symbol, and chain. The gateway returns a unique deposit address. Your customer sends funds to that address, and the blockchain scanner detects the payment automatically. Configure a callback URL in the gateway. When order or collection status changes, the gateway sends a signed `POST` request with a `NotifyPayload` containing the event type and order data. Verify the HMAC-SHA256 signature using your webhook secret. Yes. Use `GET /v1/order/getOrderStatus?orderId=...&sign=...` or `GET /v1/order/status/{orderId}` to retrieve the current order status. However, webhooks are recommended for production use. ## Deployment Clone the repository and run `docker compose up -d`. Configure your blockchain RPC endpoints, generate a wallet seed phrase, and set up your merchant account in the gateway dashboard. Access the API via the gateway on port `180`. Yes. The gateway requires RPC endpoints for each blockchain you want to support. You can use public RPC providers (e.g., Trongrid, Infura) or run your own nodes. Yes. It is recommended to place XPayLabs behind Nginx or Caddy for TLS termination and domain routing. The Java backends serve on internal ports (xpay-user:8077, xpay-merchant:8078), and the nginx gateway routes external traffic. ## Security Private keys are generated and stored in your Docker container's memory and encrypted storage. They never leave your infrastructure. The wallet seed is derived from a mnemonic phrase you configure in the gateway. Since XPayLabs is non-custodial, funds always stay on the blockchain — there is no risk of fund loss during downtime. When the gateway restarts, the blockchain scanner replays missed blocks and detects any transactions that occurred during the outage. * Place the gateway behind a firewall, accepting only necessary traffic on port 180. * Use a reverse proxy with TLS for all external-facing endpoints. * Restrict dashboard access to your internal network or VPN. * Regularly backup your wallet seed phrase offline. ## Costs Zero gateway fees. You pay only blockchain network gas fees (e.g., TRON bandwidth/energy, Ethereum gas). There are no per-transaction fees, monthly fees, or percentage-based fees. Gas costs vary by blockchain and network congestion. Typical costs: TRON \~$0.01, Ethereum ~$0.50-$5.00, BSC ~$0.05-$0.20, Polygon ~$0.01-$0.05, SUI ~$0.01. # Accept Crypto Payments: Collection Order Guide Source: https://docs.xpaylabs.com/guides/accept-payments Accept stablecoin payments via XPayLabs collection orders. Create a deposit address, generate a checkout page, and handle webhook callbacks in this step-by-step guide. Accept stablecoin payments with XPayLabs by creating a collection order, presenting the checkout page to your customer, and confirming payment via webhook. TRON transactions confirm in \~3 seconds; EVM chains in \~12-15 seconds. ## How does the crypto payment flow work? 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: ```javascript theme={null} import crypto from "crypto"; const GATEWAY_URL = "http://your-gateway:180"; 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 ```json theme={null} { "code": 200, "msg": "success", "data": { "address": "TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR", "amount": "100.00", "symbol": "USDT", "chain": "TRON", "orderId": "order_1042", "expiredTime": 1717086400, "paymentUrl": "http://your-gateway:180/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: ```html theme={null}

Send 100 USDT on TRON to:

TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR

Expires:

``` *** ## 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: ```javascript theme={null} // 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: ```javascript theme={null} async function getOrderStatus(orderId, token) { const sign = crypto .createHmac("sha256", token) .update(orderId, "utf8") .digest("hex"); const response = await fetch( `http://your-gateway:180/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: | Chain | Value | Tokens | | --------- | -------------- | ------------------------ | | TRON | `TRON` | USDT (TRC20), USDC | | Ethereum | `ETH` | USDT (ERC20), USDC, DAI | | BNB Chain | `BSC` | USDT (BEP20), USDC, BUSD | | Polygon | `POLYGON` | USDT, USDC, DAI | | Avalanche | `AVAX_C_CHAIN` | USDT, USDC | | SUI | `SUI` | USDC | During development, use testnet chains (e.g., `TRON_TEST` for Shasta, `ETH_SEPOLIA` for Sepolia) to avoid real gas costs. See the [Testing guide](/guides/testing) for details. # AI Agent Payments: Autonomous Crypto Transactions Source: https://docs.xpaylabs.com/guides/ai-agents Enable AI agents to send and receive stablecoin payments autonomously. Non-custodial, self-hosted crypto payment infrastructure for agentic workflows. AI agents can send and receive stablecoin payments autonomously using XPayLabs. Agents call the REST API to create collection orders and initiate payouts, listen for webhook events, and manage balances — with zero gateway fees and full self-custody of funds. ## Why Crypto for AI Agents? | Challenge | XPayLabs Solution | | ------------------------------------ | ----------------------------------------------------------------------------- | | Agents need programmable money | REST API for createCollection / createPayout — agents call endpoints directly | | No human in the loop for approvals | Automated signing with environment-stored secrets | | Cross-border agent-to-agent payments | Stablecoins on TRON, EVM, and SUI settle in seconds | | Microtransactions | Zero gateway fees — only blockchain gas costs ($0.01–$0.50) | | Self-custody required | Non-custodial — keys stay in your Docker containers | ## Agent Payment Flows ### Incoming Payments (Agent Gets Paid) An agent receives crypto payments by creating collection orders. This is the most common flow — users or other agents send funds to a deposit address generated by the agent. ```typescript theme={null} // AI agent creates a payment request async function requestPayment(userId: string, amount: number, orderId: string) { const collection = await xpay.createCollection({ amount, symbol: 'USDT', chain: 'TRON', orderId, uid: userId, }); return { depositAddress: collection.data.address, expiresAt: collection.data.expiredTime, checkoutUrl: collection.data.paymentUrl, }; } ``` The agent then listens for the `ORDER_SUCCESS` webhook to release the service: ```typescript theme={null} // Webhook handler for the agent app.post('/webhook', (req, res) => { const event = xpay.parseWebhook(JSON.stringify(req.body), req.body.sign, req.body.timestamp.toString()); if (event?.notifyType === 'ORDER_SUCCESS') { const data = event.data as any; // Release API credits, unlock features, or start a job await unlockService(data.orderId, data.actualAmount); } res.status(200).send('ok'); }); ``` ### Outgoing Payments (Agent Pays Out) An agent initiates payouts to external addresses — paying for compute, staking, or settling with other agents: ```typescript theme={null} // Agent pays for compute resources async function payComputeProvider(providerAddress: string, amount: number, jobId: string) { const payout = await xpay.createPayout({ amount, symbol: 'USDT', chain: 'ETH', orderId: `compute-${jobId}`, receiveAddress: providerAddress, uid: 'ai-agent-01', }); return payout.data.orderId; } ``` ### Agent-to-Agent Payments Two autonomous agents can transact directly: ``` Agent A ──POST /v1/order/createCollection──▶ Gateway ──returns deposit address──▶ Agent A │ │ sends deposit address to Agent B ▼ Agent B ──sends USDT to address─────────────▶ Blockchain ──scanner detects──▶ Gateway │ │ ORDER_SUCCESS webhook ▼ Agent A ──receives webhook──▶ fulfills agreement ``` ## Autonomous Agent Architecture ``` ┌─────────────────────────────────────────────────┐ │ Your Server │ │ │ │ ┌──────────┐ ┌─────────────┐ │ │ │ Agent A │────▶│ XPayLabs │ │ │ │ (buyer) │◀───│ SDK / API │ │ │ └──────────┘ └──────┬──────┘ │ │ │ │ │ ┌──────────┐ │ ┌───────────────┐ │ │ │ Agent B │───────────┘ │ Webhook │ │ │ │ (seller) │───────────────│ Endpoint │ │ │ └──────────┘ └───────────────┘ │ │ │ │ │ ┌──────▼──────┐ │ │ │ Blockchain │ │ │ │ Scanner │ │ │ └─────────────┘ │ └─────────────────────────────────────────────────┘ ``` ## Key Design Considerations ### Secret Management The merchant token and API secret must be accessible to the agent at runtime without hardcoding: ```bash theme={null} # Environment variables — never commit to git XPAYLABS_MERCHANT_TOKEN=your-merchant-token XPAYLABS_API_SECRET=your-api-secret XPAYLABS_GATEWAY_URL=http://gateway:180 ``` ### Idempotency Agents may retry failed requests. Use the `orderId` field as an idempotency key — the same `orderId` on a `createCollection` or `createPayout` call returns the existing order instead of creating a duplicate. ### Webhook Reliability Agents should use the polling fallback (`getOrderStatus`) if webhooks are missed. Poll every 3 seconds until `SUCCESS`, `EXPIRED`, or `FAILED`: ```typescript theme={null} async function waitForConfirmation(orderId: string, maxWaitMs = 120000): Promise { const start = Date.now(); while (Date.now() - start < maxWaitMs) { const status = await xpay.getOrderStatus(orderId); const s = status.data?.status; if (s === 'SUCCESS') return true; if (s === 'EXPIRED' || s === 'FAILED') return false; await new Promise(r => setTimeout(r, 3000)); } return false; // timeout } ``` ### Balance Management Before creating payouts, check the gateway's hot wallet balance: ```typescript theme={null} const balance = await xpay.getMerchantBalance('USDT'); console.log(`Available: ${balance.data?.balance}, Frozen: ${balance.data?.frozenBalance}`); ``` For EVM chains, also ensure the hot wallet has sufficient native gas tokens (ETH, BNB, MATIC, AVAX) to cover transaction fees. ## Use Cases | Use Case | Flow | Example | | ------------------------- | ---------------------------------------------------------------------- | ----------------------- | | API access pay-per-call | Agent creates collection → user pays → webhook unlocks endpoint | LLM inference API | | Compute marketplace | Agent A pays Agent B for GPU time via payout | Distributed ML training | | Autonomous affiliate | Affiliate agent receives collection webhook, splits payout to referrer | Referral program | | Agent-to-agent settlement | Periodic batch payouts between agent wallets | Cross-agent accounting | | Micropayment streaming | Frequent small collections (ticketed) | Real-time data feeds | ## Getting Started 1. Deploy XPayLabs via [Docker Compose](/quickstart) 2. Install the [Node.js SDK](/guides/nodejs-integration) or call the [REST API](/api-reference/overview) directly 3. Configure your agent's environment with the merchant token and API secret 4. Create a webhook endpoint for `ORDER_SUCCESS` events 5. Start creating collection and payout orders programmatically For agent-to-agent payments, both agents can share the same XPayLabs gateway instance — or run separate instances with their own keys for full isolation. # Node.js + XPayLabs: SDK Crypto Payment Integration Source: https://docs.xpaylabs.com/guides/nodejs-integration Integrate crypto payments into your Node.js app with the @xpaylabs/node-sdk. npm setup, TypeScript types, webhook verification, and full API examples. Integrate crypto payments into your Node.js application using the official XPayLabs Node.js SDK (`@xpaylabs/node-sdk`). The SDK provides typed API bindings for TypeScript and JavaScript with automatic HMAC signing, webhook verification, and full TypeScript type declarations. ## Installation ```bash theme={null} npm install @xpaylabs/node-sdk ``` The SDK requires Node.js 14+. ## Configuration ```typescript theme={null} import { XPay } from '@xpaylabs/node-sdk'; const xpay = new XPay({ apiKey: 'your-merchant-token', apiSecret: 'your-api-secret', baseUrl: 'http://your-gateway:180', timeout: 30000, }); ``` | Option | Required | Default | Description | | ----------- | -------- | -------------------------- | ------------------------------------------------------- | | `apiKey` | Yes | — | Merchant token | | `apiSecret` | Yes | — | API secret for HMAC signing | | `baseUrl` | No | `https://api.xpaylabs.com` | Gateway base URL (override for self-hosted deployments) | | `timeout` | No | `30000` | Request timeout in ms | ## Core API Methods ### Create a Collection Order ```typescript theme={null} import { XPay, CollectionRequest } from '@xpaylabs/node-sdk'; const xpay = new XPay({ apiKey: '...', apiSecret: '...', baseUrl: 'http://your-gateway:180' }); async function createPayment(uid: string, amount: number) { const collection = await xpay.createCollection({ amount, symbol: 'USDT', chain: 'TRON', orderId: `order-${Date.now()}`, uid, }); return collection.data; // Returns: { address, amount, symbol, chain, uid, orderId, expiredTime } } ``` ### Create a Payout ```typescript theme={null} async function sendPayout(uid: string, amount: number, receiveAddress: string) { const payout = await xpay.createPayout({ amount, symbol: 'USDT', chain: 'TRON', orderId: `payout-${Date.now()}`, uid, receiveAddress, }); return payout.data; // Returns: { orderId, status, amount, symbol, chain, uid, receiveAddress } } ``` ### Query Order Status ```typescript theme={null} async function getStatus(orderId: string) { const order = await xpay.getOrderStatus(orderId); return order.data; // Returns: { orderId, orderType, status, amount, actualAmount, transaction } } ``` ### Get Supported Symbols ```typescript theme={null} async function getSymbols() { const symbols = await xpay.getSupportedSymbols(); symbols.forEach(s => { console.log(`${s.symbol} on ${s.chain} (decimals: ${s.decimals})`); }); } // Filtered const filtered = await xpay.getSupportedSymbols('TRON', 'USDT'); ``` ## Handling Webhooks Use an Express.js endpoint to receive and verify webhook callbacks: ```typescript theme={null} import express from 'express'; import { XPay } from '@xpaylabs/node-sdk'; const app = express(); app.use(express.json()); const xpay = new XPay({ apiKey: process.env.XPAYLABS_MERCHANT_TOKEN!, apiSecret: process.env.XPAYLABS_API_SECRET!, baseUrl: process.env.XPAYLABS_GATEWAY_URL!, }); app.post('/webhook', (req, res) => { const body = JSON.stringify(req.body); const signature = req.body.sign; const timestamp = req.body.timestamp.toString(); const event = xpay.parseWebhook(body, signature, timestamp); if (!event) { return res.status(400).send('Invalid webhook signature or timestamp expired'); } switch (event.notifyType) { case 'ORDER_PENDING': console.log(`Order ${(event.data as any).orderId} created, awaiting payment`); break; case 'ORDER_PENDING_CONFIRMATION': console.log('Payment detected, awaiting confirmations'); break; case 'ORDER_SUCCESS': console.log(`Order ${(event.data as any).orderId} completed!`); // Fulfill the order break; case 'ORDER_EXPIRED': console.log('Order expired'); break; case 'ORDER_FAILED': console.log(`Order failed: ${(event.data as any).reason}`); break; case 'COLLECT_SUCCESS': console.log(`Collection completed: ${(event.data as any).collectAmount}`); break; } res.status(200).send('Webhook received'); }); app.listen(3000); ``` The webhook has built-in replay protection — events with timestamps older than 30 seconds are rejected. ## Error Handling ```typescript theme={null} try { const payout = await xpay.createPayout({ /* ... */ }); } catch (error: any) { console.error(`Error: ${error.message}`); console.error(`HTTP Status: ${error.status}`); console.error(`Error Code: ${error.code}`); console.error(`Additional Data: ${error.data}`); } ``` ## TypeScript Support The SDK ships with complete type declarations. Import the types directly: ```typescript theme={null} import { XPay, XPayConfig, CollectionRequest, PayoutRequest, CollectionResponse, PayoutResponse, OrderDetails, SupportedSymbol, WebhookEvent, OrderWebhookData, CollectWebhookData, OrderStatus, WebhookNotifyType, } from '@xpaylabs/node-sdk'; ``` The SDK handles HMAC-SHA256 signing internally — you don't need to compute signatures manually. All POST requests are automatically wrapped in the `ReqPayload` envelope with `sign`, `timestamp`, and `nonce`. # Send Crypto Payouts: XPayLabs Payout API Guide Source: https://docs.xpaylabs.com/guides/payouts Send crypto payments from your gateway wallet to external blockchain addresses using the XPayLabs payout API. Supports TRON, EVM chains, and SUI. Send crypto payments from your XPayLabs gateway wallet to any external blockchain address using the payout API. Supports USDT, USDC, and other stablecoins on TRON, EVM chains, and SUI. ## How to create a payout Call `POST /v1/order/createPayout` with the destination address, amount, symbol, and chain: ```javascript theme={null} import crypto from "crypto"; const GATEWAY_URL = "http://your-gateway:180"; 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 createPayout({ amount, symbol, chain, receiveAddress, orderId }) { const data = { amount, symbol, chain, receiveAddress, orderId, }; const payload = { sign: signRequest(data), timestamp: Math.floor(Date.now() / 1000), nonce: crypto.randomUUID(), data, }; const response = await fetch(`${GATEWAY_URL}/v1/order/createPayout`, { 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; } // Send 50 USDT on TRON to an external address const payout = await createPayout({ amount: "50.00", symbol: "USDT", chain: "TRON", receiveAddress: "TYourExternalAddress...", orderId: "payout_001", }); console.log(`Payout created: ${payout.orderId}`); ``` ### Request Parameters | Parameter | Required | Description | | ---------------- | -------- | ------------------------------------------ | | `amount` | Yes | Amount as decimal string (e.g., `"50.00"`) | | `symbol` | Yes | Token symbol (e.g., `"USDT"`) | | `chain` | Yes | Blockchain network | | `receiveAddress` | Yes | Destination blockchain address | | `orderId` | No | Your identifier for this payout | | `uid` | No | Your user identifier | ### Address Validation The gateway validates the `receiveAddress` format for the specified chain before processing: | Chain | Address Format | Example | | ----------------------- | ----------------------- | -------------------------------------------------------------------- | | TRON | Base58, starts with `T` | `TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR` | | EVM (ETH, BSC, POLYGON) | Hex, starts with `0x` | `0x05E833cF6a895a9ABe408FD6a8d5e0d3DB2Fec5A` | | SUI | Hex, starts with `0x` | `0x24f672b38299ae651f7598f4994ade780c38aeffa4e595c79ab73d3d176543cd` | If the address format is invalid, the API returns `"ReceiveAddress error"`. *** ## How to track payout status After creating a payout, track its status using the order status endpoint: ```javascript theme={null} async function getPayoutStatus(orderId, token) { const response = await fetch( `${GATEWAY_URL}/v1/order/status/${orderId}`, { headers: { "Content-Type": "application/json" } } ); const result = await response.json(); return result.data; } // Poll until completed const status = await getPayoutStatus("payout_001"); console.log(status.status); // "PENDING", "PENDING_CONFIRMATION", "SUCCESS", "FAILED" if (status.transaction) { console.log(`TxID: ${status.transaction.txid}`); } ``` ## What payout statuses are there? | Status | Description | | ---------------------- | ------------------------------------------ | | `PENDING` | Payout created, awaiting processing | | `PENDING_CONFIRMATION` | Transaction submitted to blockchain | | `SUCCESS` | Payout confirmed on-chain | | `FAILED` | Payout failed (insufficient balance, etc.) | *** ## What webhook events do payouts trigger? Payouts also trigger webhook events: | Event | When | | ---------------------------- | ------------------------- | | `ORDER_PENDING` | Payout order created | | `ORDER_PENDING_CONFIRMATION` | Transaction submitted | | `ORDER_SUCCESS` | Payout confirmed on-chain | | `ORDER_FAILED` | Payout failed | *** ## Important security notes * Blockchain transactions are irreversible. Double-check the `receiveAddress` before submitting. * The gateway validates address format but cannot verify the address is owned by the intended recipient. * Payouts consume blockchain gas fees (TRX for TRON, ETH gas for EVM chains). * Ensure your hot wallet has sufficient balance (including gas tokens) before initiating payouts. # Spring Boot + XPayLabs: Java SDK Crypto Payments Source: https://docs.xpaylabs.com/guides/spring-boot-integration Integrate crypto payments into your Spring Boot application with the XPayLabs Java SDK. Maven setup, configuration, webhook handling, and full API examples. Integrate crypto payments into your Spring Boot application using the official XPayLabs Java SDK (`xpay-java-sdk`). The SDK provides a comprehensive client for collection orders, payouts, webhook handling, and balance management with HMAC-SHA256 signing handled automatically. ## Installation Add the Maven dependency to your `pom.xml`: ```xml theme={null} com.xpaylabs xpay-java-sdk 0.1.0 ``` For Gradle: ```groovy theme={null} implementation 'com.xpaylabs:xpay-java-sdk:0.1.0' ``` ## Configuration Configure the SDK with your merchant token and API secret: ```java theme={null} import com.xpaylabs.sdk.XPay; import com.xpaylabs.sdk.XPayConfig; @Configuration public class XPayConfigBean { @Bean public XPay xPayClient() { XPayConfig config = XPayConfig.builder() .apiKey("your-merchant-token") .apiSecret("your-api-secret") .baseUrl("http://your-gateway:180") .connectTimeout(30000) .readTimeout(30000) .build(); return new XPay(config); } } ``` | Property | Required | Description | | ---------------- | -------- | -------------------------------------------------------------------------------------------- | | `apiKey` | Yes | Merchant token | | `apiSecret` | Yes | API secret for HMAC signing | | `baseUrl` | No | Gateway base URL (default: `https://api.xpaylabs.com`; override for self-hosted deployments) | | `connectTimeout` | No | Connection timeout in ms (default: 30000) | | `readTimeout` | No | Read timeout in ms (default: 30000) | ## Core API Methods ### Create a Collection Order ```java theme={null} import com.xpaylabs.sdk.model.request.CollectionRequest; import com.xpaylabs.sdk.model.response.ApiResponse; import com.xpaylabs.sdk.model.response.CollectionData; @RestController public class PaymentController { private final XPay xpay; public PaymentController(XPay xpay) { this.xpay = xpay; } public CollectionData createPayment(String uid, String amount) throws Exception { CollectionRequest request = CollectionRequest.builder() .amount(50.0) .symbol("USDT") .chain("TRON") .orderId("order-" + System.currentTimeMillis()) .uid(uid) .build(); ApiResponse response = xpay.createCollection(request); if (response.getCode() != 200) { throw new RuntimeException("Collection failed: " + response.getMsg()); } return response.getData(); // CollectionData fields: address, amount, symbol, chain, uid, orderId, expiredTime } } ``` ### Create a Payout ```java theme={null} import com.xpaylabs.sdk.model.request.PayoutRequest; import com.xpaylabs.sdk.model.response.PayoutData; public PayoutData sendPayout(String uid, String amount, String receiveAddress) throws Exception { PayoutRequest request = PayoutRequest.builder() .amount(100.0) .symbol("USDT") .chain("TRON") .orderId("payout-" + System.currentTimeMillis()) .uid(uid) .receiveAddress(receiveAddress) .build(); ApiResponse response = xpay.createPayout(request); return response.getData(); } ``` ### Query Order Status ```java theme={null} import com.xpaylabs.sdk.model.response.OrderDetails; public OrderDetails getStatus(String orderId) throws Exception { ApiResponse response = xpay.getOrderStatus(orderId); return response.getData(); // OrderDetails fields: orderId, orderType, status, amount, actualAmount, // transaction (chain, symbol, txid, from, to, amount, confirmedNum, ...) } ``` ### Get Supported Symbols ```java theme={null} import com.xpaylabs.sdk.model.response.SupportedSymbol; public List getSymbols() throws Exception { ApiResponse> response = xpay.getSupportedSymbols(); return response.getData(); } // Filtered by chain and symbol public List getSymbols(String chain, String symbol) throws Exception { ApiResponse> response = xpay.getSupportedSymbols(chain, symbol); return response.getData(); } ``` ### Get Merchant Balance ```java theme={null} import com.xpaylabs.sdk.model.request.MerchantBalanceRequest; import com.xpaylabs.sdk.model.response.MerchantBalanceData; public MerchantBalanceData getBalance(String symbol) throws Exception { MerchantBalanceRequest request = MerchantBalanceRequest.builder() .symbol(symbol) .build(); ApiResponse response = xpay.getMerchantBalance(request); return response.getData(); // Fields: symbol, balance, frozenBalance, totalBalance } ``` ### Get Deposit Address for a User ```java theme={null} import com.xpaylabs.sdk.model.request.CryptoAddressRequest; import com.xpaylabs.sdk.model.response.CryptoAddressData; public CryptoAddressData getDepositAddress(String uid, String chain, String symbol) throws Exception { CryptoAddressRequest request = CryptoAddressRequest.builder() .chain(chain) .symbol(symbol) .uid(uid) .build(); ApiResponse response = xpay.getCryptoAddress(request); return response.getData(); // Fields: chain, symbol, address } ``` ## Handling Webhooks The SDK provides built-in webhook signature verification and event parsing. Set up a controller to receive webhook callbacks: ```java theme={null} import com.xpaylabs.sdk.model.webhook.WebhookEvent; import com.xpaylabs.sdk.model.webhook.OrderWebhookData; import com.xpaylabs.sdk.model.webhook.CollectWebhookData; @RestController public class WebhookController { private final XPay xpay; public WebhookController(XPay xpay) { this.xpay = xpay; } @PostMapping("/webhook") public ResponseEntity handleWebhook(@RequestBody String body) { WebhookEvent event = xpay.parseWebhook(body); if (event == null) { return ResponseEntity.badRequest().body("Invalid webhook signature"); } switch (event.getNotifyType()) { case ORDER_PENDING: System.out.println("Order pending: " + ((OrderWebhookData) event.getData()).getOrderId()); break; case ORDER_PENDING_CONFIRMATION: System.out.println("Payment detected, awaiting confirmations"); break; case ORDER_SUCCESS: OrderWebhookData orderData = (OrderWebhookData) event.getData(); System.out.println("Order " + orderData.getOrderId() + " completed!"); // Fulfill the order break; case ORDER_EXPIRED: System.out.println("Order expired"); break; case ORDER_FAILED: OrderWebhookData failedData = (OrderWebhookData) event.getData(); System.out.println("Order failed: " + failedData.getReason()); break; case COLLECT_PENDING: System.out.println("Hot-to-cold sweep initiated"); break; case COLLECT_SUCCESS: CollectWebhookData collectData = (CollectWebhookData) event.getData(); System.out.println("Collection completed! Amount: " + collectData.getCollectAmount()); break; case COLLECT_FAILED: CollectWebhookData failedCollect = (CollectWebhookData) event.getData(); System.out.println("Collection failed: " + failedCollect.getReason()); break; } return ResponseEntity.ok("Webhook received"); } } ``` ## Error Handling The SDK throws `XPayApiException` for API errors: ```java theme={null} import com.xpaylabs.sdk.exception.XPayApiException; try { ApiResponse response = xpay.createPayout(request); } catch (XPayApiException e) { System.err.println("API Error: " + e.getMessage()); System.err.println("HTTP Status: " + e.getStatusCode()); System.err.println("Error Code: " + e.getErrorCode()); } catch (Exception e) { System.err.println("Unexpected error: " + e.getMessage()); } ``` ## Full Example See the [`XPayExample.java`](https://github.com/yan253319066/XPayLabs-java-sdk) class in the SDK repository for a complete example covering all API operations. The SDK uses HMAC-SHA256 for request signing. The `XPay` class handles this automatically — you only need to pass the request objects and it manages the `sign`, `timestamp`, and `nonce` fields internally. # Test Crypto Payments on Testnet: XPayLabs Guide Source: https://docs.xpaylabs.com/guides/testing Test XPayLabs payment integration on blockchain testnets before going live. Configure testnet endpoints, get faucet tokens, and verify the collection-to-webhook flow. Test your XPayLabs integration on blockchain testnets before processing mainnet transactions. Testnet networks simulate the real blockchain environment without using real funds — get test USDT from faucets and verify the complete collection-to-webhook flow risk-free. ## What testnets does XPayLabs support? | Testnet | Chain Value | Faucet | | ---------------- | ---------------- | --------------------------------------------------------------- | | TRON Shasta | `TRON_TEST` | [TRON Shasta Faucet](https://shasta.tronscan.org/) | | Ethereum Sepolia | `ETH_SEPOLIA` | [Sepolia Faucet](https://sepoliafaucet.com/) | | BSC Testnet | `BSC_TEST` | [BSC Testnet Faucet](https://testnet.bnbchain.org/faucet-smart) | | Polygon Amoy | `POLYGON_AMOY` | [Polygon Faucet](https://faucet.polygon.technology/) | | Avalanche Fuji | `AVAX_FUJI_TEST` | [Avalanche Faucet](https://faucet.avax.network/) | | SUI Testnet | `SUI_TEST` | [SUI Faucet](https://faucet.sui.io/) | ## Configuration Configure testnet RPC endpoints in your gateway configuration: ```yaml theme={null} xpay: blockchain: tron: mainnet: rpc-url: "https://api.trongrid.io" testnet: rpc-url: "https://api.shasta.trongrid.io" ethereum: sepolia: rpc-url: "https://rpc.sepolia.org" bsc: testnet: rpc-url: "https://data-seed-prebsc-1-s1.binance.org:8545" ``` ## How to test the full payment flow ### 1. Get Test Tokens Visit the faucet for your chosen testnet and request test USDT or native tokens (TRX, ETH, BNB). You'll need: * **Native gas tokens** (TRX, ETH, BNB) to pay blockchain fees * **Test USDT** to simulate customer payments ### 2. Create a Test Collection ```bash theme={null} curl -X POST http://your-gateway:180/v1/order/createCollection \ -H "Content-Type: application/json" \ -d '{ "sign": "", "timestamp": 1717000000, "nonce": "test_nonce_001", "data": { "amount": "10.00", "symbol": "USDT", "chain": "TRON_TEST", "orderId": "test_order_001" } }' ``` ### 3. Send Test USDT Send the exact amount of test USDT to the deposit address returned in the response. For TRON Shasta: * Use a test wallet (e.g., TronLink on Shasta network) * Send test USDT from a faucet or another test wallet ### 4. Verify the Webhook Your configured webhook endpoint should receive events: 1. `ORDER_PENDING` — Order created 2. `ORDER_PENDING_CONFIRMATION` — Transaction detected (within seconds) 3. `ORDER_SUCCESS` — Payment confirmed ### 5. Test a Payout ```bash theme={null} curl -X POST http://your-gateway:180/v1/order/createPayout \ -H "Content-Type: application/json" \ -d '{ "sign": "", "timestamp": 1717000000, "nonce": "test_nonce_002", "data": { "amount": "5.00", "symbol": "USDT", "chain": "TRON_TEST", "receiveAddress": "", "orderId": "test_payout_001" } }' ``` *** ## How to test edge cases | Scenario | How to Test | | -------------------------- | --------------------------------------------------------------- | | **Expired order** | Create a collection and wait for the expiry time | | **Wrong amount** | Send a different amount than requested (less than or more than) | | **Wrong token** | Send a different token to the deposit address | | **Invalid payout address** | Use an address from a different chain | | **Insufficient balance** | Try to create a payout larger than your wallet balance | | **Duplicate nonce** | Send the same request twice with the same nonce | ## Going Live When your integration is working correctly on testnet: 1. Configure mainnet RPC endpoints in the gateway configuration. 2. Switch chain values from testnet (e.g., `TRON_TEST`) to mainnet (e.g., `TRON`). 3. Fund your gateway hot wallet with real tokens and gas. 4. Test with a small real transaction before scaling up. Testnet USDT and gas tokens have no real value. You can be as liberal as you like with test transactions. Use this to thoroughly verify your webhook handling, order status polling, and error handling logic. # x402 Protocol — USDC Micropayments for AI Agents & LLM APIs Source: https://docs.xpaylabs.com/guides/x402-overview Pay-per-call USDC micropayments via the x402 protocol. One-line buyer SDK for AI agents, Express middleware for API sellers — no API key management required. The x402 protocol enables **pay-per-call USDC micropayments** for AI agents, LLM APIs, and any HTTP service. Buyers pay in USDC per request; sellers validate payments server-side with zero configuration overhead. ## How It Works 1. **Buyer** calls `pay(url, amount)` with the seller's URL and the expected USDC amount 2. The SDK negotiates a payment session with the x402 facilitator, submits an ERC-20 Transfer transaction on-chain, and attaches the authorization header to the request 3. **Seller** middleware (`@x402/express`) validates the header, extracts the payment details, and allows the request through 4. Both sides receive the on-chain transaction hash as proof of payment ## Buyer SDK (`@xpaylabs/x402`) Install: ```bash theme={null} npm install @xpaylabs/x402 ``` ### One-shot Payment ```typescript theme={null} import { pay } from '@xpaylabs/x402'; const response = await pay('https://api.example.com/weather', { amount: '0.001', // USDC amount network: 'base-sepolia', privateKey: '0x...', // buyer's EVM private key }); console.log(response.data); // response body console.log(response.paymentId); // on-chain tx hash ``` ### Session-based Client ```typescript theme={null} import { XPayClient } from '@xpaylabs/x402'; const client = new XPayClient({ privateKey: '0x...', network: 'base-sepolia', facilitatorUrl: 'https://x402-facilitator-testnet.vercel.app/api', }); const res1 = await client.request('https://api.example.com/weather', { amount: '0.001', }); const res2 = await client.request('https://api.example.com/weather', { amount: '0.001', }); ``` Each call pays independently. The client tracks the last `PAYMENT-RESPONSE` header for audit. ### Payment ID Extraction ```typescript theme={null} import { extractPaymentId } from '@xpaylabs/x402'; const paymentId = extractPaymentId(response.headers); console.log(paymentId); // "0xabc...123" ``` ## Seller Middleware (`@x402/express`) ```bash theme={null} npm install @x402/express ``` ```typescript theme={null} import express from 'express'; import { x402Middleware } from '@x402/express'; const app = express(); app.use(x402Middleware({ network: 'base-sepolia', facilitatorUrl: 'https://x402-facilitator-testnet.vercel.app/api', evmAddress: '0x...', // seller's USDC receiving address })); // Paid route — client must include valid x402 payment app.get('/api/generate', (req, res) => { // req.x402 contains decoded payment info res.json({ result: 'llm response', paymentId: req.x402.paymentId }); }); // Public route — no payment required app.get('/health', (req, res) => { res.json({ status: 'ok' }); }); ``` ## AI Agent Payment Flow x402 is designed for autonomous agent-to-API payments: ``` AI Agent (buyer) AI API (seller) │ │ │ 1. POST /api/generate │ │ (no payment yet) │ │ │ │ 2. 402 Payment Required │ │ { amount: "0.001" } │ │ │ │ 3. Submit USDC Transfer (on-chain)│ │ via x402 facilitator │ │ │ │ 4. retry with x402 auth header │ │ │ │ 5. 200 OK + response │ │ (paymentId in headers) │ ``` No API keys, no subscriptions, no billing dashboard — just pay-per-call with USDC. ## Related Resources * [Buyer SDK Repository](https://github.com/yan253319066/XPayLabs-x402) * [Seller Test Server](https://github.com/yan253319066/XPayLabs-x402-seller) * [x402 Protocol Spec](/guides/x402-workflow) # XPayLabs: Open-Source Multi-Tenant Crypto Payment Gateway Source: https://docs.xpaylabs.com/introduction XPayLabs is a self-hosted, non-custodial crypto payment gateway. Deploy on your own servers to accept stablecoin payments across TRON, EVM chains, and SUI with zero gateway fees. Multi-tenant — host unlimited merchants and collect fees. XPayLabs (often called **xpay**) is an open-source, self-hosted crypto payment infrastructure that lets you accept and manage stablecoin payments across multiple blockchains through a single API. **Multi-tenant by design** — a single deployment can host unlimited merchants, each with isolated API credentials and independent configuration. Set your own fee structure and earn from every transaction. Unlike hosted payment gateways, XPayLabs runs entirely on your infrastructure — your private keys stay on your servers, your funds go directly to your wallets, and you pay zero gateway fees. Process up to 100 transactions per second per gateway instance with no per-transaction fees — only blockchain gas costs ($0.01–$0.50 per transaction). XPayLabs is the **first self-hosted gateway** to support the [x402 protocol](/guides/x402-overview) for AI agent micropayments. Enable **pay-per-call USDC billing** for LLM APIs, AI agents, and any HTTP-based service — no API key management required. This is a capability no hosted competitor (BitPay, Coinbase Commerce, NowPayments) offers. Deploy your payment gateway and create your first collection in minutes Generate merchant tokens and sign your requests securely Explore every endpoint, parameter, and response field Step-by-step walkthroughs for collection and payout flows ## Supported Blockchains XPayLabs natively supports the following blockchain networks: | Chain | Type | Stablecoins | | --------------------- | ------ | ---------------- | | **TRON** | TRC20 | USDT, USDC | | **Ethereum** | ERC20 | USDT, USDC, DAI | | **BNB Chain** | BEP20 | USDT, USDC, BUSD | | **Polygon** | ERC20 | USDT, USDC, DAI | | **Arbitrum** | ERC20 | USDT, USDC | | **Optimism** | ERC20 | USDT, USDC | | **Base** | ERC20 | USDT, USDC | | **Avalanche C-Chain** | ERC20 | USDT, USDC | | **SUI** | Native | USDC | ## Architecture XPayLabs runs as 11 Docker containers orchestrated via Docker Compose. A single Nginx gateway serves as the public entry point, routing requests to backend services and frontend UIs. ```mermaid architecture theme={null} graph TD GATEWAY["nginx:alpine
Gateway :180"] MYSQL[("MySQL 8.0
:13306")] REDIS[("Redis 7
:16379")] GATEWAY -->|"/api/"| USER["xpay-user :8077
Collection API"] GATEWAY -->|"/prod-api/"| MERCHANT["xpay-merchant :8078
Admin API"] GATEWAY -->|"/checkout"| CHECKOUT["checkout :80
Vue 3"] GATEWAY -->|"/"| DASHBOARD["merchant-vue :80
Dashboard"] USER --> MYSQL USER --> REDIS MERCHANT --> MYSQL MERCHANT --> REDIS subgraph Blockchain Scanners ETH["xpay-eth :8076
EVM Scanner"] TRON["xpay-tron :8075
TRON Scanner"] SUI["xpay-sui :8074
SUI Scanner"] end ETH --> MYSQL TRON --> MYSQL SUI --> MYSQL SUI -->|RPC| SUI_PROXY["sui-node-service
:13001"] ``` *11 microservices: 1 gateway, 2 data stores, 3 blockchain scanners, 1 SUI RPC proxy, 2 backend APIs, 2 frontend UIs.* ## How does XPayLabs process payments? Run `docker compose up -d` on your server. All services start together — Java backends (xpay-user, xpay-merchant, xpay-eth, xpay-tron, xpay-sui), blockchain scanners, webhook dispatcher, and the checkout UI. Configure your merchant token in the gateway dashboard. This token is used to sign every API request with HMAC-SHA256. Send a `POST /v1/order/createCollection` request with amount, symbol, and chain. XPayLabs returns a unique deposit address and checkout URL. Register your callback URL. When the blockchain scanner detects the incoming transaction, XPayLabs delivers a signed webhook notification. ## Key Concepts | Concept | Description | | -------------- | ----------------------------------------------------------------------- | | **Collection** | A request for payment that generates a unique deposit address | | **Payout** | A funds transfer from your gateway wallet to an external address | | **Settlement** | Automatic sweep of received funds from hot wallets to your cold storage | | **Webhook** | An HTTP callback triggered by order or collection status changes | | **Scanner** | A blockchain indexer that monitors incoming transactions in real-time | Since XPayLabs is self-hosted, you have complete control over your funds. No third party holds your private keys, and no gateway fees are deducted from your transactions — you only pay blockchain network gas fees. # Pricing Source: https://docs.xpaylabs.com/pricing # Pricing — XPayLabs (xpay) ## Self-Hosted (Open Source) * Price: \$0/month * License: MIT * Fees: Zero gateway fees * Costs: Blockchain gas fees only (TRON \~$0.01, Ethereum ~$0.50–$5.00, BSC ~$0.05–$0.20, Polygon ~$0.01–$0.05, SUI ~$0.01) * Limits: Unlimited transactions, unlimited users * Features: All features included, full source code access, self-custody of keys and funds ## Enterprise * Price: Custom — contact [support@xpaylabs.com](mailto:support@xpaylabs.com) * Support: SLA, dedicated account manager, priority support * Deployment: Clustered/HA deployment, managed infrastructure options * Features: Custom blockchain integration, white-label checkout, advanced compliance # Deploy XPayLabs: Docker Compose Crypto Payment Quickstart Source: https://docs.xpaylabs.com/quickstart Deploy XPayLabs with Docker Compose, configure your merchant token, and create your first crypto payment collection in under 15 minutes. Deploy XPayLabs with Docker Compose and create your first collection order in under 15 minutes. By the end of this guide, you'll have a running crypto payment gateway on your own infrastructure with a working test collection. ## Prerequisites * A server with **8GB RAM / 4 vCPU** (Ubuntu 22.04+ recommended) * **Docker** and **Docker Compose** installed * RPC endpoints for the blockchains you want to support (TRON, EVM, SUI) *** ## How to deploy XPayLabs in 4 steps Clone the repository and start all services: ```bash theme={null} git clone https://github.com/yan253319066/XPayLabs-docker.git cd XPayLabs-docker docker compose up -d ``` This starts all services including Java backends (xpay-user, xpay-merchant, xpay-eth, xpay-tron, xpay-sui), blockchain scanners, and the checkout UI. Verify everything is running: ```bash theme={null} docker compose ps ``` All services should show a `Up` status. XPayLabs authenticates API requests using HMAC-SHA256 signatures. Your merchant token is generated in the gateway dashboard when you create your merchant account. The merchant token is a shared secret. It never appears in API requests directly — instead, you use it to sign request payloads. Store it securely and never commit it to version control. Send a `POST` request to create a collection order. The request body must include a `sign` field computed over the payload. ```bash theme={null} # Replace TOKEN with your merchant token curl --request POST \ --url http://your-server:180/v1/order/createCollection \ --header "Content-Type: application/json" \ --data '{ "sign": "generated_hmac_signature", "timestamp": 1717000000, "nonce": "unique_nonce_123", "data": { "amount": "100.00", "symbol": "USDT", "chain": "TRON", "orderId": "merchant_order_001" } }' ``` See the [Authentication](/authentication) page for how to compute the `sign` value. A successful response returns a deposit address and checkout URL: ```json theme={null} { "code": 200, "msg": "success", "data": { "address": "TWkKZkmuB8DpVeiMoHiKf99ZoFHzk73CqR", "amount": "100.00", "symbol": "USDT", "chain": "TRON", "orderId": "merchant_order_001", "expiredTime": 1717086400, "paymentUrl": "http://your-server/checkout?orderId=merchant_order_001" } } ``` The `address` field is where your customer sends funds. The `paymentUrl` leads to a hosted checkout page with a QR code and payment instructions. *** ## What's Next? Learn how the HMAC signing algorithm works and how to implement it in your language. Explore all endpoints for collections, payouts, and webhooks. During development, use testnet networks (TRON Shasta, ETH Sepolia, BSC Testnet) to avoid real gas costs. See the [Testing guide](/guides/testing) for testnet configuration and test token faucets. # Troubleshooting: XPayLabs Gateway Error Solutions Source: https://docs.xpaylabs.com/troubleshooting Diagnose and fix common XPayLabs issues — HMAC signature failures, blockchain scanner problems, webhook delivery errors, Docker deployment issues, and rate limiting. Diagnose and resolve common problems with your XPayLabs gateway deployment and API integration. This guide covers HMAC signature errors, blockchain scanner issues, webhook delivery failures, and Docker deployment problems. *** ## API Issues ### Why am I getting 401 sign verification failed? The HMAC-SHA256 signature does not match what the gateway computed. **Checklist:** 1. **JSON serialization** — Ensure the `data` object is serialized as **compact JSON** (no spaces, no newlines). `JSON.stringify(obj)` in JavaScript produces the correct format by default. 2. **Data content** — The `sign` is computed over only the `data` field, not the full request envelope. The `sign`, `timestamp`, and `nonce` fields are excluded. 3. **Token mismatch** — Confirm the `MERCHANT_TOKEN` used to compute the signature matches the token configured in the gateway. 4. **Character encoding** — Ensure the token and JSON string are UTF-8 encoded. 5. **Test your signature** — Compute the signature for a known payload and compare: ```javascript theme={null} const crypto = require("crypto"); const token = "your-merchant-token"; const data = { amount: "100.00", symbol: "USDT", chain: "TRON" }; const sign = crypto .createHmac("sha256", token) .update(JSON.stringify(data), "utf8") .digest("hex"); console.log(sign); // Should match what gateway expects ``` ### Why am I getting 400 validation errors? | Message | Likely Cause | | ------------------------------------- | ------------------------------------------------------------ | | `"The amount cannot be left blank."` | `amount` is `null`, `undefined`, or not a valid number | | `"The symbol cannot be left blank."` | `symbol` is missing or an empty string | | `"The chain cannot be left blank."` | `chain` is missing or not a valid chain value | | `"ReceiveAddress error"` | The payout address format does not match the specified chain | | `"The uid cannot be left blank."` | Your merchant account is V2 and requires `uid` | | `"The orderId cannot be left blank."` | Your merchant account is V3 and requires `orderId` | *** ## Blockchain Scanner Issues ## Why are blockchain payments not detected? If the scanner is not detecting incoming payments: 1. **Check scanner logs:** `docker logs ` 2. **Verify RPC endpoints:** Ensure the configured RPC URL is accessible from the gateway server. 3. **Check block height:** The scanner syncs from the last processed block. If the gateway was recently deployed, it may need time to catch up. 4. **Confirm token configuration:** Verify the token contract address is correctly configured for the chain. 5. **Check blockchain explorer:** Confirm the transaction actually exists on-chain at the expected address. ### Why is the scanner slow? * **RPC rate limits:** Public RPC endpoints may throttle high-frequency requests. Consider using dedicated RPC endpoints. * **Block time:** Different chains have different block times. TRON produces blocks every 3 seconds, Ethereum every 12 seconds. * **Confirmation requirements:** Configure the required number of block confirmations per chain in the gateway settings. *** ## Webhook Problems ## Why am I not receiving webhooks? 1. **Verify callback URL:** Check the `callback-url` in your gateway configuration. 2. **Network accessibility:** The gateway must be able to reach your callback URL. If testing locally, use a tunneling tool like ngrok. 3. **Check gateway logs:** `docker compose logs xpay-user` to see webhook delivery attempts. 4. **Check your endpoint logs:** Ensure your server is listening on the correct path and returning HTTP 200. ### Why is webhook signature verification failing? 1. **Raw data vs. full payload:** Verify the signature over the `data` field only, not the full `NotifyPayload`. 2. **Webhook secret mismatch:** Confirm the `webhook-secret` used for verification matches what is configured in the gateway. 3. **JSON format:** The `data` field must be serialized as compact JSON for verification. *** ## Deployment Issues ## Why are Docker containers not starting? ```bash theme={null} # Check container status docker compose ps # View logs for a specific service docker compose logs xpay-user docker compose logs xpay-tron ``` Common causes: * Port `180` already in use * Insufficient disk space * Missing environment variables in configuration ### Why is the gateway not responding? 1. **Verify container is running:** `docker compose ps` should show `Up` status. 2. **Check port binding:** `docker compose port gateway 180` 3. **Test locally:** `curl http://localhost:180/v1/symbol/supportSymbols` 4. **Check firewall:** Ensure port 180 is open in your server firewall. *** ## Rate Limiting If you receive `429 Too Many Requests`: | Endpoint | Default Limit | | ------------------------------ | ---------------------- | | `POST /order/createCollection` | 100 req / 10s per IP | | `POST /order/createPayout` | 100 req / 10s per IP | | `GET /order/pay` | 2 req / 1s per orderId | | `GET /order/getOrderStatus` | 2 req / 1s per orderId | Implement exponential backoff in your integration: ```javascript theme={null} async function fetchWithRetry(url, options, retries = 5) { for (let i = 0; i < retries; i++) { const response = await fetch(url, options); if (response.status !== 429) return response; const delay = Math.pow(2, i) * 1000 + Math.random() * 1000; await new Promise(r => setTimeout(r, delay)); } throw new Error("Max retries exceeded"); } ``` *** ## Getting Help If you cannot resolve your issue: 1. Check the container logs for error messages. 2. Review the [FAQ](/faq) for common questions. 3. Open a GitHub issue at [github.com/xpaylabs/gateway](https://github.com/xpaylabs/gateway). 4. For commercial support, contact [support@xpaylabs.com](mailto:support@xpaylabs.com). When reporting an issue, include: * Gateway version and deployment method * Relevant configuration (redact secrets) * Complete error messages and logs * Steps to reproduce