Webhooks API
Webhooks provide real-time notifications when payment events occur. Instead of polling our API, you'll receive HTTP POST requests to your specified endpoints whenever payments are detected, confirmed, or fail.
Webhook Events
CryptoPay sends webhooks for the following events:
| Event | Description |
|---|---|
payment.pending | Payment transaction detected on blockchain |
payment.confirmed | Payment confirmed with required confirmations |
payment.failed | Payment failed or was rejected |
order.expired | Payment order expired without payment |
Webhook Payload
All webhooks follow this structure:
{
"event": "payment.confirmed",
"data": {
"order_id": "ORD-abc123def456",
"transaction_hash": "0xabcdef1234567890...",
"amount": "100.00",
"currency": "USDC",
"blockchain": "base",
"status": "confirmed",
"confirmations": 15,
"block_number": 12345678,
"from_address": "0x9876543210987654321098765432109876543210",
"to_address": "0x1234567890123456789012345678901234567890",
"order_info": "{\"product\": \"Premium Plan\", \"quantity\": 1}",
"created_at": "2024-01-01T10:00:00Z",
"confirmed_at": "2024-01-01T10:15:00Z"
},
"timestamp": "2024-01-01T10:15:30Z",
"webhook_id": "wh_abc123def456"
}
Webhook Security
HMAC Signature Verification
All webhooks include an HMAC signature in the X-Webhook-Signature header. Always verify this signature to ensure the webhook is from CryptoPay.
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload, 'utf8')
.digest('hex');
return signature === expectedSignature;
}
// Express.js example
app.post('/webhooks/cryptopay', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = req.body;
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const webhookData = JSON.parse(payload);
handleWebhook(webhookData);
res.status(200).send('OK');
});
IP Whitelisting
For additional security, whitelist our webhook IP addresses:
52.89.214.238
54.218.53.128
52.32.178.7
Managing Webhook Endpoints
Create Webhook Endpoint
POST /api/webhook-endpoints/
{
"url": "https://your-site.com/webhooks/cryptopay",
"events": ["payment.pending", "payment.confirmed"],
"is_active": true,
"secret_key": "your-webhook-secret"
}
List Webhook Endpoints
GET /api/webhook-endpoints/
Update Webhook Endpoint
PUT /api/webhook-endpoints/{id}/
Delete Webhook Endpoint
DELETE /api/webhook-endpoints/{id}/
Webhook Event Details
payment.pending
Sent when a payment transaction is first detected on the blockchain but hasn't reached the required confirmations yet.
{
"event": "payment.pending",
"data": {
"order_id": "ORD-abc123def456",
"transaction_hash": "0xabcdef1234567890...",
"amount": "100.00",
"currency": "USDC",
"blockchain": "base",
"status": "pending",
"confirmations": 1,
"block_number": 12345678
}
}
payment.confirmed
Sent when a payment reaches the required number of confirmations and is considered final.
{
"event": "payment.confirmed",
"data": {
"order_id": "ORD-abc123def456",
"transaction_hash": "0xabcdef1234567890...",
"amount": "100.00",
"currency": "USDC",
"blockchain": "base",
"status": "confirmed",
"confirmations": 15,
"block_number": 12345678,
"confirmed_at": "2024-01-01T10:15:00Z"
}
}
payment.failed
Sent when a payment fails due to insufficient amount, wrong token, or other issues.
{
"event": "payment.failed",
"data": {
"order_id": "ORD-abc123def456",
"transaction_hash": "0xabcdef1234567890...",
"reason": "insufficient_amount",
"expected_amount": "100.00",
"received_amount": "50.00",
"currency": "USDC",
"blockchain": "base"
}
}
Handling Webhooks
Best Practices
- Respond Quickly: Return a 200 status code within 10 seconds
- Verify Signatures: Always verify HMAC signatures
- Handle Duplicates: Use webhook_id to detect duplicate deliveries
- Process Asynchronously: Queue webhook processing for complex operations
- Implement Retries: Handle temporary failures gracefully
Example Handler
const express = require('express');
const crypto = require('crypto');
const app = express();
// Store processed webhook IDs to handle duplicates
const processedWebhooks = new Set();
app.post('/webhooks/cryptopay', express.raw({type: 'application/json'}), async (req, res) => {
try {
const signature = req.headers['x-webhook-signature'];
const payload = req.body;
// Verify signature
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const webhookData = JSON.parse(payload);
const { event, data, webhook_id } = webhookData;
// Handle duplicates
if (processedWebhooks.has(webhook_id)) {
return res.status(200).send('Already processed');
}
// Process webhook asynchronously
processWebhookAsync(event, data, webhook_id);
// Mark as processed
processedWebhooks.add(webhook_id);
// Respond immediately
res.status(200).send('OK');
} catch (error) {
console.error('Webhook error:', error);
res.status(500).send('Internal error');
}
});
async function processWebhookAsync(event, data, webhookId) {
try {
switch (event) {
case 'payment.pending':
await handlePaymentPending(data);
break;
case 'payment.confirmed':
await handlePaymentConfirmed(data);
break;
case 'payment.failed':
await handlePaymentFailed(data);
break;
case 'order.expired':
await handleOrderExpired(data);
break;
}
} catch (error) {
console.error(`Error processing webhook ${webhookId}:`, error);
// Implement retry logic or dead letter queue
}
}
async function handlePaymentConfirmed(data) {
// Update your database
await db.orders.update(data.order_id, {
status: 'paid',
transaction_hash: data.transaction_hash,
confirmed_at: data.confirmed_at
});
// Fulfill the order
await fulfillOrder(data.order_id);
// Send confirmation email
await sendConfirmationEmail(data.order_id);
}
Webhook Delivery
Retry Policy
If your endpoint doesn't respond with a 2xx status code, we'll retry the webhook:
- Retry 1: After 1 minute
- Retry 2: After 5 minutes
- Retry 3: After 15 minutes
- Retry 4: After 1 hour
- Retry 5: After 6 hours
After 5 failed attempts, the webhook is marked as failed and won't be retried.
Timeout
Webhook requests timeout after 10 seconds. Ensure your endpoint responds quickly.
Testing Webhooks
Webhook Testing Tool
Use our webhook testing tool in the dashboard to:
- Send test webhooks to your endpoints
- View webhook delivery logs
- Debug webhook failures
- Test signature verification
ngrok for Local Development
For local development, use ngrok to expose your local server:
# Install ngrok
npm install -g ngrok
# Expose local port 3000
ngrok http 3000
# Use the ngrok URL for webhook endpoint
# https://abc123.ngrok.io/webhooks/cryptopay
Troubleshooting
Common Issues
- Invalid Signature: Check your webhook secret and signature verification
- Timeout: Ensure your endpoint responds within 10 seconds
- Wrong Status Code: Return 200-299 status codes for success
- SSL Issues: Ensure your webhook URL uses HTTPS with valid certificate
Webhook Logs
View webhook delivery logs in your dashboard:
- Request/response details
- Delivery attempts
- Error messages
- Response times
Next Steps
- Payment Orders API - Create payment orders
- Security Guide - Security best practices
- Integration Examples - Complete integration examples