Skip to main content
RapidDev - Software Development Agency
replit-integrationsStandard API Integration

How to Integrate Replit with Coinbase API

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.

What you'll learn

  • How to create a Coinbase Commerce account and generate an API key
  • How to store Coinbase credentials securely in Replit Secrets
  • How to create payment charges and retrieve charge status from Python and Node.js
  • How to receive and verify Coinbase Commerce webhooks to confirm crypto payments
  • How to handle charge lifecycle events (NEW, PENDING, CONFIRMED, FAILED) in your application
Book a free consultation
4.9Clutch rating ⭐
600+Happy partners
17+Countries served
190+Team members
Intermediate13 min read40 minutesPaymentMarch 2026RapidDev Engineering Team
TL;DR

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

Standard API Integration

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

1

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.

2

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.

3

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.

app.py
1import os
2import hmac
3import hashlib
4import requests
5from flask import Flask, request, jsonify
6
7app = Flask(__name__)
8
9API_KEY = os.environ["COINBASE_COMMERCE_API_KEY"]
10WEBHOOK_SECRET = os.environ["COINBASE_WEBHOOK_SECRET"]
11
12BASE_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}
18
19
20@app.route("/create-charge", methods=["POST"])
21def create_charge():
22 """Create a Coinbase Commerce charge and return the hosted payment URL."""
23 data = request.json
24 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", "")
29
30 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 }
37
38 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 })
47
48
49def 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.sha256
55 ).hexdigest()
56 return hmac.compare_digest(expected, signature)
57
58
59@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 parsing
64
65 if not verify_webhook_signature(raw_body, signature):
66 return jsonify({"error": "Invalid signature"}), 400
67
68 event = request.json
69 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")
73
74 print(f"Commerce event: {event_type} for charge {charge_code}, customer {customer_id}")
75
76 if event_type == "charge:confirmed":
77 # Payment fully confirmed β€” fulfill the order
78 print(f"Payment confirmed for customer {customer_id}. Fulfilling order.")
79 # TODO: update database, provision access, send confirmation email
80 elif event_type == "charge:failed":
81 print(f"Payment failed for charge {charge_code}")
82 # TODO: notify customer
83
84 return jsonify({"received": True}), 200
85
86
87if __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.

4

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.

server.js
1const express = require('express');
2const axios = require('axios');
3const crypto = require('crypto');
4
5const app = express();
6
7const API_KEY = process.env.COINBASE_COMMERCE_API_KEY;
8const WEBHOOK_SECRET = process.env.COINBASE_WEBHOOK_SECRET;
9const BASE_URL = 'https://api.commerce.coinbase.com';
10
11const HEADERS = {
12 'X-CC-Api-Key': API_KEY,
13 'X-CC-Version': '2018-03-22',
14 'Content-Type': 'application/json'
15};
16
17// Parse JSON for all routes except /webhook
18app.use((req, res, next) => {
19 if (req.path === '/webhook') return next();
20 express.json()(req, res, next);
21});
22
23// Create a Commerce charge
24app.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_at
40 });
41 } catch (err) {
42 console.error('Create charge error:', err.response?.data || err.message);
43 res.status(500).json({ error: err.message });
44 }
45});
46
47// Webhook endpoint β€” uses raw body for signature verification
48app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
49 const signature = req.headers['x-cc-webhook-signature'] || '';
50 const rawBody = req.body; // Buffer
51
52 const expectedSig = crypto
53 .createHmac('sha256', WEBHOOK_SECRET)
54 .update(rawBody)
55 .digest('hex');
56
57 if (!crypto.timingSafeEqual(Buffer.from(expectedSig), Buffer.from(signature))) {
58 return res.status(400).json({ error: 'Invalid signature' });
59 }
60
61 const event = JSON.parse(rawBody);
62 const eventType = event.event.type;
63 const chargeData = event.event.data;
64 const customerId = chargeData.metadata?.customer_id;
65
66 console.log(`Commerce event: ${eventType} for charge ${chargeData.code}`);
67
68 if (eventType === 'charge:confirmed') {
69 console.log(`Payment confirmed for customer ${customerId}`);
70 // TODO: fulfill order, update database
71 } else if (eventType === 'charge:failed') {
72 console.log(`Payment failed for charge ${chargeData.code}`);
73 }
74
75 res.json({ received: true });
76});
77
78app.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.

5

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.

Replit Prompt

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.

Replit Prompt

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.

Replit Prompt

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.

typescript
1// Node.js β€” correct pattern
2app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
3 const rawBody = req.body; // Buffer, not parsed object
4 // compute HMAC over rawBody
5});

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.

typescript
1# Fetch canonical charge status instead of relying on event order
2resp = 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

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation β€” no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.