← All notes

Fintech

Webhooks need a log, not a handler

Processing a webhook inline means a provider retry, a deploy, or a slow query can silently cost you an event you cannot get back.

  • Invexa Technologies
  • 2 min read

The default webhook implementation is a route that parses the payload, updates some rows, and returns 200. It works until it does not, and when it does not the event is gone — the provider retried a few times, got errors, and stopped.

Accept, then process

Split the endpoint into two jobs it should never have been doing at once.

The endpoint’s only responsibility is to verify the signature, write the raw body to a table, and return 200 as fast as possible. No business logic, no joins, nothing that can be slow or throw.

webhook_events
  id
  provider
  provider_event_id   unique
  signature_valid
  raw_body            jsonb
  received_at
  processed_at        null until handled
  attempts
  last_error

A worker picks up unprocessed rows and does the actual work. If it fails, the row stays unprocessed with an incremented attempt count and the error recorded. Nothing is lost, and you can retry a year later against fixed code.

The unique index is the deduplication

Providers deliver twice. Not rarely — by design, because at-least-once is the only guarantee a network allows. A unique provider_event_id makes the second delivery a no-op insert conflict instead of a duplicated refund.

If your provider does not send a stable event id, hash the raw body and use that. It is weaker — two legitimate identical events collapse — but far better than nothing.

Verify before you store, not after

Signature verification happens on the raw bytes, before any parsing. Parse first and you have already let unverified input into a JSON decoder and possibly into your schema. Store the result of the check on the row, so a later audit can tell the difference between “we ignored it” and “it was forged”.

Ordering is not guaranteed

A success event can arrive after the refund event for the same object. Handlers must be written against the current state of the row, not against an assumed sequence — check what it says now, decide whether the event still applies, and ignore it if it does not.

What this buys

A provider outage becomes a backlog instead of data loss. A bug in the handler becomes a replay instead of an incident. And every event that ever arrived is still on disk, which is the only thing that makes “why is this account wrong” answerable.

Next step

Have a project in mind?

A 30-minute call is usually enough to know whether we are the right team for it. If we are not, we will say so.

Start a project

Replies within one working day