A payment integration, once it works, tends to be left alone for years. The team that built it moves on; the code keeps taking money. That longevity is exactly why API changes are dangerous in payments. A renamed field or a new status value that a dashboard would shrug off can, in a checkout, mean orders stuck as unpaid, refunds that never go out, or a webhook handler that silently drops events.
This guide looks at versioning from both sides. If you run a payments API, for your own apps, internal teams or partners, it covers what counts as breaking, how to version and how to retire old versions. If you integrate with payment providers' APIs, it covers how to build so their changes don't break you.
What counts as a breaking change
A change is breaking if an existing, correctly written integration can stop working or start behaving differently without changing its code. Some are obvious; the ones that hurt are the ones that look additive.
| Change | Breaking? | Why |
|---|---|---|
| Remove or rename a field | Yes | Clients reading it get nothing, or fail to parse |
| Change a field's type or unit | Yes | Amount in rupees becoming amount in paise is a hundredfold error |
| Make an optional request field required | Yes | Existing requests start failing validation |
| Add a new value to a status or enum | Often | Clients with exhaustive handling may crash or treat it as failure |
| Change error codes or their meaning | Yes | Retry and decline logic keys off them |
| Change a default (for example, automatic capture) | Yes | Same request, different money movement |
| Change the webhook payload or signing scheme | Yes | Handlers reject or misread events |
| Tighten validation (shorter max length, stricter formats) | Yes | Previously valid requests now fail |
| Add an optional field to a response | Usually not | Safe for clients that ignore unknown fields |
| Add a new endpoint or event type | Usually not | Unless clients subscribe to all events and fail on unknown ones |
Versioning strategies
There's no universally right scheme. What matters is that clients choose when to move, and that the scheme is applied consistently, including to webhooks.
| Strategy | How it looks | Strengths | Weaknesses |
|---|---|---|---|
| Version in the path | /v1/payments, /v2/payments | Obvious, easy to route and cache | Big-bang versions; clients must move everything at once |
| Version in a header | A version header on each request | Clean URLs; versions per request | Easy to forget; harder to test in a browser |
| Dated versions pinned per account | Each account is pinned to a release date; it upgrades explicitly | Small, frequent changes; clients upgrade on their schedule | The provider maintains many versions with transformation layers |
| Evolution without versions | Only additive changes, ever | No migration work for clients | Mistakes are permanent; eventually needs a new major version |
Many payment APIs combine approaches: a major version in the path that changes rarely, and smaller dated or header-based versions within it. Whatever you choose, document exactly which changes you consider non-breaking, so clients know what to tolerate.
Version your webhooks too
Webhook payloads are part of your API, and they're harder to evolve because the client can't choose a version per request. Two practices help:
- Tie each webhook endpoint to an API version, so an account receives events shaped like the version its code was written for, and can upgrade its endpoint separately.
- When changing the signing scheme, sign with both the old and new scheme during an overlap period, and let clients switch verification before the old one is removed.
Our guide to payment gateway webhooks covers signature verification and the other rules for handling events.
Deprecating and retiring a version
Retiring an old version is where goodwill is won or lost. A process that works:
- Announce the deprecation with a clear timeline, a changelog entry and a migration guide that lists every breaking change and its replacement.
- Signal it in the API itself. RFC 9745 defines a Deprecation response header, with a link to more information, and RFC 8594 defines a Sunset header giving the date a resource is expected to stop responding.
- Measure who still uses the old version, per client or account, and contact them directly. Most integrations that miss a sunset date belong to people who never read the announcement.
- Consider short, scheduled "brownouts" before the final date, where the old version is unavailable for a few minutes, so forgotten integrations surface while there's still time.
- Retire on the published date, and keep returning a clear error that points to the migration guide.
HTTP/1.1 200 OK
Content-Type: application/json
Deprecation: @1798761600
Sunset: Fri, 30 Jul 2027 00:00:00 GMT
Link: <https://example.com/docs/migrate-v2>; rel="deprecation"; type="text/html"The Deprecation value is a structured-field date (seconds since 1970); Sunset uses the ordinary HTTP date format. Clients can log or alert on either header automatically, which is more reliable than hoping someone reads an email.
Consistent errors across versions
Error responses are part of the contract, and payment clients depend on them for retry and decline handling. A stable, documented error format, such as the Problem Details format in RFC 9457 with your own fields for a decline category and whether the request is retryable, lets you add new error types without changing how clients parse errors. Never change the meaning of an existing error code; add a new one.
If you integrate providers: build to survive their changes
Most payment teams are API consumers more often than producers. You can't control a provider's changes, but you can make your integration tolerant of them:
- Pin the version you integrated against, where the provider allows it, and upgrade deliberately rather than by default.
- Ignore unknown response fields instead of failing on them.
- Map provider statuses and error codes into your own internal set, in one place, with an explicit path for values you don't recognise.
- Watch for Deprecation and Sunset headers, and subscribe to every provider's changelog or status notices.
- Run contract tests against the provider's sandbox in your CI, so a change shows up as a failed build rather than a failed payment.
- Keep each provider behind an adapter, so a provider's change touches one module. Our guide to payment orchestration architecture shows where adapters sit.
type InternalStatus = "pending" | "succeeded" | "failed" | "unknown";
const STATUS_MAP: Record<string, InternalStatus> = {
created: "pending",
processing: "pending",
authorized: "succeeded",
captured: "succeeded",
failed: "failed",
declined: "failed",
};
function mapStatus(provider: string, raw: string): InternalStatus {
const mapped = STATUS_MAP[raw];
if (mapped) return mapped;
// A value we have never seen: don't guess. Keep the payment open,
// alert the team, and resolve it by checking the provider's status.
metrics.increment("payments.unknown_status", { provider, raw });
return "unknown";
}The important line is the last one. Treating an unrecognised status as failed is how a new "requires_action" or "partially_captured" value turns into refunds that shouldn't have happened or orders cancelled after payment.
This is one of the jobs an orchestration layer takes off your hands: LILPE's unified API page describes one request and status model across providers, with provider mappings updated on LILPE's side when a provider changes its API.
SDKs and libraries
Client libraries need their own versioning, separate from the API's. Semantic versioning works well here: a major version for breaking changes to the library's interface, a minor version for new features, a patch for fixes. Say clearly which API version each SDK release targets, and don't ship an API version change silently inside a patch release.
A checklist before you ship an API change
Run through this for every change to a payments API, however small it looks:
- Does any existing field change name, type, unit, format or meaning?
- Does any status, enum or error code gain a value or change meaning?
- Does any default behaviour change, especially around capture, refunds or settlement?
- Does validation get stricter anywhere?
- Do webhook payloads or signatures change?
- Is the change documented in the changelog, with a migration note if it's breaking?
- Can you see which clients will be affected, and have you told them?
Frequently asked questions
What is API versioning?
A way of changing an API while letting existing integrations keep working: changes that would break clients are released as a new version, and clients move to it when they're ready. Common schemes put the version in the URL path, in a request header, or pin each account to a dated version.
Is adding a field a breaking change?
Adding an optional field to a response is usually safe, as long as clients ignore fields they don't recognise. Adding a required field to a request, or a new value to a status or enum, usually is breaking, because existing clients either fail validation or mishandle the new value.
How should a payment API deprecate an old version?
Announce it with a timeline and migration guide, signal it with Deprecation and Sunset response headers, track which clients still use the old version and contact them, consider short scheduled brownouts, and retire it on the published date with an error that points to the guide.
How do I protect my integration from payment provider API changes?
Pin the API version, ignore unknown fields, map provider statuses and errors to your own set with a safe path for unknown values, keep each provider behind an adapter, run contract tests against the sandbox, and watch changelogs and deprecation headers.
Go deeper
Official sources
- IETF — RFC 9745, The Deprecation HTTP Response Header Field (March 2025)Deprecation header carrying a date, and the "deprecation" link relation.
- IETF — RFC 8594, The Sunset HTTP Header FieldIndicates that a URI is likely to become unresponsive at a specified point in the future.
- IETF — RFC 9457, Problem Details for HTTP APIsA standard JSON format for machine-readable error details, extensible with your own members.
- Semantic Versioning 2.0.0
Last reviewed . Examples, amounts and screens marked illustrative are not LILPE figures.




