Skip to content
Engineering

Payment API Versioning: Changing an API Without Breaking Integrations

Payment integrations live for years, and a change that breaks one stops money moving. What counts as a breaking change in a payments API (including the ones that look harmless), versioning strategies and their trade-offs, how to deprecate properly, and how to integrate with providers' APIs so their changes don't break you.

LILPE Editorial Team9 October 2026 8 min read
Cover for an engineering guide to versioning payment APIs without breaking integrations

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.

Changes to a payments API and whether they break integrations
ChangeBreaking?Why
Remove or rename a fieldYesClients reading it get nothing, or fail to parse
Change a field's type or unitYesAmount in rupees becoming amount in paise is a hundredfold error
Make an optional request field requiredYesExisting requests start failing validation
Add a new value to a status or enumOftenClients with exhaustive handling may crash or treat it as failure
Change error codes or their meaningYesRetry and decline logic keys off them
Change a default (for example, automatic capture)YesSame request, different money movement
Change the webhook payload or signing schemeYesHandlers reject or misread events
Tighten validation (shorter max length, stricter formats)YesPreviously valid requests now fail
Add an optional field to a responseUsually notSafe for clients that ignore unknown fields
Add a new endpoint or event typeUsually notUnless 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.

Common ways to version an API
StrategyHow it looksStrengthsWeaknesses
Version in the path/v1/payments, /v2/paymentsObvious, easy to route and cacheBig-bang versions; clients must move everything at once
Version in a headerA version header on each requestClean URLs; versions per requestEasy to forget; harder to test in a browser
Dated versions pinned per accountEach account is pinned to a release date; it upgrades explicitlySmall, frequent changes; clients upgrade on their scheduleThe provider maintains many versions with transformation layers
Evolution without versionsOnly additive changes, everNo migration work for clientsMistakes 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:

  1. Announce the deprecation with a clear timeline, a changelog entry and a migration guide that lists every breaking change and its replacement.
  2. 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.
  3. 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.
  4. 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.
  5. Retire on the published date, and keep returning a clear error that points to the migration guide.
HTTPA response from a deprecated endpoint (RFC 9745 and RFC 8594 headers; values are examples)
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.
TypeScriptMapping a provider's statuses with a safe default for values you don't know
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.

Official sources

Last reviewed . Examples, amounts and screens marked illustrative are not LILPE figures.

Route every payment to where it succeeds.

Tell us which providers you use today and where payments get stuck. We'll show you what the setup would look like on LILPE.