A cryptocurrency payment gateway is a service layer that turns an on-chain transfer into a merchant-recognizable payment event: it creates a payment intent, watches the chain for the expected amount to the expected address, applies a confirmation policy, and reports settlement. This article explains the architecture behind that flow, then walks through a runnable integration pattern with payment intents, idempotent webhook handling, and a reconciliation job. It separates documented gateway behavior from the confirmation and monitoring decisions you must make and measure yourself. The goal is a payment flow that is correct under retries, chain reorgs, underpayment, and late payment — not just one that works in a happy-path demo.
What a crypto payment gateway actually does
A cryptocurrency payment gateway sits between a customer's wallet and your order system. It does not move funds through a card network; it observes blockchain state and translates a transfer into a payment event your backend can act on. The core responsibilities are consistent across providers: create a payment intent with a fixed amount and destination, monitor the relevant chain for matching transfers, apply a confirmation threshold, and notify the merchant through a webhook or polling API.
The important architectural point is that the gateway is an oracle over chain state, not a bank. Your order system must still decide what counts as paid, how to handle underpayment or overpayment, and how to reconcile gateway records against your own ledger. Treat the gateway's webhook as a claim that needs verification, not as an authoritative balance update.
- Payment intent: amount, asset, destination address, expiry, and a merchant reference.
- Detection: matching transactions by address, asset, amount, and time window.
- Confirmation policy: how many blocks or what finality rule before marking paid.
- Settlement: conversion to fiat or stablecoin, custody model, and payout schedule.
- Reporting: transaction IDs, fees, and exportable statements for reconciliation.
Assumptions and prerequisites
This guide assumes you have a backend service that can receive HTTPS webhooks, a database with unique constraints, and a way to run scheduled jobs. You need gateway API credentials and, for self-hosted or non-custodial setups, access to a reliable RPC endpoint for the chains you accept. If you are still choosing an endpoint provider, the RPC Assistant covers concrete provider and network recommendations; this article stays on integration methodology.
You should also know your settlement model before writing code. Custodial gateways settle to a merchant balance and pay out on a schedule; non-custodial gateways send funds directly to a wallet you control. The settlement model changes your reconciliation source of truth, so decide it early. For background on node infrastructure that supports chain monitoring, see the blockchain node hosting guide.
The payment lifecycle as a state machine
Model every payment as an explicit state machine rather than a boolean paid flag. A workable set of states is created, awaiting_payment, detected, confirmed, underpaid, overpaid, expired, and refunded. Transitions should be driven by verified events and recorded with timestamps so you can audit them later.
The gateway's own status values will differ in naming, so map them into your states at the boundary. Keep the raw gateway payload alongside your normalized state. When a dispute or reconciliation mismatch appears, the raw payload is the evidence you will need.
const PAYMENT_STATES = [
'created',
'awaiting_payment',
'detected',
'confirmed',
'underpaid',
'overpaid',
'expired',
'refunded'
];
// Allowed transitions; reject anything not listed.
const TRANSITIONS = {
created: ['awaiting_payment', 'expired'],
awaiting_payment: ['detected', 'expired'],
detected: ['confirmed', 'underpaid', 'overpaid', 'expired'],
confirmed: ['refunded'],
underpaid: ['refunded', 'confirmed'],
overpaid: ['refunded'],
expired: ['detected'],
refunded: []
};
function canTransition(from, to) {
return (TRANSITIONS[from] || []).includes(to);
}Creating a payment intent with a server-side call
Create payment intents only on your server, never in client-side code, because the request carries your API key and the amount must not be user-controlled. Send a unique merchant reference so retries are idempotent. The example below uses placeholder field names; adapt them to your gateway's documented API.
Always persist the gateway's payment ID and the destination address before returning the checkout response to the client. If your process crashes after the gateway creates the intent but before you store it, you will have an orphaned payment that is hard to reconcile.
curl -X POST "$GATEWAY_API_URL/v1/payments" \
-H "Authorization: Bearer $GATEWAY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042-attempt-1" \
-d '{
"amount": "49.99",
"currency": "USD",
"asset": "USDC",
"network": "ethereum",
"merchant_reference": "order-1042",
"callback_url": "https://shop.example.com/webhooks/crypto",
"expires_in": 900
}'Confirmation policy: the decision you must measure
The confirmation threshold is the single most consequential parameter in a crypto gateway integration. Too low and a reorg or a replace-by-fee transaction can reverse a payment you already fulfilled; too high and customers wait unnecessarily. There is no universal number, and any figure quoted without context should be treated as a starting hypothesis, not a fact.
Define your policy per chain and per order value. High-value orders can require more confirmations or a finality-aware rule; low-value digital goods may accept fewer. Document the policy, then measure it against real data: record the block height at detection, the block height at confirmation, and any reorg events observed. The table below is a template to fill with your own measurements, not a set of recommended values.
- Record time from broadcast to first detection for each chain.
- Record time from detection to your confirmation threshold.
- Log any reorg depth observed and whether it affected a payment.
- Track the share of payments that expire before detection.
- Review the policy quarterly against observed reorg and latency data.
Verifying webhooks before trusting them
Webhook endpoints are public, so anyone can POST to them. Verify the signature using the shared secret your gateway provides, compare with a constant-time function, and reject stale timestamps to limit replay. Then load the payment by ID from your own database and confirm the event is a legal transition before applying it.
Make the handler idempotent: gateways retry webhooks, and the same event may arrive several times. A unique constraint on the gateway event ID is the simplest defense.
import crypto from 'node:crypto';
function verifySignature(rawBody, signatureHeader, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(signatureHeader || '', 'utf8');
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}
// Express-style handler (raw body required for HMAC).
app.post('/webhooks/crypto', express.raw({ type: '*/*' }), async (req, res) => {
const sig = req.get('X-Gateway-Signature');
if (!verifySignature(req.body, sig, process.env.GATEWAY_WEBHOOK_SECRET)) {
return res.status(401).send('bad signature');
}
const event = JSON.parse(req.body.toString('utf8'));
await db.query(
'INSERT INTO gateway_events (event_id, payload) VALUES ($1, $2) ON CONFLICT DO NOTHING',
[event.id, event]
);
await applyPaymentEvent(event); // checks state machine + idempotency
res.status(200).send('ok');
});Monitoring chain state directly with an RPC endpoint
Custodial gateways usually handle chain monitoring for you, but non-custodial setups and reconciliation jobs need their own view of the chain. A minimal monitor polls the latest block and scans for transfers to your addresses. Use a dedicated RPC endpoint rather than a public one for production, because rate limits and inconsistent head heights will cause missed or duplicated detections.
The script below is a diagnostic, not a production indexer: it prints the current head and a sample of recent block numbers so you can confirm your endpoint is advancing and consistent. Compare two endpoints if you suspect lag. For endpoint selection, see RPC pricing and plans and the multi-chain RPC endpoints guide.
import requests, time
RPC_URL = "RPC_URL" # e.g. your provider's HTTPS endpoint
def rpc(method, params):
r = requests.post(RPC_URL, json={
"jsonrpc": "2.0", "id": 1, "method": method, "params": params
}, timeout=10)
r.raise_for_status()
body = r.json()
if "error" in body:
raise RuntimeError(body["error"])
return body["result"]
head = int(rpc("eth_blockNumber", []), 16)
print("head:", head)
for offset in range(5):
block = rpc("eth_getBlockByNumber", [hex(head - offset), False])
print(block["number"], block["hash"][:18], len(block["transactions"]), "txs")
time.sleep(0.2)Expected verification results
After wiring the flow, run a test payment on a testnet or the gateway's sandbox. You should observe: the intent created with a stable ID, the webhook delivered and accepted with a valid signature, the payment transitioning through awaiting_payment to detected and then confirmed, and your order marked fulfilled exactly once even when you replay the webhook manually.
For the RPC diagnostic, the head should increase between runs and block hashes should be stable for blocks several confirmations deep. If the head stalls or jumps backward, your endpoint is unhealthy or you are hitting a load balancer with inconsistent state. Record these observations in a short runbook so on-call engineers can repeat them.
Troubleshooting common integration failures
Most production incidents fall into a few categories: signature mismatches, duplicate fulfillment, missed payments, and amount mismatches. Work through them in that order because each can mask the next.
- Signature mismatch: confirm you are hashing the raw body, not the parsed JSON, and that the secret matches the environment.
- Duplicate fulfillment: check the unique constraint on event ID and that state transitions reject repeats.
- Missed payment: verify the address and asset match the intent, and that your monitor is not lagging behind the chain head.
- Underpayment: compare the received amount to the intent amount at the exchange rate locked at creation; decide whether to accept, request the difference, or refund.
- Late payment after expiry: keep the intent open for a grace period and reconcile manually rather than auto-fulfilling.
- Webhook never arrives: poll the gateway's payment status as a fallback and alert when polling and webhooks disagree.
Reconciliation and limitations
Reconciliation is where crypto gateways differ most from card processors. Build a daily job that pulls the gateway's transaction list for the period, joins it to your orders by merchant reference, and flags any order whose state disagrees with the gateway. Store the gateway's fees and conversion rates so your ledger matches settlement.
Be honest about limitations. Confirmation thresholds reduce but do not eliminate reorg risk. Exchange-rate volatility between intent creation and settlement can create small mismatches. Custodial gateways introduce counterparty risk, while non-custodial gateways push key management and chain monitoring onto you. None of these are reasons to avoid crypto payments, but each needs an explicit owner and a documented fallback.
Next steps for a production-ready integration
Move from a working demo to a production system by adding three things: a per-chain confirmation policy backed by your own measurements, an idempotent webhook pipeline with a polling fallback, and a reconciliation job that runs on a schedule. Then load-test the webhook endpoint and simulate reorgs in a staging environment.
If you operate your own monitoring or settlement infrastructure, review dedicated node options and the networks we support to match endpoint capacity to your payment volume. For the concrete question of which endpoint or provider to use for a given chain, ask the RPC Assistant rather than guessing from a list. Finally, document your refund and dispute process before launch — it is the part customers notice first when something goes wrong.