On a platform that processes payments for many businesses, a payment webhook must answer one question safely: which business does this money belong to? The answer has to come from the field the provider cryptographically signs — the connected account that owns the charge — and never from free-form metadata riding alongside the event.
We learned this the direct way. A webhook resolved the owning business from a metadata.tenant_id field. Because metadata can be set by whoever creates the charge, a connected business could attribute a payment to a different business — and an inflated amount field could record more than was actually captured.
What the fix looks like
Three rules, enforced at the webhook boundary:
- Bind to the signed account. Resolve the business from the provider-signed
account, which maps to exactly one connected business. That mapping is the source of truth. - Refuse contradictory metadata. If metadata names a different business than the signed account, reject the event and log it — do not silently prefer one over the other.
- Clamp the amount. Record at most what the provider says it received. A metadata-supplied figure that does not reconcile to the actual amount received is ignored.
Only events with no connected account — the platform billing itself — fall back to trusting metadata, because the platform created that charge on its own account.
The test that locks it
The guardrail ships with a spec test written before the fix and confirmed failing first: feed a signed event for account A carrying metadata that names business B, then assert the handler resolves to A, refuses B, and records no more than the amount actually received. A security invariant without a failing-first test is a hope, not a guarantee.
The general pattern
Whenever a provider signs some fields and leaves others open, treat the signed set as authoritative and everything else as untrusted input. This is the payments-specific face of the same principle behind multi-tenant MCP safety: isolation has to be enforced on a value the caller cannot forge.