Guide to Coinbase Webhooks: Features and Best Practices
Coinbase is one of the most widely used cryptocurrency platforms in the world, offering APIs for payments, onramp/offramp, and onchain data across its product suite. Webhooks are central to how developers integrate with Coinbase, enabling real-time notifications for everything from payment confirmations to onchain token transfers.
This guide covers everything you need to know about Coinbase webhooks: their features across each Coinbase product, how to configure them, best practices for production deployments, and the common pain points developers face along with solutions to address them.
What are Coinbase webhooks?
Coinbase webhooks are HTTP callbacks that deliver event notifications to your endpoints whenever specific actions occur across Coinbase's platform. When a payment is confirmed, a transaction status changes, or onchain activity is detected, Coinbase sends a JSON payload via HTTP POST to URLs you configure.
Webhooks exist across three primary Coinbase products, each serving different use cases:
- Coinbase Commerce — Webhooks for crypto payment processing, notifying you of charge lifecycle events like creation, confirmation, and failure.
- Coinbase Developer Platform (CDP) Onchain Webhooks — Real-time notifications for onchain activity such as ERC-20 transfers, NFT movements, and smart contract events on Base and other supported networks.
- Coinbase Onramp & Offramp — Webhooks for fiat-to-crypto and crypto-to-fiat transaction status updates within your application.
This guide covers all three, with a focus on Commerce and CDP webhooks as the most commonly used integrations.
Coinbase webhook features
| Feature | Details |
|---|---|
| Webhook configuration | Coinbase dashboard, REST API, or CDP SDK |
| Signature algorithm | SHA-256 HMAC (Commerce: X-CC-Webhook-Signature; CDP: signature verification via SDK) |
| Retry logic | Exponential backoff, up to 3 days (Commerce); up to 60 retries (CDP Onchain) |
| Max retry interval | 1 hour (Commerce) |
| Manual retry | Not available |
| Browsable log | Limited (Commerce dashboard shows events; CDP Portal shows subscription status) |
| Test mode | Built-in test webhook button (Commerce); webhook.site recommended for CDP |
| Authentication | Shared secret HMAC signing |
| Delivery guarantee | At-least-once (CDP Onchain); best-effort with retries (Commerce) |
| Supported products | Commerce, CDP Onchain Webhooks, Onramp & Offramp |
| Status | Commerce (GA), CDP Onchain Webhooks (Beta), Onramp Webhooks (GA) |
Supported event types
Commerce events
Coinbase Commerce webhooks notify you of the following charge lifecycle events:
| Event Type | Description |
|---|---|
charge:created | A new charge has been created |
charge:confirmed | A charge has been confirmed and payment is complete |
charge:failed | A charge has failed |
charge:delayed | A charge has been detected but not yet confirmed |
charge:pending | A charge is pending (payment detected on the blockchain) |
charge:resolved | A charge that was previously underpaid or overpaid has been resolved |
CDP Onchain Webhook events
CDP Onchain Webhooks use the onchain.activity.detected event type, which you configure with labels to filter for specific contract events:
| Use Case | Configuration |
|---|---|
| ERC-20 token transfers | contract_address + event_name: "Transfer" |
| NFT ownership tracking | Subscribe to any ERC-721 contract transfer events |
| New token pair creation | Monitor Uniswap pool initialization events |
| Stablecoin movement | Subscribe to USDC transfer events on Base |
Onramp & Offramp events
| Event Type | Description |
|---|---|
onramp.transaction.created | A new onramp transaction has been created |
onramp.transaction.updated | A transaction status has changed |
onramp.transaction.success | A transaction has completed successfully |
onramp.transaction.failed | A transaction has failed |
Webhook payload structure
Commerce webhook payload
When Coinbase Commerce sends a webhook notification, it delivers a JSON payload with the following structure:
{
"id": "5306c113-7ece-4b8b-9452-49abf442bbe4",
"scheduled_for": "2023-08-30T19:29:20Z",
"attempt_number": 1,
"event": {
"id": "053b96aa-868f-4d8f-8ac6-6b96644da340",
"resource": "event",
"type": "charge:confirmed",
"api_version": "2018-03-22",
"created_at": "2023-08-30T19:29:20Z",
"data": {
"id": "2aee9dd1-67b8-43ef-8dfe-977959850f27",
"code": "XA6G6ZFR",
"pricing": {
"local": { "amount": "1.00", "currency": "USD" },
"settlement": { "amount": "1", "currency": "USDC" }
},
"metadata": {
"name": "Bobby Axlerod",
"email": "bobby@axecapital.com"
},
"timeline": [
{ "time": "2023-08-30T18:55:51Z", "status": "NEW" },
{ "time": "2023-08-30T19:24:27Z", "status": "SIGNED" },
{ "time": "2023-08-30T19:24:45Z", "status": "PENDING" },
{ "time": "2023-08-30T19:29:20Z", "status": "COMPLETED" }
],
"web3_data": {
"failure_events": [],
"success_events": [
{
"sender": "0xb6d00d83...",
"tx_hsh": "0xc0a731f8...",
"finalized": false,
"recipient": "0x293a08a5..."
}
]
},
"charge_kind": "WEB3",
"pricing_type": "fixed_price",
"confirmed_at": "2023-08-30T19:29:20Z",
"hosted_url": "https://commerce.coinbase.com/pay/2aee9dd1-..."
}
}
}
Key payload fields
| Field | Description |
|---|---|
id | Unique identifier for the webhook delivery |
scheduled_for | Scheduled delivery timestamp |
attempt_number | Retry attempt number (starts at 1) |
event.type | The event type (e.g., charge:created, charge:confirmed) |
event.data.code | Short charge identifier code |
event.data.pricing | Local and settlement amount/currency |
event.data.metadata | Custom metadata attached when creating the charge |
event.data.timeline | Array of status transitions (NEW, SIGNED, PENDING, COMPLETED) |
event.data.web3_data | Onchain transaction data including success and failure events |
event.data.payments | Array of payment details |
event.data.addresses | Crypto addresses for payment (Bitcoin, Ethereum, etc.) |
Security with HMAC signatures
Coinbase Commerce signs every webhook event it sends to your endpoints. The signature is included in the X-CC-Webhook-Signature header, containing the SHA-256 HMAC of the raw request payload computed using your webhook shared secret as the key.
You can find your shared webhook secret under Settings > Notifications in the Coinbase Commerce dashboard.
For CDP Onchain Webhooks, the subscription response includes a metadata.secret value that you use for signature verification. The CDP SDK provides built-in methods for verifying signatures.
Setting up Coinbase webhooks
Commerce webhooks via the dashboard
- Log in to your Coinbase Commerce account.
- Navigate to Settings and scroll to the Webhook subscriptions section.
- Click Add an endpoint and enter your HTTPS endpoint URL.
- Save the endpoint — Commerce will deliver events for all charge lifecycle changes.
- Note the Webhook Shared Secret displayed in the settings for signature verification.
CDP Onchain Webhooks via the REST API
- Sign up at portal.cdp.coinbase.com and navigate to API Keys.
- Select Create API key under the Secret API Keys tab.
- Install
cdpcurlfor API interaction. - Create a webhook subscription by sending a POST request:
cdpcurl -X POST https://api.cdp.coinbase.com/platform/v2/data/webhooks/subscriptions \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-endpoint.com/webhooks/coinbase",
"eventTypes": ["onchain.activity.detected"],
"labels": {
"contract_address": "0xd9aaec86b65d86f6a7b5b1b0c42ffa531710b6ca",
"event_name": "Transfer"
}
}'
- Use the returned
subscriptionIdto manage (view, update, or delete) your subscription.
Onramp webhooks via the REST API
Create a webhook subscription by sending a POST request to the CDP webhooks API:
cdpcurl -X POST https://api.cdp.coinbase.com/platform/v2/data/webhooks/subscriptions \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-endpoint.com/webhooks/onramp",
"eventTypes": [
"onramp.transaction.created",
"onramp.transaction.updated",
"onramp.transaction.success",
"onramp.transaction.failed"
]
}'
Best practices when working with Coinbase webhooks
Verifying HMAC signatures
When processing webhooks from Coinbase Commerce, verify the HMAC signature to ensure requests genuinely originated from Coinbase.
Node.js
const crypto = require('crypto');
const express = require('express');
const app = express();
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
function verifyCoinbaseSignature(rawBody, signature, secret) {
const hash = crypto
.createHmac('sha256', secret)
.update(rawBody, 'utf8')
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(hash),
Buffer.from(signature)
);
}
app.post('/webhooks/coinbase', (req, res) => {
const signature = req.headers['x-cc-webhook-signature'];
if (!verifyCoinbaseSignature(
req.rawBody,
signature,
process.env.COINBASE_WEBHOOK_SECRET
)) {
return res.status(401).send('Invalid signature');
}
// Process webhook
res.status(200).send('OK');
});
Python
import hmac
import hashlib
import os
from flask import Flask, request, abort
app = Flask(__name__)
def verify_coinbase_signature(payload, signature, secret):
computed = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(computed, signature)
@app.route('/webhooks/coinbase', methods=['POST'])
def handle_coinbase_webhook():
signature = request.headers.get('X-CC-Webhook-Signature')
if not verify_coinbase_signature(
request.get_data(as_text=True),
signature,
os.environ['COINBASE_WEBHOOK_SECRET']
):
abort(401)
# Process webhook
return 'OK', 200
Respond immediately, process asynchronously
Coinbase Commerce expects a 200 response quickly. If your endpoint takes too long, Coinbase considers the delivery failed and schedules a retry. Always acknowledge receipt immediately, then process the event asynchronously.
Use the charge code for idempotency
Each Commerce charge includes a unique code field, and events include an attempt_number. Use these to implement idempotent processing and prevent duplicate handling during retries:
async function processCoinbaseEvent(event) {
const idempotencyKey = `${event.data.code}-${event.type}`;
const exists = await redis.get(`processed:${idempotencyKey}`);
if (exists) {
console.log(`Event ${idempotencyKey} already processed, skipping`);
return;
}
await handleChargeEvent(event);
await redis.setex(`processed:${idempotencyKey}`, 86400, '1');
}
Monitor the charge timeline
The timeline array in Commerce payloads tracks the full lifecycle of a charge. Use it to build robust state machines rather than relying solely on the event type:
function getChargeStatus(timeline) {
const latestStatus = timeline[timeline.length - 1].status;
switch (latestStatus) {
case 'NEW':
return 'awaiting_payment';
case 'SIGNED':
return 'transaction_signed';
case 'PENDING':
return 'confirming';
case 'COMPLETED':
return 'paid';
case 'EXPIRED':
return 'expired';
case 'CANCELED':
return 'canceled';
default:
return 'unknown';
}
}
Coinbase webhook limitations and pain points
No configurable timeout window
The Problem: Coinbase Commerce does not document a specific timeout value for webhook deliveries, and there is no way to configure one. If your endpoint is slow to respond, Coinbase treats the delivery as failed and triggers retry logic, even if the endpoint eventually would have responded.
Why It Happens: Coinbase applies an internal timeout on webhook deliveries that isn't exposed or configurable to developers. This is compounded by the fact that crypto payment processing can require complex downstream operations like database writes, blockchain confirmations, and third-party API calls.
Workarounds:
- Always acknowledge webhooks immediately with a
200response and process asynchronously using a job queue. - Keep your webhook handler as lightweight as possible — validate the signature, enqueue the event, and respond.
How Hookdeck Can Help: Hookdeck provides configurable delivery timeouts, giving your endpoint additional time to process complex payment events without triggering Coinbase's retry logic.
Aggressive retry behaviour with limited visibility
The Problem: Commerce retries failed deliveries with exponential backoff for up to three days, with a maximum interval of one hour. CDP Onchain Webhooks retry up to 60 times. While retries ensure delivery, there is limited visibility into retry state — you can't see which deliveries are in retry, how many attempts have been made, or when the next retry is scheduled.
Why It Happens: Coinbase's dashboard provides minimal delivery monitoring. The Commerce event log shows events but not their delivery status or retry state. There is no built-in way to see failed deliveries or trigger manual replays.
Workarounds:
- Implement your own delivery tracking by logging every webhook receipt with its
attempt_number. - Monitor your endpoint's error rates independently to detect delivery issues.
- Use the Commerce API's list notifications endpoint as a fallback to poll for missed events.
How Hookdeck Can Help: Hookdeck's dashboard provides complete visibility into webhook delivery status, latency, errors, and response codes. You can see every delivery attempt, filter by status, and configure alerts when delivery issues occur.
Automatic subscription disabling on sustained failures
The Problem: CDP Onchain Webhook subscriptions may be automatically disabled if your endpoint experiences sustained delivery failures, such as high failure rates, extended unavailability, or throughput issues. Once disabled, you stop receiving events silently with no automatic recovery.
Why It Happens: Coinbase protects its infrastructure by disabling subscriptions that consistently fail, preventing wasted resources on endpoints that appear to be down.
Workarounds:
- Monitor your subscription status regularly via the CDP API.
- Implement health checks and alerting for your webhook endpoint.
- Build a re-enablement process that detects disabled subscriptions and recreates them.
How Hookdeck Can Help: Hookdeck acts as a reliable intermediary that always accepts webhooks from Coinbase, preventing your subscription from being disabled due to downstream endpoint issues. Failed deliveries are queued and retried to your actual endpoint independently.
No built-in dead-letter queue or manual replay
The Problem: When webhook deliveries fail after all retries are exhausted, the events are lost. There is no built-in dead-letter queue for inspecting failed deliveries and no manual replay functionality in the Commerce or CDP dashboards.
Why It Happens: Coinbase's webhook infrastructure focuses on forward delivery with retries but doesn't persist failed events for later recovery.
Workarounds:
- Log every incoming webhook payload immediately (before processing) so you have a local record.
- Use the Commerce API to poll for charges and reconcile against received webhooks.
- Build your own dead-letter queue by persisting failed events to a database.
How Hookdeck Can Help: Hookdeck automatically preserves failed webhooks in a dead-letter queue, allowing you to inspect, debug, and replay them once issues are resolved — without losing a single event.
Limited network and event type support (CDP)
The Problem: CDP Onchain Webhooks initially launched with support primarily for Base, with limited coverage of other networks. The available event types are currently focused on onchain.activity.detected with contract-level filtering, which requires developers to understand contract ABIs and event signatures.
Why It Happens: CDP Webhooks are still in Beta, and Coinbase is incrementally expanding network and event support.
Workarounds:
- For networks not yet supported by CDP Webhooks, use third-party indexing services or node providers with webhook capabilities.
- Monitor Coinbase's changelog for newly supported networks and event types.
- Combine CDP Webhooks with the Commerce API for broader coverage across payment and onchain use cases.
How Hookdeck Can Help: Hookdeck's routing and transformation capabilities let you normalize webhook payloads from multiple sources (Coinbase CDP, Commerce, and third-party providers) into a consistent format, simplifying multi-network architectures.
Payment status delays
The Problem: There can be significant delays between a cryptocurrency payment being made on the blockchain and Coinbase Commerce sending a charge:confirmed webhook. Depending on the cryptocurrency network and current traffic, this delay can be up to an hour or more.
Why It Happens: Coinbase waits for a sufficient number of blockchain confirmations before considering a payment confirmed. Different networks have different block times and confirmation requirements (e.g., Bitcoin requires more confirmations than Ethereum-based networks).
Workarounds:
- Use the
charge:pendingevent to detect payments early and show users a "payment detected" status while waiting for confirmation. - Set appropriate user expectations about confirmation times in your UI.
- Implement the
timelinestatus progression to show granular status updates.
How Hookdeck Can Help: Hookdeck's event routing can fan out charge:pending events to trigger immediate user notifications via one destination while routing charge:confirmed events to your order fulfillment system via another, enabling parallel processing of different event types.
Testing Coinbase webhooks
Use the built-in test feature (Commerce)
Coinbase Commerce includes a test webhook button in the dashboard:
- Navigate to Settings > Webhook subscriptions.
- Click Details next to the relevant webhook endpoint.
- Click the Send test button.
Be aware that test payloads may differ slightly from real events — for example, test payloads have been reported to include pricing: nil, which can cause parsing errors in some implementations.
Use a request inspector
Before building your handler, inspect real Coinbase payloads using a service like Hookdeck Console:
- Create a temporary endpoint URL.
- Configure it as your webhook URL in Coinbase.
- Trigger a real event (create a charge and complete a test payment).
- Inspect the full payload structure and headers.
Local development with tunnels
Since Coinbase requires HTTPS endpoints, use a tunneling tool like Hookdeck CLI for local development. Configure the generated HTTPS URL as your webhook endpoint in Coinbase during development.
Validate across charge lifecycle
Test your webhook integration with the complete charge lifecycle:
- Charge creation (
charge:created). - Payment detection (
charge:pending). - Payment confirmation (
charge:confirmed). - Charge expiry (let a charge expire without payment).
- Underpayment and overpayment scenarios.
- Multiple rapid events for the same charge.
Conclusion
Coinbase webhooks provide the real-time event infrastructure needed to build responsive cryptocurrency payment and onchain applications. The Commerce API's charge lifecycle events, CDP's onchain activity notifications, and Onramp/Offramp transaction updates cover a broad range of integration needs, and the HMAC signature verification across products provides a solid security foundation.
However, limitations around delivery visibility, automatic subscription disabling, missing dead-letter queues, and archived SDK libraries mean production deployments require careful planning. Implementing proper signature verification, idempotent processing, and asynchronous handling addresses most common issues.
For straightforward Commerce integrations with moderate transaction volumes, Coinbase's built-in webhook delivery with exponential backoff retries works well when combined with proper error handling. For high-volume payment processing, multi-product integrations, or mission-critical applications where delivery guarantees matter, webhook infrastructure like Hookdeck can address Coinbase's limitations by providing configurable timeouts, automatic deduplication, dead-letter queues, payload transformation, and comprehensive delivery monitoring — without modifying your Coinbase configuration.