Webhook Best Practices
Implementing webhooks effectively requires careful consideration of several factors to ensure reliability, security, and performance. This guide outlines best practices for integrating webhooks with banca.me.
Respond Quickly​
When receiving a webhook, your endpoint should respond with a 2XX status code as quickly as possible, typically within 5 seconds. Process the event data asynchronously after acknowledging receipt.
app.post("/webhooks/bancame", (req, res) => {
// Send 200 OK immediately
res.status(200).send("Webhook received");
// Process the event asynchronously
setTimeout(() => {
processWebhookEvent(req.body);
}, 0);
});
Be Prepared for a Single Retry​
If your endpoint fails to respond with a 2XX status code, banca.me will attempt to retry the webhook delivery once. This means you need to be prepared to handle the same event twice if your endpoint experiences temporary issues.
app.post("/webhooks/bancame", (req, res) => {
try {
// Send 200 OK immediately, even if there are issues with your server
res.status(200).send("Webhook received");
// Process the event asynchronously
setTimeout(() => {
processWebhookEvent(req.body);
}, 0);
} catch (error) {
// Even if your processing code has an error, still return 200
// to avoid an unnecessary retry
console.error("Error in webhook handler:", error);
if (!res.headersSent) {
res.status(200).send("Webhook received");
}
}
});
Remember that after a single retry, if your endpoint still fails to respond correctly, the event will not be sent again. Make sure your infrastructure is reliable to avoid missing important events.
Implement Idempotency​
Webhooks may occasionally be delivered more than once for the same event. Design your webhook handler to be idempotent, meaning that processing the same event multiple times doesn't result in unintended side effects.
async function processWebhookEvent(event) {
const { eventId, data } = event;
// Check if this event has already been processed
const isProcessed = await checkIfEventProcessed(eventId);
if (isProcessed) {
console.log(`Event ${eventId} already processed, skipping`);
return;
}
// Process the event...
// Mark as processed
await markEventAsProcessed(eventId);
}
Store Event IDs​
Maintain a record of processed event IDs, along with timestamps and processing status, to support idempotency and facilitate debugging.
CREATE TABLE webhook_events (
event_id VARCHAR(255) PRIMARY KEY,
received_at TIMESTAMP NOT NULL,
processed_at TIMESTAMP,
status VARCHAR(50) NOT NULL,
payload JSON NOT NULL
);
Implement Proper Error Handling​
Your webhook handler should include comprehensive error handling to ensure that failures are properly logged and managed.
async function processWebhookEvent(event) {
try {
// Process the event...
} catch (error) {
console.error(`Error processing event ${event.eventId}:`, error);
// Log the error, potentially retry, or alert your team
await logWebhookError(event.eventId, error);
}
}
Use Secure Protocols​
Always use HTTPS for your webhook endpoints to ensure that data is encrypted in transit. Never accept webhook calls over unencrypted HTTP in production.
Implement Rate Limiting​
Protect your webhook endpoint from potential abuse by implementing rate limiting.
const rateLimit = require("express-rate-limit");
const webhookLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
});
app.post("/webhooks/bancame", webhookLimiter, (req, res) => {
// Your webhook handler...
});
Monitor Webhook Deliveries​
Implement monitoring to track webhook deliveries and processing:
- Log all received webhooks
- Track processing success and failure rates
- Set up alerts for repeated failures or unusual patterns
async function logWebhookReceived(eventId, payload) {
await db.query(
"INSERT INTO webhook_logs (event_id, received_at, payload) VALUES (?, ?, ?)",
[eventId, new Date(), JSON.stringify(payload)]
);
}
Implement Retries for Your Logic​
If your business logic processing fails, implement a retry mechanism with exponential backoff.
async function processWithRetries(eventId, maxRetries = 3) {
let attempts = 0;
while (attempts < maxRetries) {
try {
await processBusinessLogic(eventId);
return; // Success
} catch (error) {
attempts++;
console.error(`Processing attempt ${attempts} failed:`, error);
if (attempts >= maxRetries) {
console.error(`All ${maxRetries} attempts failed, giving up`);
throw error;
}
// Wait with exponential backoff: 2s, 4s, 8s, etc.
const backoffMs = 2 ** attempts * 1000;
await new Promise((resolve) => setTimeout(resolve, backoffMs));
}
}
}
Gracefully Handle Downtime​
Your application should have a strategy for handling webhook events that arrive during maintenance or downtime:
- Use a queue service to buffer events
- Implement catch-up logic to process stored events when your service comes back online
- Consider using a managed webhook service for critical integrations
Keep Your Endpoint URL Secret​
Treat your webhook URL as a secret. Avoid publishing it in public repositories or exposing it in client-side code.
Keep Dependencies Updated​
Regularly update your webhook handler's dependencies to patch security vulnerabilities.
Test Regularly​
Periodically test your webhook endpoint to ensure it's functioning correctly:
- Test with valid and invalid signatures
- Test with malformed payloads
- Test with rate limiting edge cases
- Test recovery from downtime
Summary​
Following these best practices will help you build a reliable, secure, and robust webhook integration with banca.me. Remember that webhooks are critical integration points, so it's worth investing time in getting the implementation right.