feat(data): record which event type each webhook processed - #708
Draft
paulvanbrenk wants to merge 1 commit into
Draft
paulvanbrenk wants to merge 1 commit into
paulvanbrenk wants to merge 1 commit into
Conversation
paulvanbrenk
force-pushed
the
feat/webhook-event-type
branch
2 times, most recently
from
September 6, 2026 02:05
e153e9a to
949cb0d
Compare
paulvanbrenk
force-pushed
the
feat/webhook-event-type
branch
2 times, most recently
from
September 6, 2026 02:48
38e371d to
9910928
Compare
ProcessedWebhookEvents stored only the provider's event id and a timestamp. Both webhooks write to it, and both ids are opaque -- evt_... from Stripe, msg_... from Svix -- so the ledger could say how many events were handled but never which kinds. That is precisely the gap that let the previous commit's bug hide. Four of five Stripe event types were being dropped for months, and the table recorded nothing that would have shown it: the rows that did exist were indistinguishable from the rows that should have. It is also what makes the fix confirmable in production, where the thing to watch for is invoice.payment_succeeded rows starting to appear rather than a count going up. EventType is nullable and capped at 128 like EventId beside it. Stytch splits its type across object_type and action, joined here so the column reads the same way as Stripe's dotted types. Rows written before this column exists stay null. They cannot be backfilled: the id alone does not carry the type, and Stripe only serves full event payloads for the last 30 days. GET /api/admin/webhook-events returns the field too. That endpoint exists to answer "did we process that webhook?", which it could not do usefully while every row in it looked the same. The OpenAPI spec and generated client are regenerated to match. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
paulvanbrenk
force-pushed
the
feat/webhook-event-type
branch
from
September 6, 2026 03:06
9910928 to
2700cbb
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #707.
What
ProcessedWebhookEventsstored only the provider's event id and a timestamp:Both webhooks write to that one table —
StripeWebhook.cswrites Stripeevt_…ids,StytchWebhook.cswrites Svixmsg_…ids — and both formats are opaque. The ledger could say how many events were handled, never which kinds.Why it matters
That's exactly the gap that let #707's bug hide for months. Four of five Stripe event types were being dropped, and this table recorded nothing that would have revealed it — the rows that existed looked identical to the rows that should have existed.
It's also what makes #707 confirmable in production. The thing to watch for after deploying is
invoice.payment_succeededrows starting to appear, which a bare count can't distinguish from someone signing up.Details
EventTypeis nullable, capped at 128 to matchEventIdbeside it (repo convention is explicit lengths; the first draft generatednvarchar(max)and was redone).patchnotes-email/prisma/schema.prismamirrors the same database and is updated to match, or the drift check fails.object_typeandaction; joined here asuser.CREATEso the column reads like Stripe's dotted types.GET /api/admin/webhook-eventsreturns the field. That endpoint exists to answer "did we process that webhook?", which it couldn't do usefully while every row looked the same. OpenAPI spec and generated client regenerated to match.Rows written before this column exists stay null and can't be backfilled — the id alone doesn't carry the type, and Stripe only serves full event payloads for the last 30 days. The admin endpoint returns them as
nullrather than omitting them, and there's a test pinning that.🤖 Generated with Claude Code