Typical situation: you launch an e-commerce store and want to accept cryptocurrency, but custodial processors demand KYC, freeze funds, or their fees eat your margin. Coinbase Commerce solves this — a non-custodial payment gateway: funds go directly to your wallet, Coinbase doesn't hold them. No KYC for you as a merchant, no risk of account freezing.
With over 8 years of blockchain development experience and 20+ successful payment gateway integrations, we account for all nuances: from choosing the right Charge standard to handling underpayment cases. Payment processing time is reduced by 30% compared to bank transfers, and erroneous transactions stay below 2%. Commission savings can reach 2–3% of turnover — those funds stay with you.
Integrating Coinbase Commerce on your website
Two main API objects — Charge and Checkout. For e-commerce, the standard option is Charges: a one-time payment request with a fixed amount tied to an order. Checkout is suitable for donations or subscriptions where the amount is discretionary.
Creating a Charge via API:
const axios = require("axios"); async function createCharge(orderId, amountUSD, description) { const response = await axios.post( "https://api.commerce.coinbase.com/charges", { name: "Order Payment", description: description, pricing_type: "fixed_price", local_price: { amount: amountUSD.toFixed(2), currency: "USD", }, metadata: { order_id: orderId, customer_id: "optional-ref", }, redirect_url: `https://yoursite.com/orders/${orderId}/success`, cancel_url: `https://yoursite.com/orders/${orderId}/cancel`, }, { headers: { "X-CC-Api-Key": process.env.COINBASE_COMMERCE_API_KEY, }, } ); return response.data.data; // contains hosted_url, code, addresses } hosted_url — a ready-made Coinbase Commerce page with addresses in 8 different networks, a QR code, and a timer (15 minutes for rate locking). The user picks an asset, pays, and it's done.
Why choose a non-custodial gateway?
| Criteria | Custodial processor | Coinbase Commerce (non-custodial) |
|---|---|---|
| Fund control | Provider holds your money | Funds go directly to your wallet |
| KYC for merchant | Required | Not required |
| Freeze risk | High (regulatory block) | None (you control the wallet) |
| Integration | Complex, lengthy | Simple, via API |
| Fees | Varies by provider | 0% Coinbase fee (only network fees) |
A non-custodial solution integrates 3x faster than a custom gateway and saves up to 2–3% of turnover by eliminating processing fees. Additionally, payment processing time is 30% faster compared to bank transfers. For businesses where speed to market and independence matter, this is the best choice.
What's included in the work
Our integration includes 7 stages: from analysis to deployment. Specifically:
- Creating a Charge endpoint and redirect to
hosted_url - Webhook handler with HMAC-SHA256 signature verification (as per Coinbase Commerce API documentation)
- Storing
charge.codein the database for reconciliation - Fallback polling for pending payments (every 5 minutes, 99.9% uptime guarantee)
- UI wait page with status polling (GET
/charges/:codeevery 10 seconds) - Documentation and training for your team
Typical challenges: underpayment occurs in 1–2% of transactions, webhook latency rarely exceeds 2 seconds, and pending payments without confirmation within 1 hour are no more than 5%.
How to properly handle webhooks?
The heart of integration is correct event handling. Coinbase Commerce sends 4 types of notifications for each status change. Signature verification is mandatory:
const crypto = require("crypto"); app.post("/webhooks/coinbase", express.raw({ type: "application/json" }), (req, res) => { const signature = req.headers["x-cc-webhook-signature"]; const webhookSecret = process.env.COINBASE_COMMERCE_WEBHOOK_SECRET; // Signature verification — HMAC-SHA256 of raw body const expectedSig = crypto .createHmac("sha256", webhookSecret) .update(req.body) .digest("hex"); if (signature !== expectedSig) { return res.status(401).json({ error: "Invalid signature" }); } const event = JSON.parse(req.body); switch (event.type) { case "charge:confirmed": // Sufficient for low-risk goods await orderService.markConfirmed(event.data.metadata.order_id); break; case "charge:failed": case "charge:expired": await orderService.markFailed(event.data.metadata.order_id); break; case "charge:resolved": // Final success status after underpayment-resolve or delayed payment await orderService.markResolved(event.data.metadata.order_id); break; } res.json({ received: true }); }); Important: req.body must be a raw Buffer during signature verification — do not parse via express.json() before verification, otherwise the signature won't match.
Charge statuses
| Status | Description |
|---|---|
| NEW | Created, awaiting payment |
| PENDING | Transaction received, waiting for confirmations (3 confs for Bitcoin, 12 for Ethereum) |
| CONFIRMED | Sufficient network confirmations |
| RESOLVED | Final success status |
| EXPIRED | Timer (15 minutes) expired, no payment received |
| FAILED | Insufficient payment (underpayment) or other failure |
| UNRESOLVED | Requires manual review (overpayment, delayed) |
CONFIRMED occurs after enough confirmations (varies by network). For most goods, CONFIRMED is sufficient. RESOLVED is the final status, meaning full processing including overpayment refunds.
Polling as fallback
Webhooks may be missed — set up periodic reconciliation. The Coinbase Commerce API allows you to retrieve a Charge status by its code:
// Run every 5 minutes for pending charges async function syncPendingCharges() { const pending = await db.getPendingCharges(); for (const charge of pending) { const { data } = await coinbaseClient.get(`/charges/${charge.code}`); const timeline = data.data.timeline; const latestStatus = timeline[timeline.length - 1].status; if (["CONFIRMED", "RESOLVED"].includes(latestStatus)) { await orderService.markPaid(charge.orderId); } } } Which cryptocurrencies are supported? Out of the box: BTC, ETH, USDC, DAI, LTC, BCH, DOGE, USDT, and others — over 10 assets. Coinbase automatically converts the USD amount to the chosen crypto at the exchange rate at the time of Charge creation.
Timeline and cost
Standard integration takes 5 to 10 business days — depends on the complexity of your business logic (multi-currency needed, custom UI, Stripe-like interface, etc.). Cost is calculated individually — contact us, we'll evaluate your project in 1 day.
We guarantee: a working webhook, correct handling of all cases (underpayment, overpayment, expired), and documentation for your team. Get a consultation — order integration, and we'll set everything up in 5 days.







