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

  1. Always Verify Signatures: Never trust unsigned webhooks
  2. Use HTTPS: Always receive webhooks over encrypted connections
  3. Validate Payloads: Check that required fields are present
  4. Handle Idempotency: Use event IDs to prevent duplicate processing
  5. 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/webhooks

Webhook 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!