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.

Deduplicate on payload.webhookEventId (mwe_…), the id Inflow gives each event. It is the same value across retry attempts of a delivery and when the event is replayed:

app.post('/webhooks/inflow', express.raw({ type: 'application/json' }), async (req, res) => {
  // Respond immediately
  res.status(200).json({ received: true });

  const { data } = JSON.parse(req.body);
  for (const event of data) {
    const eventId = event.payload.webhookEventId;

    // Check if already processed
    const existing = await db.webhookEvents.findOne({ eventId });
    if (existing) continue;

    // Process the event
    await handleEvent(event.eventType, event.payload);

    // Record that we've processed this event
    await db.webhookEvents.create({ eventId, processedAt: new Date() });
  }
});

A delivery is a batch: the svix-id header identifies the HTTP request, not one event, and a replayed event arrives under a new svix-id. Keep verifying it as part of the signature, but deduplicate per event on webhookEventId.

Events sent before webhookEventId was introduced do not carry it — fall back to the resource id plus its status (e.g. pay_… + CHECKOUT_SUCCESS) for those.

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.

Never Infer State From a Sequence

Deliveries can arrive out of order. If you receive subscription.canceled and then subscription.active, do not conclude that the subscription is active — the second event may simply be an older delivery landing late. Treat each event as a trigger to reconcile, and read the authoritative state from the API when the two disagree.

To order or date events, use payload.occurredAt, the time of the change the event reports, recorded when the change was made. It is the same whether the delivery is on time, late, retried or replayed. Neither the delivery time nor the resource's updatedAt is the event's time: a retried delivery lands later, and updatedAt is the resource's last write, which may be an earlier change than the one the event reports.

Events sent before occurredAt was introduced do not carry it — fall back to the delivery time for those.

Granting access from webhooks? Granting Access with Webhooks maps each event to a grant or revoke decision, and Refreshing Entitlements shows the reconciliation fallback when a delivery is late or lost.

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.com/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.

In the Dashboard, Settings → Developers shows each webhook's Status. Error appears when the last delivery failed; click it to see why.

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

Did this page help you?