Engine Room

Nine copies arrive. One change is made.

Billing events can arrive twice, late, or in the wrong order. This page runs that case against the same code that handles real Kinde events, and shows you each step.

3 events, 9 copies, wrong order, 1 failure. A new test account each time.

  1. The copies appear here as they arrive.

The four rules behind it

Rule 1

Store, then answer

The endpoint checks the signature, writes the event, and only then answers 200. Kinde retries until it gets that 200, so no event is lost between the two systems.

INSERT INTO inbox_events (source, dedupe_key, ...)
ON CONFLICT (source, dedupe_key)
DO UPDATE SET seen_count = inbox_events.seen_count + 1

Rule 2

One key for each event

Kinde sends a webhook-id header that stays the same on every retry. That header is the dedupe key. A second copy adds one to a counter and does nothing else.

const dedupeKey = request.headers.get("webhook-id")

Rule 3

The effect and the mark commit together

A worker claims an event with a lease and applies it in one transaction. If the worker stops half way, the database rolls back and the event runs again when the lease ends.

SELECT id FROM inbox_events WHERE ...
FOR UPDATE SKIP LOCKED

Rule 4

Effects are safe to repeat

A grant is a ledger row with a key for the account and the month. A plan change applies only if it is newer than the one stored. So a replay changes nothing.

INSERT INTO ledger (..., entry_key)
VALUES (..., 'pro:kp_123:2026-10')
ON CONFLICT (entry_key) DO NOTHING

Real Kinde events on this server

Types and counts only. Bottle does not show payloads or account details here.

4
Events stored
4
Copies received
0
Duplicates dropped
0
Waiting to apply
  • customer.plan_changedplan set to pro, Pro allowance granted
  • customer.agreement_cancelledplan set to free
  • customer.plan_changedignored: a newer plan change is already stored
  • user.authenticatedignored: no handler for this type