入门指南

第三方 API 版本升级:三种版本、六步迁移、一条回滚路

对照 Stripe 与 GitHub 的公开版本策略,把依赖升级拆成盘点、钉版本、影子端点、切换、清旧五段,写清强类型 SDK、webhook 快照版本与 24 个月支持窗口的真实边界。

API 版本是三层:账号默认、SDK 锁定、事件快照。

作者
AOPC 编辑部
发布
AOPC 编辑部
发布时间
更新
2026/10/25
核查
已复核
一个人在白板上把账号默认版本、SDK 锁定版本与 webhook 快照版本画成三条并行轨道
经营者责任与 AI 执行层的关系

先看答案

适合谁
  • 依赖支付或代码托管 API 的一人公司
  • 收到「版本即将停用」通知、但不确定先改哪里的开发者
  • 自己维护 webhook 端点、没有专职运维的小团队
你会获得
先搞清楚是谁在替你选版本,再决定什么时候改版本号。
发布时间
实质更新
发布
AOPC 编辑部

60 秒结论

一分钟看懂这篇文章

API 版本是三层:账号默认、SDK 锁定、事件快照。

人负责

  • 决定升级窗口
  • 确认回滚路径
  • 裁决旧端点下线

AI 负责

  • 比对 changelog 差异
  • 生成双端点代码
  • 跑幂等回归
  1. 盘点版本
  2. 钉住版本
  3. 影子端点
  4. 切默认

直接答案

第三方 API 升级出事,很少是因为版本号写错,而是因为三个不同的东西都被叫作「版本」:

  1. 你账号上的默认版本——请求没写版本时,供应商替你选的那个;
  2. 你代码里 SDK 锁定的版本——装哪个 SDK,就发哪个版本的请求;
  3. 你的 webhook 目的地快照版本——事件按哪个版本渲染,创建端点时就定死了。

这三层可以互相不一致,而且不会互相报错。于是最常见的故障不是「升级失败」,而是「你以为自己还在旧版本,实际已经在用新版本」——某天改了一行 curl、换了一个 SDK 小版本,行为就变了。

所以在动手之前,先把三个问题写成一句话:

  • 我现在钉的是哪个版本?(不是「大概最新」,是具体字符串)
  • 谁在替我选版本?(账号默认、SDK 发行时间、还是端点创建时间)
  • 出问题时退回哪里?(旧端点、旧 pin、还是旧 SDK)

一人公司的特殊之处不在技术,而在没有人替你跑兼容性回归。大公司可以先升级再让 QA 扫一遍;你只能靠「顺序」和「可回滚」把风险压下去。

一、三种版本,三张表对不上

先看 Stripe 的公开口径。它的版本策略从 2024-09-30.acacia 起改成:

Starting with the 2024-09-30.acacia release, Stripe follows a new API release process where we release new API versions monthly with no breaking changes. Twice a year, we issue a new major release (for example, Basil) that starts with an API version containing breaking changes. You can safely upgrade to any monthly release without updating your code. Upgrading to a new major release can require changes to your existing integration.

同一个页面还写清了「谁在替你选版本」:

Requests made with curl use your Stripe account’s default API version (controlled in Workbench) unless you override it by setting the Stripe-Version header.

Webhook events also use your account’s default API version unless you set an API version during endpoint creation.

把这两句翻译成运维语言:

层 由谁决定 什么时候变 典型翻车方式
账号默认版本 Workbench 里的设置 有人在后台点了一次「升级」 你没改代码,线上行为变了
SDK 锁定版本 你装的 SDK 发行时间 依赖升级、或锁文件被重算 换了 SDK 小版本,请求版本跟着变
事件快照版本 端点创建时传入的 api_version / snapshot_api_version 只能新建端点 服务端升级了,事件还是旧结构

第三层最容易被忽略,因为它在 Stripe 的升级文档里写得很克制:

You can set snapshot_api_version only when you create an event destination. To use a different API version, create and test a destination configured with that version before deleting the existing destination.

「只能创建时设」意味着升级 webhook 不是改配置,而是换端点——这就决定了后面第六节那条「先建、后切、最后删」的顺序。

还有一个只在排障时才暴露的细节:回放历史事件时,事件结构按「事件发生那一刻」的账号默认版本渲染,不是你现在的版本。原文是「When you retrieve an event through the API, the structure of the event that Stripe returns is defined by the account’s default API version at the time the event occurred.」所以升级账号默认版本之后,你的对账脚本读三年前的旧事件,会拿到三年前的结构——补数据脚本要么兼容多版本,要么按事件时间分批处理。

二、先分清「加法」与「删除」:两类变更的处置完全不同

Stripe 的口径是「月度版本不含破坏性变更,大版本才可能要求改代码」。GitHub 则把两类变更逐条列了出来,这份清单值得直接当成自查表用。

破坏性变更(GitHub 会放进新版本):

  • 删除整个操作(endpoint)
  • 删除或重命名参数
  • 删除或重命名响应字段
  • 新增必填参数
  • 把原本可选的参数改成必填
  • 改变参数或响应字段的类型
  • 删除枚举值
  • 给已有参数新增校验规则
  • 改变认证或授权要求

加法变更(GitHub 明确说对所有受支持版本同时生效):

  • 新增操作
  • 新增可选参数
  • 新增可选请求头
  • 新增响应字段
  • 新增响应头
  • 新增枚举值

第二张表比第一张更值得警惕,因为它不会体现在版本号上。GitHub 原文:

Any additive (non-breaking) changes will be available in all supported API versions.

也就是说,你什么都不升级,响应里也可能多出一个字段、枚举里多出一个值。Stripe 在枚举那一节给了同一套防御:

Don’t assume that the values documented for an open enum are exhaustive. When you read an open enum from an API response, your code should include a safe fallback—such as a default branch in a switch statement—to gracefully handle values your integration doesn’t yet recognize.

两家说的其实是同一件事:解析器必须容忍未知值。如果你的代码里有 switch 没有 default、有 if status == "paid" 没有兜底分支,那么第一次「加法变更」就会变成你的线上故障——而它根本不算版本升级。

三、强类型 SDK:升级 SDK 就是升级 API 版本

这是最容易被「升级依赖」这四个字掩盖的一层。Stripe 写得很直白:

Because Java is a strongly-typed programming language, the API version used in the SDK is fixed and is the latest API version at the time of the SDK release.

Go 与 .NET 同理。而且官方不建议在强类型语言里覆盖版本:

We don’t recommend setting a different API version for strongly-typed programming languages, because the response objects might not match the strong types in the SDK and result in request failures.

对 Ruby、Python、PHP、Node 这类动态语言,情况反过来——可以全局或逐请求覆盖,但覆盖之后响应对象也按那个版本返回:

When you override the version globally or per-request, the API response objects are also returned in that version.

Node 还有一条 TypeScript 的坑:类型定义跟随 SDK 发行时的最新版本,手动覆盖版本「might cause inaccurate Typescript types」。

所以「升 SDK」和「升 API 版本」是两件不同的事,但在强类型语言里它们被绑死了:想换 API 版本,只能升(或降)SDK 主版本。这直接带出下一节的时间成本。

SDK 主版本不是永久维护的

Stripe 的 SDK 政策写了两件对一人公司很实际的事。

第一,新功能与修复只进最新主版本:

New features and bug fixes are released on the latest major version of the SDK. If you’re on an older major SDK version, we recommend upgrading to the latest major version … Older major versions of the package continue to be available for use, but won’t receive any additional updates.

第二,语言运行时到期后有一个 1–2 年的扩展窗口,窗口结束后的下一个 SDK 主版本就不再支持它:

When a language runtime version reaches its end of life, we mark it as “deprecated” in the tables below and begin its extended support window. The exact length of the extended support window varies by language, but the range is 1 to 2 years.

还有一句比前两句更值得抄进备忘录:

Though an SDK might continue to work on an unsupported language version, don’t continue to use it. SDKs that run on unsupported versions might break unexpectedly in any release, and the cause of that breakage might not be included in the changelog.

「可能在任何一次发布中意外损坏,而且原因不一定写进 changelog」——这就是「我什么都不动,应该没事」这句话的反面证据。不升级本身也是一种选择,它有成本,只是账单来得晚。

四、webhook 迁移:官方给了一条可以照抄的顺序

Stripe 的升级文档把 webhook 单独拆成一节,顺序写得很具体。以下步骤是页面原文的整理,不是我发明的流程:

第 0 步:先验证签名、放行来源 IP。

To upgrade your webhook endpoints, you need to verify incoming webhook signatures and allow traffic from Stripe public IP addresses.

第 1 步:建一个新端点,但先禁用。 URL 与旧端点相同,靠查询参数区分版本:

url: the same URL as your original webhook endpoint, but add a query parameter to distinguish between events sent to the two different endpoints. For example https://example.com/webhooks?version=2024-04-10.

第 2 步:启用新端点,但代码先「接住并忽略」它。 目的是不让它触发重试:

If the query parameter is for the newer API version, ignore the event and return a 200 response to prevent delivery retries.

第 3 步:切换处理逻辑。 新端点真正处理,旧端点改为返回 400:

If the query parameter is for the older version, ignore the event. We recommend returning a 400 status to let Stripe automatically retry the event. This ensures that if you need to revert, events are re-sent to the older webhook endpoint.

这一步是整个方案的核心:用 400 把旧端点变成「可回放的缓冲」。如果旧端点直接返回 200 丢弃事件,回滚之后就补不回来了。

第 4 步:观察,出问题按顺序退。 官方给的处置顺序是:回退代码 → 暂时禁用新端点 → 处理失败事件(因为之前返回了 400,Stripe 会自动重投)→ 修好 → 重新启用。

第 5 步:禁用旧端点。 注意一个不可逆的细节:

After you disable the old webhook endpoint, Stripe won’t re-deliver events that returned a 400.

也就是说,旧端点一旦下线,那条「回滚还能补事件」的路就断了。所以第 5 步应该是整条迁移里最后一个动作,而且要在一个观察窗口之后再做。

双端点并行期间,幂等不是可选项

If both destinations are active during the migration, your event handler must be idempotent because Stripe delivers subscribed events to both destinations.

同一笔付款会到两次。如果你的处理逻辑是「收到 invoice.paid 就发一封开通邮件」,用户会收到两封。迁移期的幂等要求比平时高一档:平时去重是优化,迁移期去重是正确性。

五、GitHub 的另一套披露方式:日期版本 + 24 个月窗口

GitHub 不用「月度 + 大版本」,而是日期命名的版本 + 请求头:

You should use the X-GitHub-Api-Version header to specify an API version.

几个数字和信号值得记住:

机制 具体口径
不写版本头 默认使用 2022-11-28
支持窗口 新版本发布后,上一版本「at least 24 more months」
版本停用后 指定该版本的请求返回 410 Gone
提前预警 响应头带 Deprecation(停用日期)与 Sunset(彻底移除日期)
关闭后不写版本 默认落到「下一个最旧的受支持版本」,而不是正在关闭的版本

最后一条最阴:

Requests that do not specify an API version default to the next oldest supported version, not the closing down version. If you rely on unversioned requests, you may observe behavioral changes as older versions are removed from support.

翻译过来:你以为「不写版本」是保守,其实是把选择权交给了时间。 每关掉一个旧版本,你的默认行为就往前挪一格,而且没有任何一次部署对应这个变化。

GitHub 还写了两类例外,同样适用于任何供应商的「SLA 承诺」:

  • 安全漏洞、数据暴露、严重可靠性问题,可能不按常规节奏发版,甚至在极少数情况下直接对现有版本做破坏性变更;
  • 使用量极低的服务,可能在标准流程之外被弃用。

对一人公司的含义是:支持窗口是下限,不是保险单。 24 个月不代表第 23 个月动手就安全。

还要提醒一句边界:Stripe 那两页没有写旧 API 版本的停用时限。别把 GitHub 的 24 个月搬到 Stripe 身上——两家披露的东西不一样,能抄的是方法,不是数字。

六、一人公司的六步迁移(编辑判断)

下面这套顺序是编辑建议,不是厂商规定。它的目标不是「升到最新」,而是任何一步都能停下来。

1. 盘点调用点。 把所有会发请求的地方列出来:服务端 SDK、脚本里的 curl、CLI、定时任务、webhook 端点、以及别人帮你写的那部分。漏掉一个 curl,它就会继续用账号默认版本,成为「升级完成」之后唯一还在旧行为上的那个。

2. 把版本钉在代码里。 目标是不再依赖账号默认值。Stripe 的第一步就是这个:

Make sure that you specify the API version that you’re integrating against in your code instead of relying on your account’s default API version.

3. 读 changelog,但只读命中你的那几段。 全量 changelog 对一人公司没有可执行性。按「你用到的端点 + 你读的字段」筛,命中「删除/改名/改类型/新增必填」才需要改代码;命中「新增字段/新增枚举值」只需要确认解析器有兜底。

4. 建影子端点,先不切流量。 新端点按新版本渲染事件,代码先记录不处理。这一步的产出是差异样本——同一批事件在旧版本与新版本下的结构差异,比读十页文档都直观。

5. 切换,并留一个回滚窗口。 按第四节的 400 技巧保留重投能力。窗口多长取决于你的业务周期:如果是订阅扣款,至少要覆盖一个完整的账单周期,否则你会漏掉只在续费时才出现的事件类型。

6. 最后才清旧端点与旧 pin。 顺序反了就没有回滚路。

每一步的停止条件

步骤 看到什么就停
盘点 发现有调用点你无法修改(别人的脚本、第三方集成)
钉版本 强类型 SDK 无法单独指定版本 → 先规划 SDK 主版本升级
读 changelog 变更涉及你无法快速验证的资金流字段
影子端点 签名校验失败、或事件数量与旧端点长期对不上
切换 出现 400 重投堆积、或同事件被处理两次
清理 回滚窗口内出现过一次以上异常

「停下来」不是失败。在没有回归测试的情况下,停下来是唯一的测试。

三种最常见的误读

  1. 「月度版本没有破坏性变更,所以不用管。」 原文说的是「发布流程不包含破坏性变更」,不是「你的集成不会坏」。加法变更照样能打穿没有 default 分支的解析器,而 SDK 主版本停止更新是另一条独立的时间线。
  2. 「升级 SDK 就等于升级 API 版本。」 对 Java、Go、.NET 成立,对 Ruby、Python、PHP、Node 不成立——后者可以在 SDK 之外单独覆盖版本,而且覆盖后响应结构也跟着变。反过来,「只升了 SDK 小版本」也可能悄悄换了请求版本。
  3. 「webhook 会自动重投,所以双端点迁移是无损的。」 重投只在端点返回失败时发生。旧端点一旦被禁用,之前返回 400 的事件不会再补发;双端点并行期间同一事件会到两次,没有幂等就会重复处理。

上线前的八项检查

  • 三个版本都写下来了吗:账号默认、SDK 锁定、事件快照,各自的当前值?
  • 代码里钉住版本了吗:还是仍在吃账号默认值?
  • 解析器能容忍新增字段与新增枚举值吗:有没有 default 兜底?
  • 强类型 SDK 的主版本升级排进计划了吗:它和 API 版本是同一件事?
  • webhook 端点有版本号区分吗:同一 URL 靠什么参数区分新旧?
  • 迁移期幂等键存了吗:同一事件到两次时会怎样?
  • 回滚窗口覆盖一个账单周期了吗:只覆盖一天会漏掉什么事件?
  • 旧端点下线前,确认它已经不再返回 400 了吗:下线即失去重投能力。

来源

本文所有引用均来自下列公开页面,访问日期均为 2026-10-25,发布前逐条重新验证为 HTTP 200:

  1. Versioning – Stripe核查于 2026/10/25:月度版本与大版本的区别、当前版本 2026-09-30.endive、curl 与 webhook 默认使用账号默认版本、Stripe-Version 头、open enum 的安全兜底建议。
  2. Upgrade your integration – Stripe核查于 2026/10/25:升级第一步是「在代码里指定版本」、强类型语言无法单独指定版本、snapshot_api_version 只能创建时设定、双 destination 并行要求幂等、webhook 迁移的五段顺序(建禁用端点 → 忽略新事件回 200 → 切换并让旧端点回 400 → 监控 → 禁用旧端点)、旧端点禁用后不再重投 400 事件。
  3. Set a Stripe API version – Stripe核查于 2026/10/25:各语言 SDK 的版本覆盖方式与生效范围、覆盖后响应对象也按该版本返回、强类型语言不建议覆盖、通过 API 读取历史事件时结构由「事件发生时」的账号默认版本决定。
  4. How SDK versioning works – Stripe核查于 2026/10/25:SDK 语义化版本、新功能只进最新主版本、旧主版本不再更新、语言运行时 EOL 后 1–2 年扩展窗口、不受支持的运行时「might break unexpectedly in any release」且原因可能不进 changelog。
  5. API Versions – GitHub核查于 2026/10/25:X-GitHub-Api-Version 头、缺省回落 2022-11-28、破坏性与加法变更的完整清单、上一版本至少再支持 24 个月、Deprecation 与 Sunset 响应头、410 Gone、未指定版本时默认落到下一个最旧受支持版本、安全与低使用量两类例外。

唯一主行动

把下一步变成可执行动作

对照产品维护与发布回滚工作流