Crypto Payment API Integration Guide for Developers (2026)
Crypto Payment API Integration Guide for Developers (2026)
Why Integrate Crypto Payments?
| Benefit | What it means for you |
|---|---|
| No processor risk | Funds settle directly to your wallet; you never hold fiat with a third‑party acquirer. |
| Global reach | Anyone with an internet connection and a crypto wallet can pay — no country‑specific banking limits. |
| Lower fees | ChainPay charges a flat 0.8 % per transaction versus 2.9 % + typical card processors. |
| No chargebacks | Crypto payments are irreversible once confirmed, eliminating fraud‑related disputes. |
| Instant settlement (optional) | Choose to sweep received funds to your hot or cold wallet immediately after confirmation. |
These advantages make crypto a compelling addition — especially for SaaS, digital goods, gaming, and cross‑border marketplaces.
Core Concepts Before You Start### Payment address generation (HD wallets, unique per order)
ChainPay derives a fresh receiving address from a hierarchical deterministic (HD) wallet for every orderId. This guarantees privacy and prevents address reuse attacks. The address is tied to the order until it expires or is marked as confirmed.
Blockchain confirmation (how many confirms = safe for which amounts)
| Crypto | Recommended confirms | Typical time (mainnet) |
|---|---|---|
| Bitcoin (BTC) | 3–6 | 10‑30 min |
| Ethereum (ETH) | 12–20 | 2‑5 min |
| USDT‑TRC20 | 1 | < 10 sec |
| USDC‑ERC20 | 12 | 2‑5 min |
| Others | Check ChainPay docs | Varies |
For low‑value orders (< $50) a single confirmation on fast chains (TRC20, SOL) is usually enough. High‑value orders should wait for the chain‑specific threshold above.
Webhooks vs polling (always use webhooks)
Polling introduces latency and unnecessary load. ChainPay pushes a signed JSON payload to your webhookUrl whenever an order’s state changes (pending → confirmed, expired, underpaid, etc.). Webhooks guarantee near‑real‑time notification and let you keep your backend lightweight.
Handling underpaid/overpaid orders
- Underpaid: You can automatically accept the shortfall (if within a tolerance you define) or reject and notify the user to send the remainder. ChainPay returns
amountReceivedso you can decide. - Overpaid: The excess stays in the receiving address. You may refund it manually (see Step 5) or let the user keep it as a credit — just be clear about your policy in the UI.
Step 1: Get Your API Keys
- Sign up at chainpay.pro (free tier available).
- In the dashboard, create a new App → give it a name, select the environments (sandbox/production).
- Copy the API Key (Bearer token) and the Webhook Secret (used to verify signatures).
- Store these securely — never commit them to source control. Use environment variables or a secret manager.
Step 2: Create a Payment Order
The POST /api/v1/orders endpoint returns a unique crypto address, a QR‑code image (base64 PNG), and an expiration timestamp.
// Node.js (≥18) – using native fetch
async function createOrder() {
const response = await fetch('https://chainpay.pro/api/v1/orders', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHAINPAY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 49.99, // fiat amount you want to receive
currency: 'USD', // fiat denomination for pricing
crypto: 'USDT_TRC20', // settlement chain/token
orderId: 'ORDER_123', // your internal identifier
webhookUrl: 'https://yourapp.com/webhook/payment',
}),
});
if (!response.ok) {
const err = await response.json();
throw new Error(`Order creation failed: ${err.message}`);
}
const { paymentAddress, qrCode, expiresAt } = await response.json();
return { paymentAddress, qrCode, expiresAt };
}
| Field | Type | Description |
|---|---|---|
paymentAddress | string | Destination address for the user to send funds |
qrCode | string (base64) | PNG image ready to embed in an <img src="data:image/png;base64,…"> |
expiresAt | ISO‑8601 timestamp | When the address will no longer accept payments |
Step 3: Display Payment to User
Show the address, QR code, and a live countdown timer. Convert the fiat amount to crypto using the rate ChainPay provides (you can also fetch it from a market API if you prefer).
<div id="payment-widget">
<h2>Pay $49.99 with USDT (TRC20)</h2>
<p>Amount in crypto: <span id="cryptoAmount">…</span> USDT</p>
<p>Address: <code id="addr">…</code></p>
<img id="qr" alt="QR code" />
<p>Expires in: <span id="timer">…</span></p>
</div>
<script>
async function loadPayment() {
const { paymentAddress, qrCode, expiresAt } = await fetch('/api/create-order')
.then(r => r.json());
document.getElementById('addr').textContent = paymentAddress;
document.getElementById('qr').src = `data:image/png;base64,${qrCode}`;
// Optional: fetch crypto amount from ChainPay's rate endpoint const rateResp = await fetch(
`https://chainpay.pro/api/v1/rate?from=USD&to=USDT_TRC20&amount=49.99`
);
const { cryptoAmount } = await rateResp.json();
document.getElementById('cryptoAmount').textContent = cryptoAmount.toFixed(6);
// Countdown const expires = new Date(expiresAt);
function updateTimer() {
const now = new Date();
const diff = expires - now;
if (diff <= 0) {
document.getElementById('timer').textContent = 'Expired';
return;
}
const mins = Math.floor(diff / 60000);
const secs = Math.floor((diff % 60000) / 1000);
document.getElementById('timer').textContent = `${mins}m ${secs}s`;
setTimeout(updateTimer, 1000);
}
updateTimer();
}
loadPayment();
</script>
Step 4: Handle Webhook Notifications
ChainPay signs each payload with HMAC‑SHA256 using your webhook secret. Verifying the signature prevents spoofed callbacks.
// Express.js example
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.CHAINPAY_WEBHOOK_SECRET;
// Helper: verify signature
function verifySignature(req) {
const sig = req.headers['x-chainpay-signature'];
if (!sig) return false;
const hm = crypto.createHmac('sha256', WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest('hex');
return sig === hm;
}
app.post('/webhook/payment', (req, res) => {
if (!verifySignature(req)) {
console.warn('Invalid webhook signature');
return res.status(401).end();
}
const