Skip to main content

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:

EventDescription
payment.pendingPayment transaction detected on blockchain
payment.confirmedPayment confirmed with required confirmations
payment.failedPayment failed or was rejected
order.expiredPayment 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

  1. Respond Quickly: Return a 200 status code within 10 seconds
  2. Verify Signatures: Always verify HMAC signatures
  3. Handle Duplicates: Use webhook_id to detect duplicate deliveries
  4. Process Asynchronously: Queue webhook processing for complex operations
  5. 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

  1. Invalid Signature: Check your webhook secret and signature verification
  2. Timeout: Ensure your endpoint responds within 10 seconds
  3. Wrong Status Code: Return 200-299 status codes for success
  4. 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