Build a Crypto Invoice System with REST API: From Zero to Automated Payments

Tutorial API Invoice

In June 2026, Coinbase officially entered India's $3 billion crypto market with local INR trading — proof that global crypto payment demand is expanding far beyond the US and Europe into Latin America, Southeast Asia, and South Asia. But hosted payment platforms have limited geographic coverage: your customer might be in a country Coinbase Commerce doesn't support. Build your own crypto invoice system and let anyone, anywhere, pay you in crypto — no waiting for a platform to expand into your market, no 1% fee, and no handing over your private keys.

Why Not Just Use a Hosted Platform's Invoice API?

Coinbase Commerce, CoinGate, and OpenNode all offer invoice APIs — sign up, grab an API key, and create invoices with a few lines of code. For solo devs, it's genuinely convenient. But three months in, you hit three problems:

Problem 1: Fees eat your margin

Hosted platforms charge a flat 1% fee. A merchant processing $50K/month pays $6,000/year — enough for three dedicated servers. And 1% is just the sticker price: exchange rate spreads (the platform quotes 0.5-1% below market), withdrawal minimums (Coinbase Commerce's 0.001 BTC minimum means small merchants wait weeks to withdraw) are hidden costs. Real cost is closer to 2% — it's just not on the pricing page.

Problem 2: Your customers might not be in the service area

Coinbase Commerce doesn't support dozens of countries including China, Iran, Cuba, and Syria. OpenNode is mostly North America and Europe. If you run a global business, some fraction of your customers will hit a blocked payment page — and they won't tell you "your payment page doesn't load," they'll just go elsewhere. A self-hosted invoice system is only limited by your own server's jurisdiction, not a third party's country policy.

Problem 3: Platforms freeze accounts without explanation

In 2025, Coinbase froze thousands of accounts for compliance reasons — some users waited weeks to regain access. Under a hosted model, the payment gateway holds your private keys. The platform freezes your account, you stop getting paid. Self-hosted invoice system: keys live on your server, the gateway software is open source, and no intermediary can unilaterally cut off your payment flow. Read Why Non-Custodial Matters.

Hosted vs Self-Hosted Invoice Systems: The Full Picture

Solution API Fee Key Control Geo Restrictions Customization
Coinbase CommerceREST API1%Coinbase holds keys30+ countries blockedLow — locked to Coinbase UI
CoinGateREST API1%CoinGate holds keysSome countries excludedLow — fixed payment page
OpenNodeREST API1%OpenNode holds keysLimited countriesLow — BTC/Lightning focused
Self-Hosted XcashREST APIZero✅ You hold keys✅ Global✅ Fully customizable

Hosted solutions win on zero setup — but you trade 1% of every transaction and full custody for that convenience. Once your volume crosses a few thousand dollars a month, that 1% starts to hurt.

Three Components of a Crypto Invoice System

A complete crypto invoice system needs exactly three pieces. None are complicated:

  • Payment gateway. Generates addresses, monitors on-chain transactions, confirms payments. This is the core of a self-hosted setup — Xcash runs in Docker and supports Bitcoin and 100+ EVM chains
  • REST API. Your backend calls the API to create invoices, check payment status, and fetch transaction details. Standard REST — any language works
  • Webhook notifications. Once an on-chain payment is confirmed, the gateway proactively notifies your backend: "this invoice just got paid." No polling needed — just listen

The flow is simple: customer places order → your backend calls the API to create an invoice → gateway returns payment address + amount → customer pays → gateway detects the on-chain transaction → webhook fires to your backend → order status flips to "paid." Zero third parties in the loop.

Step 1: Deploy the Payment Gateway with Docker

Using Xcash as an example, deployment takes one docker-compose file. Run this on your server:

docker pull xcash/xcash:latest
docker run -d -p 8000:8000 \
  -v xcash-data:/data \
  -e XCASH_BTC_ENABLED=true \
  -e XCASH_EVM_ENABLED=true \
  -e XCASH_API_KEY=replace-with-your-secure-key \
  xcash/xcash:latest

Two commands, three minutes. After deploy, hit http://your-server-ip:8000/health to confirm it's running. For the full setup guide (Nginx reverse proxy, HTTPS, firewall), see Deploy a Crypto Payment Gateway in 3 Minutes with Docker.

Step 2: Create Your First Invoice with the REST API

Once the gateway is live, creating an invoice is a single POST request. Here's the curl example — swap in your backend's HTTP client in production:

curl -X POST https://your-server:8000/api/v1/invoices/ \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": "100.00", "currency": "USDT", "chain": "bsc", "order_id": "ORDER-12345", "callback_url": "https://your-site.com/webhook"}'

The API responds with JSON containing the payment address, amount, and expiry:

{
  "id": "inv_a1b2c3d4",
  "status": "pending",
  "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb1",
  "amount": "100.00",
  "currency": "USDT",
  "chain": "bsc",
  "expires_at": "2026-06-01T10:00:00Z",
  "payment_url": "https://your-server:8000/pay/inv_a1b2c3d4"
}

Show payment_url to your customer — it renders a payment page with the amount, chain, and address. They scan or paste into their wallet and pay. Key fields:

  • amount — payment amount as a string (avoids floating-point precision bugs)
  • currency — token symbol: USDT, USDC, ETH, BTC, DAI, etc.
  • chain — chain identifier: ethereum, bsc, polygon, arbitrum, optimism, tron, etc. Omit to let the gateway pick a default
  • order_id — your internal order reference. The gateway doesn't care what this is — pure pass-through for reconciliation
  • callback_url — webhook endpoint. The gateway POSTs here when payment is confirmed

Step 3: Handle Payment Webhooks

After the customer pays (on-chain confirmation), the gateway POSTs to your callback_url. Your backend needs a listener:

POST https://your-site.com/webhook
Content-Type: application/json
X-Signature: sha256=abc123...

{
  "event": "invoice.paid",
  "invoice_id": "inv_a1b2c3d4",
  "order_id": "ORDER-12345",
  "amount_paid": "100.00",
  "currency": "USDT",
  "tx_hash": "0x7a1b...3f2e",
  "chain": "bsc",
  "confirmations": 3
}

When your backend receives this webhook:

  1. Verify the X-Signature header — HMAC-SHA256 with your API key. This confirms the notification is from your gateway, not forged
  2. Look up the order by order_id
  3. Compare amount_paid against the order amount — customers sometimes underpay
  4. Update order status to "paid"
  5. Return HTTP 200 so the gateway knows you got it

If you don't return 200, the gateway retries at 5 seconds, 30 seconds, and 5 minutes. Three failures and it marks the delivery as failed — you can manually re-push from the admin panel.

Beyond Basics: Invoice Management Features

Auto-Expiring Invoices

Set expires_in (in seconds) when creating an invoice. If unpaid by expiry, the gateway marks it expired and fires an invoice.expired webhook. Your backend can release inventory or cancel order holds. Typical values: 600 seconds (10 min) for digital goods, 3600 seconds (1 hour) for physical.

Handling Underpayments and Overpayments

On-chain payments rarely match the invoice amount exactly — gas fees, user error, or split payments cause drift. The API returns amount_paid as the actual received amount. Your backend decides the tolerance:

  • Underpaid by more than 5%: keep order pending, trigger manual review or refund
  • Underpaid by 5% or less: accept as full payment — good for returning customers
  • Overpaid: record the difference as account credit or auto-refund (requires additional refund logic)

Multi-Currency Invoices: Pay a BTC Invoice in USDT

In the real world, customers hold different tokens than what you price in. Xcash lets you specify multiple accepted_currencies on an invoice:

curl -X POST https://your-server:8000/api/v1/invoices/ \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount": "100.00", "currency": "USD", "accepted_currencies": ["USDT", "USDC", "BTC", "ETH"], "chain": "bsc", "order_id": "ORDER-12346"}'

The customer sees USDT, USDC, BTC, and ETH as payment options on the checkout page — the gateway auto-converts at real-time rates. Your backend books it in USD; the customer pays in whatever token they hold.

Xcash vs Other Self-Hosted Solutions

BTCPay Server also has an invoice API — it's feature-complete and the established player in this space. Key differences:

  • Setup complexity: BTCPay requires a Bitcoin full node (600GB+ storage) and Lightning node. Initial sync takes days. Xcash doesn't need a full node — Docker deploy boots in 3 minutes
  • Chain coverage: BTCPay excels at Bitcoin and Lightning. EVM chains need extra configuration. Xcash natively supports 100+ EVM chains
  • API design: BTCPay's API is more powerful (pull payments, crowdfunds) but has a steeper learning curve. Xcash's API does one thing: create invoices and receive payments

If you primarily take Bitcoin payments and need Lightning, BTCPay is the better choice. If you need multi-chain coverage, fast deployment, and minimal learning curve, Xcash is easier. Full comparison: BTCPay Server vs Xcash.

The Full Flow, Summarized

  1. Deploy Xcash with Docker (3 minutes)
  2. POST /api/v1/invoices to create an invoice (one curl command)
  3. Give the payment_url to your customer
  4. Wait for the invoice.paid webhook
  5. Update order status, fulfill the order

Five steps from zero to automated crypto payments. No Coinbase, no CoinGate, no third-party platform. Your server, your keys, your customer data — all under your control.

Xcash is an MIT-licensed open-source self-hosted multi-chain crypto payment gateway. Bitcoin and 100+ EVM chains, zero platform fees, full key control, one Docker command to deploy. Code is fully open source on GitHub. API docs at xca.sh/en/docs.

FAQ

How much engineering effort does an invoice system take?

Deploying the gateway: 3 minutes. API integration depends on your backend stack — Python, Node.js, or Go, it's one HTTP call and one webhook endpoint, under 50 lines of code total. If you already have a backend that handles HTTP requests, you can have the full flow running in an afternoon.

Bitcoin confirmations take 10 minutes. What if my customers won't wait?

For time-sensitive use cases, use stablecoins (USDT/USDC) on fast chains — BSC confirms in 3 seconds, Polygon in 2 seconds, TRC-20 in 3 seconds. You'll get the webhook within seconds of the customer hitting send. Save Bitcoin confirmation requirements for large settlements ($1000+). The API supports a confirmations parameter: 0-conf for instant notification, 3-conf for security — choose based on your risk tolerance.

Which cryptocurrencies are supported?

Bitcoin plus every EVM-compatible chain: Ethereum, BSC, Polygon, Arbitrum, Optimism, Base, Avalanche C-Chain, Fantom, Gnosis Chain, Linea, Scroll — 100+ chains. USDT, USDC, DAI, and all ERC-20 tokens are automatically supported. Full comparison: BTCPay Server vs Xcash.

How is this different from Stripe's crypto payments?

Stripe's crypto payments go through a third-party custodian (Stripe essentially resells a hosted provider's API), charge 1-2% in fees, and Stripe holds your customer payment data. A self-hosted invoice system: zero platform fees, customer payment data stays on your server, no intermediaries. If you care about "writing less code," use Stripe. If you care about "paying less in fees" and "controlling your own data," self-host.


Related Posts