Webhook Integration Guide: Real-Time Payment Notifications
Webhooks are essential for building modern payment systems. They enable real-time notifications when payments are received, confirmed, or fail. This guide covers everything you need to know about webhook integration.
What Are Webhooks?
Webhooks are HTTP callbacks that send real-time notifications to your server when specific events occur. Unlike polling (checking for updates repeatedly), webhooks push data to you instantly.
Why Use Webhooks?
Real-Time Updates
- Instant Notifications: Know immediately when payments are received
- No Polling Required: Save server resources and API calls
- Better User Experience: Update your UI in real-time
Automation
- Auto-Fulfill Orders: Automatically process orders when payment confirms
- Update Databases: Keep your records synchronized
- Trigger Workflows: Start other processes based on payment events
Reliability
- Retry Logic: Webhooks retry failed deliveries
- Event History: Track all webhook events
- Idempotency: Handle duplicate events safely
Common Webhook Events
Invoice Events
- invoice.created: New invoice generated
- invoice.paid_detected: Payment detected (awaiting confirmations)
- invoice.confirmed: Payment confirmed (required confirmations met)
- invoice.expired: Invoice expired without payment
- invoice.overpaid: Payment received exceeds invoice amount
Withdrawal Events
- withdrawal.created: Withdrawal request submitted
- withdrawal.processing: Withdrawal being processed
- withdrawal.completed: Withdrawal successfully completed
- withdrawal.failed: Withdrawal failed
Webhook Security
Signature Verification
Always verify webhook signatures to ensure requests are legitimate:
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
const digest = hmac.update(JSON.stringify(payload)).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(digest)
);
}
// In your webhook handler
app.post('/webhooks', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const isValid = verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Process webhook
});Best Practices
- Always Verify Signatures: Never trust unsigned webhooks
- Use HTTPS: Always receive webhooks over encrypted connections
- Validate Payloads: Check that required fields are present
- Handle Idempotency: Use event IDs to prevent duplicate processing
- Log Everything: Keep records of all webhook events
Webhook Implementation
Setting Up Webhooks
// Create webhook endpoint
const response = await fetch('https://api.fromchain.plus/webhooks', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://yourdomain.com/webhooks',
events: [
'invoice.created',
'invoice.confirmed',
'invoice.expired'
]
})
});Handling Webhooks
app.post('/webhooks', async (req, res) => {
// Verify signature
const signature = req.headers['x-webhook-signature'];
if (!verifySignature(req.body, signature)) {
return res.status(401).send('Invalid signature');
}
const { event, data } = req.body;
// Handle different events
switch (event) {
case 'invoice.confirmed':
await handlePaymentConfirmed(data);
break;
case 'invoice.expired':
await handleInvoiceExpired(data);
break;
default:
console.log('Unknown event:', event);
}
// Always return 200 quickly
res.status(200).json({ received: true });
});
async function handlePaymentConfirmed(data) {
const { invoiceId, amount, tenantId } = data;
// Update order status
await updateOrderStatus(invoiceId, 'paid');
// Fulfill order
await fulfillOrder(invoiceId);
// Send confirmation email
await sendConfirmationEmail(invoiceId);
}Testing Webhooks
Using ngrok for Local Development
# Install ngrok
npm install -g ngrok
# Start your local server
npm run dev
# In another terminal, expose your local server
ngrok http 3000
# Use the ngrok URL for webhook testing
# https://abc123.ngrok.io/webhooksWebhook Testing Checklist
- Verify signature validation works
- Test all event types
- Test retry logic
- Test idempotency handling
- Test error scenarios
- Monitor webhook delivery times
Common Issues and Solutions
Webhook Not Received
Possible Causes:
- Firewall blocking requests
- Server not accessible from internet
- Invalid webhook URL
Solutions:
- Check server logs
- Verify webhook URL is accessible
- Test with curl or Postman
Duplicate Events
Solution: Use idempotency keys or event IDs to prevent duplicate processing:
const processedEvents = new Set();
app.post('/webhooks', (req, res) => {
const { eventId } = req.body;
if (processedEvents.has(eventId)) {
return res.status(200).json({ received: true, duplicate: true });
}
processedEvents.add(eventId);
// Process event...
});Slow Response Times
Solution: Process webhooks asynchronously:
app.post('/webhooks', (req, res) => {
// Return immediately
res.status(200).json({ received: true });
// Process asynchronously
processWebhookAsync(req.body);
});Monitoring Webhooks
Key Metrics
- Delivery Rate: Percentage of successful deliveries
- Response Time: How quickly you process webhooks
- Error Rate: Percentage of failed webhook processing
- Retry Count: How many times webhooks are retried
Logging
function logWebhook(event, data, status) {
console.log({
timestamp: new Date().toISOString(),
event,
invoiceId: data.invoiceId,
status,
processingTime: Date.now() - startTime
});
}Conclusion
Webhooks are essential for building responsive payment systems. Proper implementation ensures real-time updates, automation, and reliability.
Get started with FromChain webhooks and build powerful payment automation today!
