Webhook event payloads have all the versioning problems of API responses without any of the version-pinning escape hatches. A REST response is requested by the customer and the request can carry a version header that selects the schema. A webhook is pushed to the customer by the platform, and the customer cannot opt into a new schema by changing what they send. The right design treats the payload as customer contract from day one, and the wrong design produces customer-facing breakage every time the schema needs to evolve.
What can change without breaking customers
The asymmetry between provider cost and customer cost makes some changes safe and others dangerous. Adding a new optional field is safe because well-behaved customer parsers ignore unknown fields. Adding a new event type is safe for subscriptions that did not opt into it. Adding new values to a previously-fixed enum is dangerous because customer code typically branches on the value and may have a default that does the wrong thing for unknown values.
Changing a field type is always breaking. Renaming a field is always breaking. Removing a field is always breaking even if the field is documented as deprecated. Changing the semantic meaning of a field while keeping the name is the worst case because it slips past type checking and produces wrong behavior in production. The discipline is to treat the payload contract as something that grows by addition and never by mutation, with the rare exceptions requiring the full versioning machinery.
The per-subscription versioning pattern
The Stripe convention is that every webhook subscription is pinned to an API version at creation time and continues receiving payloads in that version forever unless the customer explicitly upgrades. The platform retains the version metadata per subscription and selects the appropriate payload schema at send time. New customers default to the latest version. Existing customers keep their pinned version through arbitrary platform schema evolution.
The implementation requires a webhook_subscriptions table with an api_version column and a payload serialization layer that can produce any supported version from the canonical internal representation. The serialization layer is the operational cost, growing in complexity proportional to the number of supported versions multiplied by the schema divergence between them. Most providers cap the supported version range at two or three years of history to bound the cost.
The embedded version field pattern
An alternative is embedding the schema version directly in the payload, typically as a top-level field like api_version or schema_version. The customer subscribes once and receives whatever version the platform is currently sending. Customer code branches on the version field to handle the schema differences.
This pattern is operationally simpler for the platform because there is no per-subscription state and no schema selection at send time. It is operationally harder for the customer because every parser must handle the version branching. Most major providers reject this pattern in favor of per-subscription versioning because the customer-cost asymmetry dominates the platform-cost simplification.
The deprecation pipeline
A webhook schema version cannot be deprecated until the platform has notified every subscription using it and given them time to migrate. The right pipeline has four stages. The announcement at month zero documents the new version and the sunset date. Monthly reminders from month three onward target subscriptions still on the old version with usage data. The final warning at month twenty-two ships with explicit subject lines and dashboard banners. The sunset at month twenty-four switches the remaining subscriptions to the new version with a final 30-day window to roll back if customer parsers break.
The two-year window is the standard for B2B SaaS because customer planning cycles run on annual budgets and integration changes require quarterly engineering allocation. Shorter windows produce customer churn. Longer windows balloon the operational cost of supporting old versions. The discipline is to start the pipeline as soon as the new version stabilizes rather than delaying the announcement.
Our use across the four products
Across DocuMint, CronPing, FlagBit, and WebhookVault, every webhook payload includes a top-level api_version field and every subscription has a pinned version. We have not yet shipped a breaking change because the four products are too young to have accumulated schema regret. The infrastructure is in place to handle the eventual migration.
The discipline that prevents future regret is treating the payload as customer contract from the first webhook ever sent. Every field name is one we are willing to keep forever, every type is one we are willing to defend against alternative representations, and every enum is one whose value set we have thought about extending. The cost of this discipline at the start is small. The cost of breaking it later is large enough to drive customers to competitors.
Our products: DocuMint (PDF invoice generation API), CronPing (cron job monitoring with status pages), FlagBit (feature flags API for modern teams), and WebhookVault (webhook capture and replay) put these patterns into production.