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
  1. Receive the webhook event.
  2. Acknowledge immediately with 200.
  3. Queue the event for asynchronous processing.
  4. 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