Developer Guide

How to Accept Crypto Payments: A Developer's Guide to Self-Hosted Infrastructure

A complete, step-by-step walkthrough for accepting cryptocurrency payments using a self-hosted, non-custodial gateway. From Docker deployment to webhook integration — everything you need to start accepting USDT, USDC, and other crypto assets with zero transaction fees and full control over your private keys.

Why Accept Crypto Payments?

Cryptocurrency payments have evolved from a niche experiment to a mainstream payment rail processing billions of dollars daily. For merchants, accepting crypto is no longer about speculation — it is about accessing a faster, cheaper, and more inclusive financial system. Here is why businesses are adding crypto checkout to their payment stack:

Global Reach

Crypto payments work across borders with no currency conversion, no cross-border fees, and no bank account requirements. Anyone with a wallet can pay you, regardless of their country, banking status, or credit score. This opens your business to the 1.4 billion unbanked adults worldwide and the rapidly growing crypto-native consumer base.

Low Fees

Traditional payment processors charge 1.5–3.5% plus a per-transaction fee. A self-hosted crypto gateway charges 0% transaction fees — you only pay network gas fees. On TRON, a USDT transfer costs ~$0.02–$0.10. On an L2 like Arbitrum, it is often under $0.01. For a business processing $100k/month, the savings exceed $20k/year.

No Chargebacks

Blockchain transactions are irreversible. Once a payment is confirmed on-chain, it cannot be reversed or charged back. This eliminates the #1 cost for online merchants: payment fraud and dispute fees. For digital goods merchants where chargeback rates can exceed 5%, switching to crypto can dramatically improve bottom-line margins.

Financial Privacy

With a self-hosted, non-custodial gateway, you retain complete control over your funds and financial data. No KYC requirements, no payment processor account freezes, and no third party with the power to restrict your access. Your customers' payment data stays between them and the blockchain — no payment processor databses to leak or monetize.

Self-Hosted vs Third-Party Gateways

Before diving into setup, it is important to understand the tradeoffs between self-hosted and hosted (third-party) crypto payment gateways. Each model serves different needs:

AspectThird-Party (Hosted)Self-Hosted
Setup TimeMinutes (SaaS signup)30 minutes (Docker deploy)
Transaction Fees1 – 3% per transaction0% (gas only)
Monthly Cost$30 – $300+ plan fees$10 – $20 (VPS hosting)
Key CustodyCustodial (processor holds keys)Non-custodial (you hold keys)
SettlementDelayed (daily/weekly batches)Instant (direct to wallet)
KYC RequiredYes (business verification)No (self-sovereign)
Account Freeze RiskYes (processor decides)None (you control access)
White-Label CheckoutProcessor branding requiredFull white-label (your brand)
Source AccessClosed-sourceFully auditable
MaintenanceNone (managed service)You manage updates/monitoring

Self-hosted gateways like XPay Labs are ideal for developers and businesses that want maximum control, minimal fees, and sovereign infrastructure. Third-party gateways make sense for non-technical users who prefer a fully managed solution and are willing to pay for convenience.

Prerequisites

Before you begin, ensure you have the following:

  • 1
    A VPS or cloud server — Linux machine with at least 1 GB RAM, 20 GB storage, and a public IP. Providers like Hetzner ($5/month), DigitalOcean ($12/month), or AWS EC2 work well.
  • 2
    Docker & Docker Compose — installed on your server. Most VPS images come with Docker pre-installed, or you can use the official install script.
  • 3
    A domain name — pointed to your server IP for the gateway API and checkout page with SSL via Let's Encrypt or a reverse proxy like Caddy/Nginx.
  • 4
    RPC API keys — for the blockchains you want to support. TronGrid for TRON, Alchemy or Infura for EVM chains, and a SUI RPC provider. Most have free tiers.
  • 5
    Basic DevOps knowledge — familiarity with the command line, environment variables, and the ability to configure a reverse proxy for TLS termination.

Step 1: Deploy the Core Node

The fastest way to start accepting crypto payments is with Docker Compose. Create a directory on your VPS and save the following as docker-compose.yml:

version: '3.8'
services:
  xpay-gateway:
    image: ghcr.io/xpay-labs/gateway:latest
    container_name: xpay_core
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      - XPAY_SEED_PHRASE=${XPAY_SEED_PHRASE}
      - XPAY_HMAC_SECRET=${XPAY_HMAC_SECRET}
      - XPAY_TRON_RPC=https://api.trongrid.io
      - XPAY_EVM_RPC=https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_KEY}
      - XPAY_WEBHOOK_URL=https://api.yourdomain.com/webhooks/xpay
    volumes:
      - ./data:/app/data
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

Run docker compose up -d to start the gateway. On your first launch, XPay Labs generates a new HD wallet seed if XPAY_SEED_PHRASE is not set, or derives addresses from your existing seed. The gateway exposes a REST API on port 8080 and immediately begins scanning configured chains for incoming transactions.

Step 2: Configure Environment

The gateway requires several environment variables to operate. Create a .env file in the same directory as your docker-compose.yml:

VariableRequiredDescription
XPAY_SEED_PHRASEYesBIP-39 mnemonic seed (12–24 words) for HD wallet derivation. Generate offline with 'openssl rand -hex 16 | xargs -I echo ' or use a hardware wallet.
XPAY_HMAC_SECRETYesHMAC key for signing webhook payloads. Use a 64-character hex string. Verify webhooks on your server using this secret.
XPAY_TRON_RPCOptionalTRON full node URL. Defaults to https://api.trongrid.io. For production, use your own TronGrid API key or a dedicated node.
XPAY_EVM_RPCOptionalEVM RPC URL for the chain you want to support. For multi-chain, specify each chain separately (e.g., XPAY_EVM_RPC_ETH, XPAY_EVM_RPC_BNB).
XPAY_WEBHOOK_URLOptionalDefault callback URL for all payments. Can be overridden per invoice. Your server must return 200 OK to confirm receipt.
XPAY_CONFIRMATIONSOptionalNumber of block confirmations required before triggering a webhook. Default: 19 for TRON, 12 for EVM chains. Lower for faster but riskier detection.
# .env
XPAY_SEED_PHRASE="abandon abandon apple ... twelve words here"
XPAY_HMAC_SECRET="a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1"
XPAY_TRON_RPC=https://api.trongrid.io
XPAY_EVM_RPC_ETH=https://eth-mainnet.g.alchemy.com/v2/your_key
XPAY_EVM_RPC_BNB=https://bsc-dataseed.binance.org
XPAY_WEBHOOK_URL=https://api.yourdomain.com/webhooks/xpay
XPAY_CONFIRMATIONS=19

Step 3: Generate API Keys

With the gateway running, generate a merchant API key to authenticate your payment requests. Use the admin endpoint:

curl -X POST "http://localhost:8080/v1/admin/api-keys" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${XPAY_ADMIN_TOKEN}" \
  -d '{
    "label": "production-checkout",
    "permissions": ["payments:create", "payments:read"]
  }'

The response includes an API key prefixed with xpay_live_. Store this securely — it replaces username/password authentication for all payment API calls. You can generate multiple keys with different permission scopes for development, staging, and production environments.

// Example response
{
  "id": "key_7f3d9c2a1b",
  "label": "production-checkout",
  "key": "xpay_live_8f3a9d7219bc4e5f6a7b8c9d0e1f2a3b4c5d6e7f",
  "permissions": ["payments:create", "payments:read"],
  "created_at": "2026-05-22T10:30:00Z"
}

Step 4: Create Your First Payment

Now you can create a payment invoice. Your backend calls this endpoint when a customer reaches checkout:

curl -X POST "https://gateway.yourdomain.com/v1/payments" \
  -H "Authorization: Bearer xpay_live_8f3a9d7219bc" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "100.00",
    "currency": "USDT",
    "chain": "TRON",
    "order_id": "order_783120",
    "callback_url": "https://api.merchant.com/v1/webhooks/xpay",
    "customer_email": "[email protected]",
    "metadata": {
      "product_id": "prod_456",
      "plan": "premium_annual"
    }
  }'

The gateway responds with a unique deposit address derived from your seed, an invoice ID, and the current payment status:

{
  "id": "pay_9f3b8c2a1d",
  "order_id": "order_783120",
  "amount": "100.00",
  "currency": "USDT",
  "chain": "TRON",
  "deposit_address": "TYpSq7f8MubE8bK6vG7m8F7WbA9c3DxE1F",
  "qr_code": "https://gateway.yourdomain.com/v1/qr/pay_9f3b8c2a1d",
  "status": "pending",
  "expires_at": "2026-05-22T11:00:00Z",
  "created_at": "2026-05-22T10:00:00Z"
}

Display the deposit address and QR code to your customer. The gateway monitors the blockchain for incoming transactions to this address. Once detected and confirmed, it sends a webhook to your callback URL and updates the invoice status to "confirmed".

Key insight: Each invoice gets a unique deposit address. No address reuse means no privacy leaks and no payment collision risks. The same address will never be assigned to two different invoices — even for the same customer.

Step 5: Handle Webhook Callbacks

XPay Labs sends payment confirmation webhooks to your callback_url via POST with an HMAC-SHA256 signature in the X-Signature header. Here is how to verify and process the payload in Node.js:

import crypto from 'crypto';

// Your HMAC secret from XPAY_HMAC_SECRET
const HMAC_SECRET = process.env.XPAY_HMAC_SECRET;

export async function POST(request: Request) {
  const body = await request.text();
  const signature = request.headers.get('x-signature');

  // Verify HMAC-SHA256 signature
  const expected = crypto
    .createHmac('sha256', HMAC_SECRET)
    .update(body, 'utf8')
    .digest('hex');

  if (signature !== expected) {
    return Response.json({ error: 'invalid signature' }, { status: 401 });
  }

  const payload = JSON.parse(body);

  // payload structure:
  // {
  //   "event": "payment.confirmed",
  //   "payment": {
  //     "id": "pay_9f3b8c2a1d",
  //     "order_id": "order_783120",
  //     "amount": "100.00",
  //     "currency": "USDT",
  //     "chain": "TRON",
  //     "tx_id": "b4f7c3a12d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2",
  //     "from_address": "TXYZ...",
  //     "to_address": "TABC...",
  //     "confirmations": 32,
  //     "status": "confirmed",
  //     "timestamp": 1715875200
  //   }
  // }

  if (payload.event === 'payment.confirmed') {
    // Fulfill the order
    await fulfillOrder(payload.payment.order_id, payload.payment);
    return Response.json({ received: true });
  }

  return Response.json({ received: true });
}

Webhooks are sent with exponential backoff retry (3 attempts: 10s, 60s, 300s) until your server returns a 200 OK. Implement idempotency checks using the payment.id to prevent double-fulfillment in case of duplicate webhook deliveries.

Supported Chains & Tokens

XPay Labs supports three blockchain ecosystems out of the box, covering the vast majority of stablecoin and altcoin payment volume. Each chain has unique characteristics that affect transaction speed, cost, and integration approach:

TRON (TRC-20)

TRON is the dominant chain for stablecoin payments, hosting over $50 billion in TRC-20 USDT — more than any other blockchain. Its 3-second block time and sub-cent transaction fees make it ideal for high-volume merchant payments. TRC-20 USDT accounts for the majority of all on-chain stablecoin transfer volume.

  • Block time: ~3 seconds, finality at 19 blocks (~57s)
  • Fee per USDT transfer: ~0.2–1 TRX ($0.02–$0.10) with energy management
  • Supported tokens: USDT, USDC, USDD, TUSD, and any TRC-20 token
  • RPC: TronGrid API (free tier: 10k requests/day) or your own full node
  • Address format: Base58 T-addresses (starts with "T")

EVM Chains (ERC-20)

XPay Labs supports all EVM-compatible chains including Ethereum, BNB Chain, Polygon, Arbitrum, Optimism, and Base. While Ethereum mainnet fees can be high during congestion, L2 networks like Arbitrum and Base offer sub-cent transaction costs with Ethereum-level security guarantees.

  • Block time: 2–15s depending on chain (12s ETH mainnet, 2s Polygon, 0.25s Arbitrum)
  • Fee per USDC transfer: ~$0.50–$5 (ETH), ~$0.0003 (Polygon), ~$0.01 (Arbitrum)
  • Supported tokens: USDT, USDC, DAI, and any ERC-20 token per chain
  • RPC: Alchemy, Infura, public nodes, or your own execution client
  • Address format: 0x-hex addresses (same across all EVM chains)

SUI

SUI is an emerging Layer 1 blockchain built on the Move language with parallel execution and sub-second finality. While its stablecoin ecosystem is still growing, SUI offers the fastest transaction finality of any supported chain and is gaining traction among crypto-native merchants and users.

  • Block time: ~1 second (immediate finality via Narwhal/Bullshark consensus)
  • Fee per transaction: ~0.001 SUI (~$0.001) — fractions of a cent
  • Supported tokens: SUI, USDC (native), and custom Move-based assets
  • RPC: SUI public endpoints or your own full node
  • Address format: 0x-prefixed hex addresses
Multi-chain routing: If your customer pays on a different chain than expected, XPay Labs can detect cross-chain payments by scanning all configured chains for the invoice amount. Enable this in the config with multi_chain_scan: true. The gateway normalizes amounts based on token decimals and fires the webhook once any configured chain receives the correct payment.

Next Steps

You now have a fully functional self-hosted crypto payment gateway. Here is what to explore next to get the most out of your infrastructure:

Ready to Accept Crypto Payments?

Deploy XPay Labs on your own infrastructure in under 30 minutes. Zero transaction fees, non-custodial security, and support for TRON, EVM, and SUI chains. No signup required — just Docker.