Skip to main content

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

  1. Get the raw request body (before parsing)
  2. Compute HMAC-SHA256 using your webhook secret
  3. 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:

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

EventTriggerKey Data
payment_status_updateAny payment status changestatus, transaction_id, payment_type
deposit_receivedNew deposit detectedamount, transaction_hash, confirmations

Withdrawal Events

EventTriggerKey Data
withdrawal_status_updateAny withdrawal status changestatus, withdrawal_id, transaction_hash

Status Values

Payment Statuses:

  • waiting - Awaiting payment
  • confirming - Payment received, confirming
  • confirmed - Confirmed on blockchain
  • sending - Processing to merchant
  • completed - Successfully completed
  • partially_paid - Partial payment received
  • expired - Payment window expired
  • failed - Payment failed

Withdrawal Statuses:

  • pending - Awaiting processing
  • processing - Being processed
  • completed - Successfully sent
  • failed - Processing failed
  • cancelled - 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

  1. Verify webhook URL is accessible from internet
  2. Check for IP whitelisting or firewall rules
  3. Ensure URL returns 200 OK status
  4. Verify webhook configuration in dashboard

Signature Verification Fails

  1. Ensure using raw request body (not parsed)
  2. Verify webhook secret matches exactly
  3. Check for encoding issues (UTF-8)
  4. Compare signature algorithms (HMAC-SHA256)

Duplicate Webhooks

  1. Implement idempotency using transaction IDs
  2. Store processed webhook IDs
  3. Check status before processing
  4. 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