Back to Blog
Integration 12 min read Jan 10, 2025

Webhook Best Practices for Production

Learn how to securely handle webhooks and build resilient event-driven integrations.

Why Webhooks Matter

Webhooks enable real-time communication between CallPayMin and your application. When events occur (calls start, end, billing processed), we POST the event data to your configured endpoint.

Available Webhook Events

EventDescription
call.createdA new call has been created
call.startedA call has started (first participant joined)
call.endedA call has ended
call.billing.chargedBilling has been processed for a call
user.balance_lowUser balance below threshold
summary.readyAI summary is available

Security: Verify Signatures

Always verify webhook signatures to ensure requests are from CallPayMin. We sign every webhook with your webhook secret.

Security Warning

Never process webhooks without verifying the signature. Attackers can forge requests to your endpoint.

Node.js - Signature Verification
import crypto from '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)
  );
}

// In your webhook handler
app.post('/webhooks/callpaymin', (req, res) => {
  const signature = req.headers['x-callpaymin-signature'];
  const isValid = verifyWebhookSignature(
    JSON.stringify(req.body),
    signature,
    process.env.WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process the webhook...
  res.status(200).json({ received: true });
});

Handle Retries Gracefully

CallPayMin retries failed webhooks (5xx errors or timeouts) with exponential backoff. Make your handlers idempotent to handle duplicate deliveries.

Idempotent Handler
// Store processed event IDs to prevent duplicates
const processedEvents = new Set();

app.post('/webhooks/callpaymin', async (req, res) => {
  const eventId = req.body.id;

  // Check if already processed
  if (processedEvents.has(eventId)) {
    return res.status(200).json({ received: true, duplicate: true });
  }

  // Process the event
  await handleEvent(req.body);

  // Mark as processed
  processedEvents.add(eventId);

  res.status(200).json({ received: true });
});

Best Practices Checklist

  • Always verify signatures - Never skip signature verification
  • Return 200 quickly - Acknowledge receipt before processing
  • Use a queue - Process events asynchronously for reliability
  • Make handlers idempotent - Handle duplicate deliveries gracefully
  • Use HTTPS only - We only send webhooks to HTTPS endpoints
  • Log everything - Keep audit logs for debugging

Configure Webhooks

Set up your webhook endpoint in the Webhooks Dashboard. You can configure which events to receive and test your endpoint before going live.