← notes

One Stripe account, and every endpoint gets every event

Jul 7, 2026

Stripe warned that one product's webhook was failing. The events breaking it belonged to a different product entirely.

Two products billed through a single Stripe account. Every webhook endpoint registered on that account receives every matching event, regardless of which product generated it. That is the whole mechanism, and it is easy to forget once the second product ships.

One product’s Rails handler crashed on checkout.session.completed events that came from the other product’s checkouts. The immediate cause was a metadata key mismatch: one product writes its plan under one key, the other reads a different key, so the handler dereferenced nil and raised. Harmless to the product that sent the event, fatal to the one receiving it.

The confusing part is the warning email. Stripe reports a failing endpoint URL, not a product, a customer, or the checkout that produced the event. So the alert named the product whose endpoint was returning 500s, while the events actually originated somewhere else. Reading that email at face value sends you debugging payments that were never broken.

Repeated failures also compound. Each event was retried around fourteen times, and Stripe schedules an endpoint for disabling after enough consecutive failures. A crash on data you do not care about can therefore take down processing for the data you do.

Two guards fix it. Ignore events whose shape you do not recognise and return 200, because a shrug is a valid response to someone else’s event. Then wrap the action in a catch-all that logs and still returns 2xx, so no single malformed event can accumulate enough failures to disable the endpoint.

Beyond that the choice is structural. Tag each event with a product marker in metadata and have every handler drop what is not addressed to it, or give each product its own Stripe account. The second is cleaner. The first is what you do when sharing the account was deliberate.