A crypto payment API is the programming layer that turns a blockchain transfer into a trackable order: it creates a payment request, watches the chain for the matching transfer, and notifies your backend when funds arrive. The hard parts are not the initial API call but the lifecycle around it: idempotency, webhook authenticity, confirmation depth, underpayment and overpayment, and reconciliation against your own ledger. This guide walks through a provider-agnostic integration model with runnable request and webhook examples, a verification procedure you can reproduce in a sandbox, and the tradeoffs between hosted gateways and self-managed node infrastructure. Concrete endpoint and provider selection is intentionally left to RPC Assistant; this page focuses on how to build and test the flow.
Scope and the mental model behind a crypto pay API
A crypto payment API sits between your order system and the blockchain. Your backend asks it to create a payment request for a known amount and currency, the customer sends funds from their own wallet, and the API reports back when a matching transfer is observed. Unlike card rails, crypto transfers are push payments: the customer initiates the movement, and there is no issuer that can reverse it. That single property explains most of the design differences you will encounter.
The useful mental model is a four-stage lifecycle. Create: your system registers an invoice with an amount, a reference, and an expiry. Observe: the provider watches addresses or mempools and matches incoming transfers to that invoice. Confirm: the transfer is counted only after a policy-defined number of blocks or a finality signal. Settle: your ledger marks the order paid, and optionally a payout or conversion step follows. Every integration bug you will debug lives in one of these four stages.
This article is provider-agnostic. Field names differ between gateways, but the lifecycle, the security requirements, and the verification procedure are stable. When you need to choose a specific endpoint or network, use the RPC Assistant rather than hard-coding assumptions from a tutorial.
- Push payments: the customer sends funds; your system never pulls from their wallet.
- Irreversibility: refunds are new outbound transactions, not reversals.
- Asynchrony: payment confirmation arrives via webhook or polling, not in the create response.
- Finality variance: Bitcoin, Ethereum, and fast-finality chains need different confirmation policies.
Prerequisites and assumptions before you write code
You need a sandbox or testnet account with the payment provider, an API key pair (usually a public key plus a secret used for signing), a publicly reachable HTTPS endpoint for webhooks, and a persistent store for invoice state. You also need a clear answer to two policy questions before integration: which assets and networks you will accept, and how many confirmations count as settled for each of them.
Assume your backend is the only trusted component. Browser clients should never hold the secret key, and webhook handlers must treat every incoming request as untrusted until the signature is verified. If you operate your own chain infrastructure for verification, you will also need a reliable RPC endpoint; the multi-chain RPC endpoints guide covers how to structure that access, and OnFinality's API service describes managed access if you prefer not to run nodes.
Creating an invoice with an idempotent request
The create call is the only synchronous step in the flow, and it is the one most likely to be retried by your own infrastructure. Always send an idempotency key derived from your internal order ID so a network timeout does not produce two invoices for one order. The example below uses placeholder credentials and a generic REST shape; adapt field names to your provider's reference.
curl -X POST "$PAY_API_BASE/v1/invoices" \
-H "Authorization: Bearer $PAY_API_KEY" \
-H "Idempotency-Key: order-10482" \
-H "Content-Type: application/json" \
-d '{
"amount": "49.00",
"currency": "USD",
"asset": "USDT",
"network": "ethereum",
"reference": "order-10482",
"expires_in": 900,
"callback_url": "https://api.example.com/webhooks/pay"
}'Handling the webhook: verify before you trust
Webhook payloads are the primary signal that funds arrived, but they are also the most common source of fraud and double-crediting bugs. Verify the signature against the raw request body, not a parsed and re-serialized object, because re-serialization changes byte order and breaks HMAC checks. Then check the event ID against a deduplication store before applying any state change.
The handler below shows the pattern in Node.js: read the raw body, compute an HMAC with the shared secret, compare in constant time, deduplicate, then transition the order. The exact header name and signing scheme vary by provider; substitute yours.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
const seen = new Set(); // replace with a durable store
app.post('/webhooks/pay', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.get('X-Pay-Signature') || '';
const expected = crypto
.createHmac('sha256', process.env.PAY_WEBHOOK_SECRET)
.update(req.body)
.digest('hex');
const ok = signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!ok) return res.status(401).send('bad signature');
const event = JSON.parse(req.body.toString('utf8'));
if (seen.has(event.id)) return res.status(200).send('duplicate');
seen.add(event.id);
// Only act on the terminal state, not intermediate ones.
if (event.type === 'payment.confirmed') {
// enqueue order transition; do not do heavy work inline
}
res.status(200).send('ok');
});Confirmation policy and the verification procedure
A payment is not settled when the transfer appears in the mempool; it is settled when your policy says it is. Define a per-network confirmation threshold and record it with each invoice so you can audit later why an order was marked paid. For fast-finality chains the threshold may be a finality gadget signal rather than a block count; for proof-of-work chains it is a depth.
To verify your integration end to end, run this procedure in a sandbox and record the observed values in a table. Do not copy numbers from any guide, including this one; measure your own.
- Create an invoice and confirm the response contains a unique ID and a payment address or URI.
- Send a test transfer below the invoice amount and confirm the API reports an underpayment state, not paid.
- Send the remaining amount and confirm the state moves to pending, then confirmed after your threshold.
- Replay the same webhook twice and confirm your handler credits the order exactly once.
- Expire an unpaid invoice and confirm late transfers are flagged for manual review rather than auto-credited.
Reconciling the provider ledger against your own
Webhooks can be missed, delayed, or delivered out of order. A scheduled reconciliation job that lists invoices in a time window and compares them to your order table is the safety net. Treat the provider as the source of truth for on-chain observation and your database as the source of truth for fulfillment; the job's purpose is to find and repair divergence.
The Python sketch below pulls recent invoices and flags any that are confirmed at the provider but not fulfilled locally. Run it on a schedule and alert on non-empty output rather than auto-fulfilling silently.
import os, requests
base = os.environ["PAY_API_BASE"]
headers = {"Authorization": f"Bearer {os.environ['PAY_API_KEY']}"}
resp = requests.get(f"{base}/v1/invoices", headers=headers,
params={"status": "confirmed", "limit": 100}, timeout=15)
resp.raise_for_status()
for inv in resp.json().get("data", []):
if not local_order_is_fulfilled(inv["reference"]):
print("DIVERGENCE", inv["id"], inv["reference"], inv["amount"])Troubleshooting the failures that actually happen
Most production incidents fall into a small set of patterns. Signature failures usually mean the body was parsed before verification or the secret belongs to a different environment. Duplicate credits usually mean the dedup store is in-memory and resets on deploy. Stuck pending invoices usually mean the customer sent on a different network than the invoice specified, or the amount fell below the provider's dust threshold.
Underpayment and overpayment deserve explicit handling. Decide in advance whether an underpayment within a tolerance is accepted, and whether an overpayment is refunded or credited. Late transfers after expiry should route to manual review, never to automatic fulfillment. If your own chain reads are part of the flow, intermittent RPC errors can look like missing payments; the RPC endpoints guide explains retry and failover patterns that prevent that class of false negative.
- 401 on webhook: raw body not preserved, or wrong environment secret.
- Order credited twice: dedup keyed on payload hash rather than event ID, or non-durable store.
- Invoice never confirms: wrong network selected by customer, or amount below threshold.
- Reconciliation noise: timezone mismatch between provider timestamps and your window query.
Hosted gateway versus self-managed node infrastructure
A hosted crypto payments API gives you invoice management, address derivation, confirmation tracking, and often conversion or payout in one contract. You trade control and per-transaction cost for speed of integration and offloaded operational risk. A self-managed approach, where you run nodes and watch addresses yourself, gives you full custody of the observation layer and no third-party dependency for confirmation logic, but you own uptime, reorg handling, and chain-specific quirks.
The decision usually hinges on how many networks you support and how much of the settlement logic is core to your product. If payments are a feature, a gateway is usually correct. If payment observation is the product, self-managed infrastructure with a reliable RPC layer is worth the cost. For teams going the self-managed route, dedicated node capacity removes noisy-neighbor effects that cause missed blocks, and RPC pricing helps model the cost per network before committing.
Limitations and tradeoffs to accept up front
No integration removes blockchain finality risk; it only shifts where you measure it. Confirmation thresholds trade latency for safety, and the right value depends on transaction size, not just the chain. Webhook delivery is at-least-once in most systems, so idempotency is mandatory rather than optional. Currency conversion, if offered, introduces a rate at a point in time that must be stored with the invoice for dispute resolution.
There is also a compliance surface that engineering cannot design away: know-your-customer obligations, sanctions screening, and record retention vary by jurisdiction and by whether you custody funds. Treat those as product requirements that constrain which providers and networks you can accept, not as afterthoughts.
Next steps for a production-ready flow
Move from sandbox to production in stages: enable one asset on one network, run the verification procedure against real low-value transactions, and watch reconciliation output for a week before expanding. Add alerting on webhook signature failures, reconciliation divergence, and invoices stuck in pending beyond your expected confirmation time. Document your confirmation policy per network so support staff can explain why an order is not yet fulfilled.
When you need to pick concrete endpoints or confirm which networks a provider supports, ask the RPC Assistant for a direct recommendation instead of inferring from marketing pages. For background on the infrastructure underneath, the blockchain node hosting guide and the network directory are useful starting points.