Webhook Reference
Cryptofuse uses webhooks to notify your application about payment and withdrawal events in real-time. This eliminates the need for constant polling and ensures you're immediately informed of important status changes.
Overview
Webhooks are HTTP POST requests sent to your specified callback URL when certain events occur. Each webhook contains:
- Event type and data
- HMAC signature for verification
- Timestamp and metadata
Webhook Configuration
Setting Up Webhooks
Configure your default webhook URL in the merchant dashboard or via API:
curl -X PATCH https://api.cryptofuse.io/user/webhooks/ \
-H "Authorization: Bearer your_access_token" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-site.com/webhook/cryptofuse",
"webhook_secret": "your-webhook-secret-key"
}'
Per-Payment Webhooks
Override the default webhook URL for specific payments:
{
"price_amount": 100.00,
"pay_currency": "USDTERC20",
"callback_url": "https://your-site.com/webhook/payment/12345"
}
Webhook Events
payment_status_update
Sent when a payment status changes.
{
"event": "payment_status_update",
"data": {
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"previous_status": "confirming",
"payment_type": "full",
"order_id": "ORDER-12345",
"price_amount": "100.00",
"price_currency": "USD",
"pay_amount": "100.050000",
"pay_currency": "USDTERC20",
"blockchain_address": "0x742d35Cc6634C0532925a3b844Bc9e7595f8b8E0",
"transaction_hash": "0x123abc456def789...",
"confirmations": 12,
"merchant_received_usd": "99.00",
"payment_history": [
{
"deposit_id": "dep_123",
"amount": "100.050000",
"transaction_hash": "0x123abc456def789...",
"confirmations": 12,
"timestamp": "2025-01-15T10:15:00Z"
}
],
"metadata": {
"updated_at": "2025-01-15T10:20:00Z"
}
},
"timestamp": "2025-01-15T10:20:00Z"
}
deposit_received
Sent when a new deposit is detected for a payment.
{
"event": "deposit_received",
"data": {
"transaction_id": "550e8400-e29b-41d4-a716-446655440000",
"deposit_id": "dep_123",
"amount": "100.050000",
"transaction_hash": "0x123abc456def789...",
"confirmations": 0,
"blockchain_network": "ETH",
"token": "USDT",
"timestamp": "2025-01-15T10:15:00Z"
},
"timestamp": "2025-01-15T10:15:00Z"
}
withdrawal_status_update
Sent when a withdrawal status changes.
{
"event": "withdrawal_status_update",
"data": {
"withdrawal_id": "660e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"previous_status": "processing",
"amount": "50.00",
"currency": "USDTERC20",
"address": "0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE",
"transaction_hash": "0x789def123abc456...",
"network_fee": "2.50",
"custom_id": "WITHDRAW-001",
"metadata": {
"processed_at": "2025-01-15T11:30:00Z"
}
},
"timestamp": "2025-01-15T11:30:00Z"
}
Webhook Security
HMAC Signature Verification
All webhooks include an X-Cryptofuse-Signature header with an HMAC-SHA256 signature.
Verification Process
- Get the raw request body (before parsing)
- Compute HMAC-SHA256 using your webhook secret
- Compare with the signature header
Example Implementation
Node.js
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 middleware
app.post('/webhook/cryptofuse', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-cryptofuse-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 webhook...
res.status(200).json({status: 'received'});
});
Python (Flask)
import hmac
import hashlib
from flask import Flask, request, abort
def verify_webhook_signature(payload, signature, secret):
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
@app.route('/webhook/cryptofuse', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Cryptofuse-Signature')
payload = request.get_data()
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
abort(401)
event = request.get_json()
# Process webhook...
return {'status': 'received'}, 200
PHP
function verifyWebhookSignature($payload, $signature, $secret) {
$expected = hash_hmac('sha256', $payload, $secret);
return hash_equals($signature, $expected);
}
$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_CRYPTOFUSE_SIGNATURE'] ?? '';
if (!verifyWebhookSignature($payload, $signature, $webhookSecret)) {
http_response_code(401);
exit('Invalid signature');
}
$event = json_decode($payload, true);
// Process webhook...
http_response_code(200);
echo json_encode(['status' => 'received']);
Webhook Handling Best Practices
1. Immediate Response
Respond quickly with 200 OK to acknowledge receipt:
HTTP/1.1 200 OK
Content-Type: application/json
{"status": "received"}
Process the webhook asynchronously to avoid timeouts.
2. Idempotency
Webhooks may be sent multiple times. Use the transaction_id or withdrawal_id to ensure idempotent processing:
def process_payment_webhook(event):
transaction_id = event['data']['transaction_id']
# Check if already processed
if is_already_processed(transaction_id, event['data']['status']):
return
# Process webhook
update_order_status(transaction_id, event['data']['status'])
mark_as_processed(transaction_id, event['data']['status'])
3. Event Ordering
Webhooks may arrive out of order. Always check the current status:
async function handlePaymentUpdate(webhookData) {
// Get current status from your database
const currentStatus = await getPaymentStatus(webhookData.transaction_id);
// Define status progression
const statusOrder = ['waiting', 'confirming', 'confirmed', 'sending', 'completed'];
const currentIndex = statusOrder.indexOf(currentStatus);
const newIndex = statusOrder.indexOf(webhookData.status);
// Only update if progressing forward
if (newIndex > currentIndex) {
await updatePaymentStatus(webhookData.transaction_id, webhookData.status);
}
}
4. Error Handling
Implement proper error handling and logging:
import logging
logger = logging.getLogger(__name__)
def handle_webhook(event):
try:
if event['event'] == 'payment_status_update':
process_payment_update(event['data'])
elif event['event'] == 'withdrawal_status_update':
process_withdrawal_update(event['data'])
else:
logger.warning(f"Unknown event type: {event['event']}")
except Exception as e:
logger.error(f"Webhook processing failed: {str(e)}", exc_info=True)
# Don't re-raise - respond with 200 to prevent retries
# Handle error internally (e.g., alert team)
5. Retry Logic
Cryptofuse retries failed webhooks with exponential backoff:
- 1st retry: 1 minute
- 2nd retry: 5 minutes
- 3rd retry: 15 minutes
- 4th retry: 1 hour
- 5th retry: 4 hours
After 5 failed attempts, the webhook is marked as failed.
Testing Webhooks
Test Webhook Endpoint
Send a test webhook to verify your implementation:
curl -X POST https://api.cryptofuse.io/user/webhooks/test/ \
-H "Authorization: Bearer your_access_token" \
-H "Content-Type: application/json" \
-d '{
"event_type": "payment_status_update"
}'
Local Development
For local testing, use a webhook forwarding service:
- ngrok:
ngrok http 3000 - localtunnel:
lt --port 3000 - webhookrelay
Example with ngrok:
# Start your local server
npm start # Runs on port 3000
# In another terminal
ngrok http 3000
# Use the ngrok URL as your webhook URL
# https://abc123.ngrok.io/webhook/cryptofuse
Webhook Event Reference
Payment Events
| Event | Trigger | Key Data |
|---|---|---|
payment_status_update | Any payment status change | status, transaction_id, payment_type |
deposit_received | New deposit detected | amount, transaction_hash, confirmations |
Withdrawal Events
| Event | Trigger | Key Data |
|---|---|---|
withdrawal_status_update | Any withdrawal status change | status, withdrawal_id, transaction_hash |
Status Values
Payment Statuses:
waiting- Awaiting paymentconfirming- Payment received, confirmingconfirmed- Confirmed on blockchainsending- Processing to merchantcompleted- Successfully completedpartially_paid- Partial payment receivedexpired- Payment window expiredfailed- Payment failed
Withdrawal Statuses:
pending- Awaiting processingprocessing- Being processedcompleted- Successfully sentfailed- Processing failedcancelled- Cancelled by user
Common Integration Patterns
Order Status Updates
async function handlePaymentWebhook(event) {
const { transaction_id, status, order_id } = event.data;
switch(status) {
case 'confirming':
await updateOrder(order_id, 'payment_confirming');
await notifyCustomer(order_id, 'Payment received, confirming...');
break;
case 'completed':
await updateOrder(order_id, 'paid');
await fulfillOrder(order_id);
await notifyCustomer(order_id, 'Payment completed!');
break;
case 'expired':
await updateOrder(order_id, 'payment_expired');
await notifyCustomer(order_id, 'Payment expired, please try again');
break;
case 'failed':
await updateOrder(order_id, 'payment_failed');
await notifySupport(order_id, 'Payment failed - investigate');
break;
}
}
Balance Updates
def handle_payment_completed(data):
# Update user balance
user_id = get_user_by_order(data['order_id'])
amount_usd = Decimal(data['merchant_received_usd'])
with transaction.atomic():
user = User.objects.select_for_update().get(id=user_id)
user.balance += amount_usd
user.save()
# Record transaction
Transaction.objects.create(
user=user,
type='deposit',
amount=amount_usd,
reference=data['transaction_id'],
status='completed'
)
Troubleshooting
Webhook Not Received
- Verify webhook URL is accessible from internet
- Check for IP whitelisting or firewall rules
- Ensure URL returns 200 OK status
- Verify webhook configuration in dashboard
Signature Verification Fails
- Ensure using raw request body (not parsed)
- Verify webhook secret matches exactly
- Check for encoding issues (UTF-8)
- Compare signature algorithms (HMAC-SHA256)
Duplicate Webhooks
- Implement idempotency using transaction IDs
- Store processed webhook IDs
- Check status before processing
- Use database transactions for atomicity
Support
For webhook issues:
- Check webhook logs in merchant dashboard
- Test with the test webhook endpoint
- Contact support@cryptofuse.io with:
- Transaction/webhook ID
- Timestamp of issue
- Error messages/logs