Webhooks
BITXpay Merchant Platform supports webhook subscriptions across several products, all using the same underlying API and authentication. Use webhooks to receive real-time HTTP notifications when events happen onchain, in your wallets, or across your BITXpay accounts.
Getting started
Before you get started, ensure you have:
A Secret API Key
- Sign up at sandbox.bitxpay.com/auth/signup
- Navigate to API Keys
- Select Create API key under the Secret API Keys tab
- Enter an API key nickname (restrictions are optional)
- Click Create
- Secure your API Key ID and Secret in a safe location
A webhook URL
You'll need an HTTPS URL to receive webhook events.
- For quick testing: webhook.site gives free temporary URLs instantly
- For production: Use your own HTTPS endpoint
BITXpay CLI (Optional)
Install the BITXpay CLI to make authenticated requests to BITXpay APIs:
# With npm
npm install -g @bitxpay/cli
# Or with pip
pip install bitxpay-cliWebhook Events
BITXpay sends webhook notifications for the following event types:
Payment Events
| Event Type | Description |
|---|---|
payment.created | A new payment has been created |
payment.pending | Payment is awaiting blockchain confirmation |
payment.completed | Payment has been confirmed on-chain |
payment.failed | Payment has failed or expired |
payment.refunded | Payment has been refunded |
Transfer Events
| Event Type | Description |
|---|---|
transfer.initiated | A transfer has been initiated |
transfer.completed | Transfer has been confirmed |
transfer.failed | Transfer has failed |
Wallet Events
| Event Type | Description |
|---|---|
wallet.deposit | Funds deposited to wallet |
wallet.withdrawal | Funds withdrawn from wallet |
Subscribe by product
Webhook support is actively expanding across BITXpay products. Check back as more integrations are added.
Onchain Data
Monitor smart contract events and token transfers on supported networks.
Server Wallets
Track transfer activity in and out of your BITXpay Server Wallet addresses.
Embedded Wallets
Track transfer activity on your users' wallets using Onchain Data Webhooks. Native Embedded Wallet webhook support is coming soon.
Onramp & Offramp
Receive real-time status updates for your users' buy and sell transactions.
Webhook Payload Structure
All webhooks follow a consistent structure:
{
"id": "evt_1234567890",
"type": "payment.completed",
"created_at": "2024-01-15T10:30:00Z",
"data": {
"id": "pay_abc123",
"amount": "100.00",
"currency": "USDT",
"status": "completed",
"network": "ethereum",
"transaction_hash": "0x123...",
"metadata": {
"order_id": "12345"
}
}
}Verifying Webhook Signatures
Always verify webhook signatures to ensure requests are from BITXpay:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Express.js example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-bitxpay-signature'];
const payload = req.body.toString();
if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
// Process the event
res.status(200).send('OK');
});import hmac
import hashlib
def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
expected_signature = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected_signature)
# Flask example
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Bitxpay-Signature')
payload = request.get_data()
if not verify_webhook_signature(payload, signature, os.environ['WEBHOOK_SECRET']):
return 'Invalid signature', 401
event = request.json
# Process the event
return 'OK', 200<?php
function verifyWebhookSignature($payload, $signature, $secret) {
$expectedSignature = hash_hmac('sha256', $payload, $secret);
return hash_equals($signature, $expectedSignature);
}
// Example usage
$signature = $_SERVER['HTTP_X_BITXPAY_SIGNATURE'];
$payload = file_get_contents('php://input');
$secret = getenv('WEBHOOK_SECRET');
if (!verifyWebhookSignature($payload, $signature, $secret)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($payload, true);
// Process the event
http_response_code(200);
echo 'OK';Handling Webhook Events
Implement idempotent event handling to prevent duplicate processing:
const processedEvents = new Set();
app.post('/webhook', async (req, res) => {
const event = req.body;
// Check if already processed
if (processedEvents.has(event.id)) {
return res.status(200).send('Already processed');
}
try {
// Process based on event type
switch (event.type) {
case 'payment.completed':
await handlePaymentCompleted(event.data);
break;
case 'payment.failed':
await handlePaymentFailed(event.data);
break;
case 'transfer.completed':
await handleTransferCompleted(event.data);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
// Mark as processed
processedEvents.add(event.id);
res.status(200).send('OK');
} catch (error) {
console.error('Webhook processing error:', error);
res.status(500).send('Processing error');
}
});Best Practices
1. Respond quickly
Return a 200 status code as quickly as possible. Process events asynchronously if needed.
app.post('/webhook', async (req, res) => {
// Immediately acknowledge receipt
res.status(200).send('OK');
// Process asynchronously
processWebhookAsync(req.body).catch(console.error);
});2. Handle retries
BITXpay will retry failed webhooks with exponential backoff. Implement idempotency to handle duplicate events.
3. Use HTTPS
Webhook URLs must use HTTPS in production. HTTP is only allowed for local development.
4. Validate event types
Always check the event type before processing to handle new event types gracefully.
5. Store webhook secrets securely
Never hardcode webhook secrets. Use environment variables or secure secret management.
Retry Policy
BITXpay automatically retries failed webhook deliveries:
- Attempt 1: Immediate
- Attempt 2: After 5 minutes
- Attempt 3: After 30 minutes
- Attempt 4: After 2 hours
- Attempt 5: After 24 hours
- Success criteria: HTTP 2xx response
- Timeout: 30 seconds per attempt
Testing Webhooks Locally
Using ngrok
# Install ngrok
npm install -g ngrok
# Start your local server
node server.js
# Expose it via ngrok
ngrok http 3000Use the ngrok URL (e.g., https://abc123.ngrok.io/webhook) as your webhook URL in the BITXpay dashboard.
Using webhook.site
- Go to webhook.site
- Copy your unique URL
- Use it as your webhook URL for testing
- View incoming webhooks in real-time
Monitoring Webhooks
Monitor webhook delivery in your BITXpay dashboard:
- Navigate to Developers → Webhooks
- View delivery history, status codes, and response times
- Manually retry failed deliveries
- View webhook payload and response details
Webhook Endpoints Management
Create a webhook endpoint
curl -X POST https://sandboxapi.bitxpay.com/api/v1/webhook-endpoints \
-H "X-API-Key: your-api-key" \
-H "X-Signature: your-signature" \
-H "X-Timestamp: $(date +%s)000" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yoursite.com/webhook",
"events": ["payment.completed", "payment.failed"],
"description": "Production webhook endpoint"
}'List webhook endpoints
curl -X GET https://sandboxapi.bitxpay.com/api/v1/webhook-endpoints \
-H "X-API-Key: your-api-key" \
-H "X-Signature: your-signature" \
-H "X-Timestamp: $(date +%s)000"Delete a webhook endpoint
curl -X DELETE https://sandboxapi.bitxpay.com/api/v1/webhook-endpoints/{endpoint_id} \
-H "X-API-Key: your-api-key" \
-H "X-Signature: your-signature" \
-H "X-Timestamp: $(date +%s)000"Troubleshooting
Webhooks not being received
- Check your endpoint is publicly accessible: Test with
curl https://yoursite.com/webhook - Verify HTTPS: Production webhooks require HTTPS
- Check firewall rules: Ensure your server accepts requests from BITXpay IPs
- Review response codes: Must return 200-299 for success
Signature verification failing
- Use raw request body: Don't parse JSON before verification
- Check secret: Ensure you're using the correct webhook secret
- Verify header name: Look for
X-Bitxpay-Signature(case-insensitive)
Duplicate events
Implement idempotency using the event id field to prevent duplicate processing.
Support
For webhook-related issues:
- Documentation: https://docs.bitxpay.com/
- Email: support@bitxpay.com