Stripe popularized a date-based approach to API versioning that focuses on consumer safety and predictable upgrades. Instead of incrementing /v2 and asking everyone to move at once, Stripe pins each account to a specific API version by date. New accounts default to the latest date. Existing accounts keep working until they explicitly upgrade. The result is a system that evolves quickly without surprising long-lived integrations.
In this guide, we will unpack the mechanics of Stripe's API versioning, what the upgrade experience feels like for developers, and how you can borrow the good parts for your own platform. If you need a broader survey of versioning strategies first, read our companion article on Best practices for API versioning and backward compatibility. For help choosing interface styles and evolution patterns, compare options in REST vs GraphQL.
The core idea: date-based epochs per account
Stripe represents API versions as dates such as 2024-10-01. Your account is pinned to a specific date. That date determines default behavior across endpoints. You can temporarily request a different version per call by sending a header, then upgrade your account when ready.
Conceptually, the model looks like this:
Account → Pinned API Version (YYYY-MM-DD) → Server selects behavior set
Request → Optional Override Header → Temporary behavior for this requestBenefits:
- Per-account safety: existing integrations do not break when the platform evolves.
- Incremental adoption: test an upgrade on a subset of requests by sending a header.
- Clear changelog semantics: changes are grouped into dated epochs instead of ambiguous vX numbers.
For the trade-offs between date epochs and path versions, see the quick guide in our general versioning article.
The headers: Stripe-Version and Accept overrides
Stripe accepts a Stripe-Version header to request a specific API behavior for a call. Some SDKs also support passing version overrides. The account's pinned version remains unchanged unless you explicitly update it in the dashboard or via support.
curl https://api.stripe.com/v1/customers \
-u sk_test_...: \
-H "Stripe-Version: 2024-10-01"If you do not send an override, Stripe uses your account's pinned version. This is critical: version is a property of the integration, not just a URL suffix. It reduces the need to duplicate endpoints or maintain many surface variants.
Backward compatibility rules in practice
Stripe biases to additive changes that are safe for old clients. Breaking changes - renamed fields, changed types, shifted semantics - are bundled into a new dated version. That version ships with a detailed changelog and migration guide.
Common patterns:
- Add fields without changing existing semantics. Old clients ignore unknown properties.
- Introduce new enums while maintaining a strategy for unknown values on old clients.
- Keep error envelopes stable and only extend with nested details.
- Defer default behavior changes to a new date version, often guarded with account-level flags before they graduate.
If you want the full cross-vendor playbook for safe evolution, our platform-agnostic guide has broader patterns: Best practices for API versioning.
How upgrades work: from dry run to switch-over
The best part of the Stripe model is how you can dry run an upgrade. You send Stripe-Version: <new-date> from a staging environment or even selectively in production. You capture diffs and errors, fix your code, then change the account's pinned version.
Upgrade loop:
- Read the changelog for your current date to the target date, noting breaking changes and deprecations.
- Add automated tests that request the target version via header.
- Ship to staging and canary some production traffic with per-request overrides.
- Fix incompatibilities, adjust schemas, and confirm metrics stability.
- Update the account's pinned version and remove per-request overrides.
This is essentially date epochs plus a built-in canary mechanism. If you are designing your own platform, you can emulate this by pinning clients to an epoch and supporting an override header. See the Node example we used in the general guide for version negotiation, or adapt the Next.js pattern shown there: Header-based version selection.
Example: implementing a Stripe-like date epoch in Next.js
Here is a compact example that demonstrates the override header and a pinned default. It is not Stripe's code - it is the pattern you can adopt.
// app/api/charges/[id]/route.ts
import { NextRequest, NextResponse } from "next/server";
type Epoch = "2024-01-01" | "2024-10-01";
const PINNED_EPOCH_FOR_ACCOUNT: Epoch = "2024-01-01"; // pretend we load this from DB
const resolveEpoch = (req: NextRequest): Epoch => {
const override = req.headers.get("stripe-version") || "";
return override === "2024-10-01" ? "2024-10-01" : PINNED_EPOCH_FOR_ACCOUNT;
};
export const GET = async (req: NextRequest, { params }: { params: { id: string } }) => {
const epoch = resolveEpoch(req);
if (epoch === "2024-10-01") {
return NextResponse.json({ id: params.id, status: "succeeded", receipt_url: "https://example.com/r/123" });
}
return NextResponse.json({ id: params.id, status: "succeeded" });
};Notice how we add fields in the newer epoch without changing the essentials. Old clients still work. New clients can adopt enhancements before the account upgrade happens.
Changelogs and migration guides that actually help
Stripe's changelogs are narrative - they tell you what changed and how to migrate, with concrete examples. They also separate additive enhancements from breaking changes. For your own platform, adopt a similar structure:
- Group changes by date epochs.
- For each breaking change, include a before and after request and response.
- Link to SDK updates and deprecation warnings so developers see issues at build time.
If you are generating docs from OpenAPI or GraphQL schemas, add a CI step that diffs versions and renders a human-readable summary. Our broader write up on Next.js SEO best practices includes patterns to keep docs discoverable - canonical links, structured data, and stable URLs.
Webhooks - versioned contracts in reverse
Stripe treats webhook payloads as contracts too. Payload shapes evolve with the same date versions. You can test event payloads for a specific version from the dashboard. This is a great habit to copy: version event types or payload envelopes alongside your API, and publish example payloads for each epoch.
Practical checklist:
- Version payload schema per epoch; offer test sends per version.
- Keep a stable envelope and extend details where needed.
- Version signatures carefully and document verification examples.
If you are designing event schemas from scratch, cross reference our general advice on webhook versioning in the backward compatibility article.
SDKs and pinned API versions
Stripe's official SDKs default to the account's pinned version. If you request a different version, you pass an override. This separation keeps the SDK and API versioning concerns decoupled. Your SDK can use SemVer for library changes while the API uses date epochs for behavior.
If you maintain SDKs for your platform, adopt the same split: SDK SemVer for code, API epochs for server behavior. For type-safety and smoother migrations, TypeScript pays off - see TypeScript default for web development.
Designing your own Stripe-like policy
You can borrow the following:
- Use date-based epochs that pin per account.
- Support a per-request override header in a safe list of endpoints.
- Publish narrative changelogs grouped by epoch with before and after examples.
- Provide staging data and test tools to simulate versions.
- Track version adoption in dashboards and message lagging clients.
Pair these with the more general operational habits - schema diffs, contract tests, and replay testing - that we outline in Best practices for API versioning.
Common pitfalls to avoid
- Hidden default changes between date epochs without explicit mention. Call them out.
- Partial upgrades that silently mix behaviors across endpoints. Keep epochs consistent.
- Relying on SDK auto-magic to mask breaking changes. Surface warnings clearly.
- Forgetting webhooks. They need the same version discipline as your request-response API.
Conclusion
Stripe's date-based versioning is not a magic trick. It is a set of disciplined choices: pin behavior per account, allow safe overrides, ship clear changelogs, and give developers time to upgrade. Adopt those habits and your API can evolve quickly without surprising integrations that keep your product running. For a fuller exploration of versioning strategies - path versus header versus epochs - and migration playbooks, read Best practices for API versioning and backward compatibility. For architecture patterns that minimize blast radius when behavior changes, revisit Clean architecture for fullstack apps.
Where to go next
- Compare interface styles and evolution trade-offs: REST vs GraphQL
- Strengthen contracts and SDK ergonomics: TypeScript default for web development
- Operationalize rollouts and docs: Best practices for API versioning and Next.js SEO best practices
