To integrate Replit with the Coinbase Commerce API, create a Commerce account, generate an API key, store it in Replit Secrets (lock icon π), and use your Python or Node.js backend to create crypto payment charges and receive webhook notifications when payments are confirmed. Deploy on Autoscale for webhook processing or Reserved VM for continuous payment monitoring.
Why Connect Replit to Coinbase Commerce?
Coinbase Commerce lets any business accept cryptocurrency payments without needing to manage wallets or understand blockchain infrastructure directly. By integrating Commerce into a Replit backend, you can add a 'Pay with Crypto' option to your web app, SaaS product, or marketplace β generating a unique payment address for each transaction and receiving a webhook notification when funds are confirmed on-chain.
The Commerce API is significantly simpler than the Binance trading API: you create a 'charge' object with a name, description, and price, and Commerce returns a hosted payment page URL you can redirect customers to. Customers choose which cryptocurrency to pay with, and Commerce monitors the blockchain for the incoming transaction. Your Replit backend receives webhook events as the payment progresses through states: NEW (charge created), PENDING (payment detected on-chain), CONFIRMED (sufficient confirmations received), and FAILED (underpaid or unresolved).
Replit's Secrets system (lock icon π in the sidebar) keeps your API key and webhook shared secret encrypted and out of your codebase. The webhook verification step is critical β always validate the X-CC-Webhook-Signature header on every incoming request to prevent fraudulent payment confirmations from being injected by third parties.
Integration method
You connect Replit to Coinbase Commerce by generating an API key in the Commerce dashboard, storing it in Replit Secrets, and calling the Commerce REST API from your server-side code to create payment charges and retrieve payment status. Webhook delivery notifies your Replit backend when a charge transitions to 'confirmed' or 'failed', and you verify each webhook using an HMAC-SHA256 signature computed with your shared secret. All Commerce API calls happen server-sideβnever from a browser clientβto keep your API key protected.
Prerequisites
- A Replit account with a Python or Node.js project created
- A Coinbase Commerce account (https://commerce.coinbase.com) β free to sign up
- Basic familiarity with webhooks and REST API concepts
- Node.js 18+ or Python 3.10+ (both available on Replit by default)
- An understanding of cryptocurrency payment confirmations and blockchain latency
Step-by-step guide
Create a Coinbase Commerce Account and Generate API Keys
Create a Coinbase Commerce Account and Generate API Keys
Go to https://commerce.coinbase.com and create a free account. Once logged in, navigate to Settings β API keys. Click 'Create an API key' and copy the generated key β it will only be shown once in full, though you can view the last four characters later. Still in Settings, scroll to the Webhook Subscriptions section. Click 'Add an endpoint' and enter the URL where your Replit app will receive webhooks. For development, you can use a placeholder URL and update it after deploying. After saving, Coinbase Commerce generates a 'Shared Secret' for this webhook endpoint β copy this value as well. You will use it to verify the HMAC-SHA256 signature on every incoming webhook. Coinbase Commerce does not charge a monthly fee; it takes a small transaction fee per completed payment. Your Commerce account comes with a sandbox testing mode accessible from the dashboard, but note that Commerce does not have a true staging environment with fake blockchain transactions β use small live amounts for integration testing, or mock the webhook events locally by manually sending test payloads to your endpoint.
Pro tip: Copy both the API key and the webhook shared secret immediately after creation. If you lose either, you can generate a new API key from Settings β API keys, and a new webhook secret from the webhook settings page.
Expected result: You have a Coinbase Commerce API key and a webhook shared secret saved and ready to add to Replit Secrets.
Store Credentials in Replit Secrets
Store Credentials in Replit Secrets
Open your Replit project and click the lock icon π in the left sidebar to open the Secrets pane. Add two secrets: - Key: COINBASE_COMMERCE_API_KEY β Value: your Commerce API key - Key: COINBASE_WEBHOOK_SECRET β Value: your webhook shared secret Click 'Add Secret' after each entry. These values are encrypted with AES-256 and injected as environment variables at runtime. They never appear in your source files, Git commits, or deployment exports. Replit's Secret Scanner will alert you if it detects a Commerce API key pattern in your code files. If you are building a multi-environment setup (development and production), create two separate Commerce accounts or two sets of API keys β one for testing small real transactions and one for production. Store them as separate secret pairs (e.g., COINBASE_COMMERCE_API_KEY_DEV vs COINBASE_COMMERCE_API_KEY_PROD) and use a single ENVIRONMENT variable to select which set your code uses.
Pro tip: Webhook signatures are computed using the raw request body, not the parsed JSON. Make sure your webhook route receives the raw body bytes before any JSON parsing middleware runs.
Expected result: COINBASE_COMMERCE_API_KEY and COINBASE_WEBHOOK_SECRET appear in your Replit Secrets pane with values hidden.
Create Charges and Verify Webhooks in Python
Create Charges and Verify Webhooks in Python
Install the requests library (available by default on Replit) for HTTP calls. The Coinbase Commerce REST API uses your API key in an X-CC-Api-Key header and expects Content-Type: application/json with a version header X-CC-Version: 2018-03-22. The Flask app below exposes two endpoints: POST /create-charge to generate a new payment charge and return the hosted checkout URL, and POST /webhook to receive Commerce payment events. The webhook handler verifies the HMAC-SHA256 signature before processing the event β this is the security-critical step that prevents fraudulent order fulfillment. Coinbase Commerce webhooks deliver events multiple times for each charge state change and expect your endpoint to return HTTP 200 quickly. Process any database updates or downstream actions asynchronously if they take more than a few seconds, or return 200 immediately and queue the work.
1import os2import hmac3import hashlib4import requests5from flask import Flask, request, jsonify67app = Flask(__name__)89API_KEY = os.environ["COINBASE_COMMERCE_API_KEY"]10WEBHOOK_SECRET = os.environ["COINBASE_WEBHOOK_SECRET"]1112BASE_URL = "https://api.commerce.coinbase.com"13HEADERS = {14 "X-CC-Api-Key": API_KEY,15 "X-CC-Version": "2018-03-22",16 "Content-Type": "application/json"17}181920@app.route("/create-charge", methods=["POST"])21def create_charge():22 """Create a Coinbase Commerce charge and return the hosted payment URL."""23 data = request.json24 name = data.get("name", "Order")25 description = data.get("description", "")26 amount = data.get("amount") # e.g. "49.99"27 currency = data.get("currency", "USD")28 customer_id = data.get("customer_id", "")2930 payload = {31 "name": name,32 "description": description,33 "pricing_type": "fixed_price",34 "local_price": {"amount": amount, "currency": currency},35 "metadata": {"customer_id": customer_id}36 }3738 resp = requests.post(f"{BASE_URL}/charges", json=payload, headers=HEADERS)39 resp.raise_for_status()40 charge = resp.json()["data"]41 return jsonify({42 "charge_id": charge["id"],43 "code": charge["code"],44 "hosted_url": charge["hosted_url"],45 "expires_at": charge["expires_at"]46 })474849def verify_webhook_signature(payload_body: bytes, signature: str) -> bool:50 """Verify Coinbase Commerce webhook HMAC-SHA256 signature."""51 expected = hmac.new(52 WEBHOOK_SECRET.encode("utf-8"),53 payload_body,54 hashlib.sha25655 ).hexdigest()56 return hmac.compare_digest(expected, signature)575859@app.route("/webhook", methods=["POST"])60def webhook():61 """Receive and process Coinbase Commerce payment events."""62 signature = request.headers.get("X-CC-Webhook-Signature", "")63 raw_body = request.get_data() # raw bytes before JSON parsing6465 if not verify_webhook_signature(raw_body, signature):66 return jsonify({"error": "Invalid signature"}), 4006768 event = request.json69 event_type = event["event"]["type"]70 charge_data = event["event"]["data"]71 charge_code = charge_data.get("code")72 customer_id = charge_data.get("metadata", {}).get("customer_id")7374 print(f"Commerce event: {event_type} for charge {charge_code}, customer {customer_id}")7576 if event_type == "charge:confirmed":77 # Payment fully confirmed β fulfill the order78 print(f"Payment confirmed for customer {customer_id}. Fulfilling order.")79 # TODO: update database, provision access, send confirmation email80 elif event_type == "charge:failed":81 print(f"Payment failed for charge {charge_code}")82 # TODO: notify customer8384 return jsonify({"received": True}), 200858687if __name__ == "__main__":88 app.run(host="0.0.0.0", port=3000, debug=False)Pro tip: Crypto payments can take minutes to confirm on-chain. Design your UI to show a 'Waiting for payment confirmation' state and update via polling or WebSocket when the webhook confirms payment.
Expected result: The Flask server starts, accepts POST /create-charge requests, and correctly verifies incoming webhook signatures.
Build a Node.js Coinbase Commerce Server
Build a Node.js Coinbase Commerce Server
For Node.js projects, the Commerce API works identically. Install axios for HTTP requests with 'npm install axios'. Webhook signature verification uses Node's built-in crypto module. The critical difference in Node.js webhook handling is that Express's express.json() middleware parses the request body before your route handler runs, which destroys the raw body bytes needed for HMAC verification. Use express.raw() for the webhook route specifically, or capture the raw body using a custom middleware that reads the buffer before JSON parsing. The server below handles charge creation and webhook verification with the correct raw body approach. It uses separate middleware for the webhook route to preserve the raw bytes.
1const express = require('express');2const axios = require('axios');3const crypto = require('crypto');45const app = express();67const API_KEY = process.env.COINBASE_COMMERCE_API_KEY;8const WEBHOOK_SECRET = process.env.COINBASE_WEBHOOK_SECRET;9const BASE_URL = 'https://api.commerce.coinbase.com';1011const HEADERS = {12 'X-CC-Api-Key': API_KEY,13 'X-CC-Version': '2018-03-22',14 'Content-Type': 'application/json'15};1617// Parse JSON for all routes except /webhook18app.use((req, res, next) => {19 if (req.path === '/webhook') return next();20 express.json()(req, res, next);21});2223// Create a Commerce charge24app.post('/create-charge', async (req, res) => {25 const { name, description, amount, currency = 'USD', customer_id } = req.body;26 try {27 const { data } = await axios.post(`${BASE_URL}/charges`, {28 name,29 description,30 pricing_type: 'fixed_price',31 local_price: { amount: String(amount), currency },32 metadata: { customer_id }33 }, { headers: HEADERS });34 const charge = data.data;35 res.json({36 charge_id: charge.id,37 code: charge.code,38 hosted_url: charge.hosted_url,39 expires_at: charge.expires_at40 });41 } catch (err) {42 console.error('Create charge error:', err.response?.data || err.message);43 res.status(500).json({ error: err.message });44 }45});4647// Webhook endpoint β uses raw body for signature verification48app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {49 const signature = req.headers['x-cc-webhook-signature'] || '';50 const rawBody = req.body; // Buffer5152 const expectedSig = crypto53 .createHmac('sha256', WEBHOOK_SECRET)54 .update(rawBody)55 .digest('hex');5657 if (!crypto.timingSafeEqual(Buffer.from(expectedSig), Buffer.from(signature))) {58 return res.status(400).json({ error: 'Invalid signature' });59 }6061 const event = JSON.parse(rawBody);62 const eventType = event.event.type;63 const chargeData = event.event.data;64 const customerId = chargeData.metadata?.customer_id;6566 console.log(`Commerce event: ${eventType} for charge ${chargeData.code}`);6768 if (eventType === 'charge:confirmed') {69 console.log(`Payment confirmed for customer ${customerId}`);70 // TODO: fulfill order, update database71 } else if (eventType === 'charge:failed') {72 console.log(`Payment failed for charge ${chargeData.code}`);73 }7475 res.json({ received: true });76});7778app.listen(3000, '0.0.0.0', () => {79 console.log('Coinbase Commerce server running on port 3000');80});Pro tip: Never call express.json() before your webhook route β it consumes the raw body and makes signature verification fail. Use express.raw() exclusively for the webhook endpoint.
Expected result: The Node.js server starts and correctly returns a hosted_url from POST /create-charge and verifies webhook signatures on POST /webhook.
Deploy and Register Your Webhook URL
Deploy and Register Your Webhook URL
Once your integration works locally, deploy it on Replit so Coinbase Commerce can deliver webhooks to a stable public URL. Click Deploy in the top toolbar. For a checkout server that handles user-initiated payment requests, Autoscale deployment is appropriate. For continuous payment monitoring with WebSocket streams, use Reserved VM. After deploying, note your stable URL (e.g., https://your-app.replit.app). Go back to your Coinbase Commerce dashboard at https://commerce.coinbase.com, navigate to Settings β Webhook subscriptions, and update the webhook endpoint URL to https://your-app.replit.app/webhook. Coinbase Commerce will re-generate a shared secret for the new URL β update your COINBASE_WEBHOOK_SECRET in Replit Secrets and redeploy. Test your webhook integration by creating a test charge from the Commerce dashboard and manually triggering a test webhook delivery from the Settings β Webhook subscriptions page. Confirm your Replit deployment logs show the event type and charge code. For charge:confirmed events specifically, verify your fulfillment logic runs correctly before processing live payments.
Pro tip: Coinbase Commerce webhooks retry delivery up to three times if your endpoint returns a non-200 status. Make your webhook handler idempotent β check if the charge has already been processed before fulfilling to avoid duplicate order fulfillment.
Expected result: Webhook deliveries from Coinbase Commerce appear in your Replit deployment logs with correct event types, and your fulfillment logic runs on charge:confirmed events.
Common use cases
Crypto Checkout for a SaaS Product
A Replit web app generates a Coinbase Commerce charge when a user clicks 'Pay with Crypto', redirects them to the Commerce-hosted payment page, and listens for a webhook to provision their account access once payment is confirmed. The checkout flow works for one-time purchases or annual subscriptions.
Build an Express server that creates a Coinbase Commerce charge for a $49 SaaS plan, redirects the user to the hosted payment URL, and listens for a webhook at /webhook/coinbase to activate the user's account when payment is confirmed.
Copy this prompt to try it in Replit
NFT Mint Payment Gate
A Replit backend creates a Commerce charge for each NFT mint request, stores the charge ID in a database linked to the user's wallet address, and triggers the mint transaction only after the Commerce webhook confirms full payment. This decouples payment confirmation from the minting logic.
Create a Flask app that generates a Coinbase Commerce charge for an NFT mint, stores the charge ID and user wallet in a SQLite database, and triggers the mint only after receiving a confirmed webhook event for that charge.
Copy this prompt to try it in Replit
Crypto Invoice System
A Replit service generates Coinbase Commerce invoices for B2B clients who prefer to pay in cryptocurrency. Each invoice is a Commerce charge with the client's company name and invoice number in the metadata. A webhook listener updates the invoice status in a database when payment arrives.
Write a Python Flask API that creates Coinbase Commerce charges for client invoices, stores charge IDs in a PostgreSQL database, and updates invoice status to 'paid' when the confirmed webhook is received.
Copy this prompt to try it in Replit
Troubleshooting
Webhook signature verification always fails even though the secret looks correct
Cause: The HMAC signature is computed over the raw request body bytes. If Express's JSON middleware has already parsed the body, the raw bytes are no longer available and the signature computed from the parsed/re-serialized JSON will not match.
Solution: Use express.raw({ type: 'application/json' }) middleware exclusively on your /webhook route, not express.json(). In Python/Flask, use request.get_data() to read raw bytes before any JSON parsing.
1// Node.js β correct pattern2app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {3 const rawBody = req.body; // Buffer, not parsed object4 // compute HMAC over rawBody5});Charge status shows PENDING but never transitions to CONFIRMED
Cause: Crypto transactions require a certain number of blockchain confirmations before Commerce marks a charge as confirmed. Bitcoin requires 3 confirmations (30+ minutes), while Ethereum requires 14 confirmations (~3 minutes). Underpayments also stay in PENDING state indefinitely.
Solution: Wait for the appropriate confirmation time for each cryptocurrency. For time-sensitive applications, consider fulfilling orders on PENDING status for trusted customers and reversing if the charge ultimately fails. Always display expected wait times to customers on your payment confirmation page.
401 Unauthorized when calling the Commerce API
Cause: The API key is missing from the request headers, expired, or the wrong key is being used (e.g., a test key against the live API).
Solution: Verify that COINBASE_COMMERCE_API_KEY is set in Replit Secrets and that your code reads it correctly with os.environ['COINBASE_COMMERCE_API_KEY'] or process.env.COINBASE_COMMERCE_API_KEY. Confirm the key in your Commerce dashboard at Settings β API keys matches what is stored in Secrets.
Webhook events arrive out of order (confirmed before pending)
Cause: Commerce may deliver webhooks out of chronological order due to network retries and delivery timing. Your handler should not assume events arrive in a specific sequence.
Solution: In your webhook handler, always look up the current charge status from the Commerce API rather than inferring state from event order. Use the charge code in the event data to fetch the latest status before deciding whether to fulfill.
1# Fetch canonical charge status instead of relying on event order2resp = requests.get(f"{BASE_URL}/charges/{charge_code}", headers=HEADERS)3current_status = resp.json()["data"]["timeline"][-1]["status"]Best practices
- Always verify the X-CC-Webhook-Signature header on every incoming webhook before processing payment confirmations β never trust unverified events.
- Store COINBASE_COMMERCE_API_KEY and COINBASE_WEBHOOK_SECRET in Replit Secrets and never hardcode them in source files.
- Make your webhook handler idempotent by checking if a charge has already been processed before fulfilling β Commerce may deliver the same event multiple times.
- Use express.raw() (Node.js) or request.get_data() (Python) on your webhook route to preserve the raw body for HMAC verification.
- Design your UI to handle blockchain confirmation latency β show customers a 'Waiting for confirmation' state rather than an immediate success screen.
- Only fulfill orders on charge:confirmed events, not charge:pending, unless you are explicitly accepting unconfirmed transactions and accept the associated risk.
- Log every webhook event with charge code, event type, timestamp, and metadata so you can audit the full payment lifecycle for customer support.
- Test your webhook handler with manually triggered test deliveries from the Commerce dashboard before processing live payments.
Alternatives
Stripe is the better choice for fiat currency payments β it offers instant settlement, lower integration complexity, and broader global payment method support compared to crypto-only Coinbase Commerce.
Binance is preferable when you need advanced crypto trading capabilities such as limit orders and market data, whereas Coinbase Commerce focuses purely on accepting crypto payments from customers.
Braintree supports PayPal, Venmo, and traditional cards natively, making it a better fit if your customers prefer established payment methods over cryptocurrency.
Frequently asked questions
How do I store my Coinbase Commerce API key in Replit?
Click the lock icon π in the left sidebar to open the Secrets pane. Add COINBASE_COMMERCE_API_KEY with your API key value and COINBASE_WEBHOOK_SECRET with your webhook shared secret. Access them in Python with os.environ['COINBASE_COMMERCE_API_KEY'] and in Node.js with process.env.COINBASE_COMMERCE_API_KEY.
Can I test Coinbase Commerce webhooks on Replit?
Yes. After deploying your Replit app, register the deployment URL in the Commerce dashboard under Settings β Webhook subscriptions. You can then trigger test webhook deliveries from that same settings page. Your Replit deployment logs will show each delivered event. For local development before deploying, use a service like ngrok to expose your local port temporarily.
How long does it take for a Coinbase Commerce payment to confirm?
Confirmation time depends on the cryptocurrency used. Ethereum payments typically confirm within 3-5 minutes (14 block confirmations). Bitcoin payments can take 20-60 minutes (3 block confirmations). USDC payments on Ethereum confirm at the same speed as Ethereum. Your webhook will receive a charge:confirmed event when all required confirmations are reached.
Does Coinbase Commerce work in Replit free tier?
Yes, you can build and test the integration on Replit's free tier. For production deployments that receive webhooks reliably, an Autoscale deployment (which requires a paid Replit plan) is recommended so your server is always available to receive payment confirmations. Free-tier Replit projects can time out during inactivity and miss webhook deliveries.
What currencies does Coinbase Commerce support?
Coinbase Commerce accepts Bitcoin (BTC), Ethereum (ETH), USD Coin (USDC), Dai (DAI), Litecoin (LTC), and Bitcoin Cash (BCH). You price charges in fiat currency (e.g., USD) and Commerce handles the conversion rate display to customers. Settlements are made in the cryptocurrency the customer used to pay.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation