Webhook Best Practices
Return 200 Quickly
Your webhook endpoint should return a 200 status code as fast as possible. Do the heavy processing asynchronously.
app.post('/webhooks/inflow', express.raw({ type: 'application/json' }), (req, res) => {
const raw = req.body.toString('utf8');
// Verify signature first — see Verifying signatures
res.status(200).json({ received: true });
const { data } = JSON.parse(raw);
processEvent(data).catch(console.error);
});If your endpoint doesn't respond with a 200 within 15 seconds (the hard timeout), the delivery is considered failed. As a best practice, aim to respond within 5 seconds by processing events asynchronously.
Handle Duplicate Events
Your endpoint may receive the same event more than once. Always implement idempotent processing.
Use the svix-id header as the unique delivery identifier — it is the same value across all retry attempts for the same event:
app.post('/webhooks/inflow', express.raw({ type: 'application/json' }), async (req, res) => {
const deliveryId = req.headers['svix-id'];
// Respond immediately
res.status(200).json({ received: true });
// Check if already processed
const existing = await db.webhookEvents.findOne({ deliveryId });
if (existing) return;
// Process the event
const { data } = JSON.parse(req.body);
for (const event of data) {
await handlePayment(event.payload);
}
// Record that we've processed this delivery
await db.webhookEvents.create({ deliveryId, processedAt: new Date() });
});Verifying webhook signatures
Inflow signs every delivery with Svix. You must verify svix-id, svix-timestamp, and svix-signature using your endpoint secret before processing the body.
See the dedicated guide: Verifying signatures (SDK example, manual HMAC steps, test vectors, and GET /api/webhook/{webhookId}/secret).
Handle All Event Types
Even if you only care about specific events, your endpoint should handle unexpected event types gracefully:
const { data } = JSON.parse(rawBody);
for (const event of data) {
switch (event.eventType) {
case 'payment.created':
case 'payment.authorized':
case 'payment.settled':
case 'connect.payment.authorized':
case 'connect.payment.settled':
handlePayment(event.payload);
break;
default:
console.log('Unhandled event type:', event.eventType);
break;
}
}Always return 200 even for event types you don't process — otherwise Inflow may consider the delivery failed.
Use HTTPS
Your webhook URL must use HTTPS. HTTP endpoints are not supported.
Monitor Webhook Health
Periodically check your webhook status:
curl https://api.inflowpay.xyz/api/webhook/{webhookId}/status \
-H "X-Inflow-Api-Key: inflow_prod_your_key"If the status shows disabled or a failureReason, investigate and fix the issue, then re-enable the webhook.
Recommended Architecture
Inflow Webhook
|
v
[Your Endpoint] --> [Message Queue / Job] --> [Business Logic]
|
v
Return 200
- Receive the webhook event.
- Acknowledge immediately with
200. - Queue the event for asynchronous processing.
- Process the event in a background worker.
This architecture ensures you never miss events due to processing delays.
Checklist
- Endpoint uses HTTPS
- Returns 200 within 5 seconds (hard limit: 15s)
- Handles duplicate events (idempotent)
- Processes events asynchronously
- Handles unknown event types gracefully
- Logs all received events for debugging
- Monitors webhook status regularly