If you sell credits or usage through Stripe, LemonSqueezy or Polar, your webhook handler will receive the same event more than once. Providers retry when your server is slow, when a deploy lands mid-request, when a response gets lost. If the handler does balance += 100 every time it sees checkout.session.completed , somebody eventually gets paid twice.
The fix is two checks, not one. Here is what each one does and why the second exists.
Check 1: de-dupe on the event id, inside the same transaction
Keep a table of events you have handled, keyed by provider and event id. Insert before you do any work, and skip the work if the insert did nothing:
await db . transaction ( async ( tx ) => { const inserted = await tx . insert ( billingWebhookEvents ) . values ({ provider , eventId , type : eventType }) . onConflictDoNothing () . returning ({ eventId : billingWebhookEvents . eventId });
if ( inserted .…