如何为你的客户开通验证码 API 密钥

如果你把 CapSkip 跑给别人用,那么客户付款的那一刻,你就可以直接从计费 webhook 里为他开通验证码 API 密钥。Remote Key Management 提供三个 POST 端点,用来在正在运行的实例上添加、列出和删除密钥,而新密钥在紧接着的下一次识别请求上就已生效。不用重启,没有手工步骤,付款到账和客户开始识别之间不再夹着任何东西。本文讲解订阅时怎么发放密钥、退订时怎么回收、怎么让密钥列表和计费系统保持对账,以及这种部署最容易犯的那个网络错误。
你需要什么
- 一台你自己掌控的 Windows 机器上跑着 CapSkip。这台机器就是你的识别服务,也是唯一需要安装该应用的机器。你的客户什么都不用装。
- 还需要服务器模式,这样客户才真的连得上。本地模式只绑定回环地址,仅服务于本机;服务器模式绑定你的内网或公网 IP,别的机器就能连进来。两种模式都写在 连接设置里,而且在把地址交给任何人之前,最好先准备一个静态公网 IP。
- 在 CapSkip 窗口里操作一次,用来生成管理 token。这是唯一发生在界面里的步骤,而且不会重复第二次。
- 一个能发起这些调用的地方:你的计费 webhook 处理程序、你的后台服务,或者调试阶段的一个终端。
在看代码之前有一点值得明说,因为正是它让这套模式成立。你发出去的这些密钥是你自己铸的。它们不是你买来再转卖的额度,背后也没有任何按次计量。CapSkip 跑在你自己的硬件上,所以一百个客户密钥和一个客户密钥的成本完全一样。
第 1 步:打开远程密钥管理
开关在 Settings 里,位于 API Key Validation 下的 Advanced,再进入 Remote Key Management。打开它,然后点击 Generate 按钮。
token 只显示一次,并且只以加盐哈希的形式保存,所以之后无法再读出来。丢了就重新生成一个,旧的会立刻失效。把它当作吊销按钮用,因为并没有另外一个。请把它放在你的计费系统存放其他机密的地方,而不是写进应用代码里。
这些端点就在已经响应识别请求的那个主机和端口上。如果你的客户访问 8080 端口,管理 API 也在同一个端口。这既是便利,也正是你必须当心的地方,后文有专门一节讲它。
功能关闭时,所有管理路径都返回一个没有响应体的 404。这和访问一个不存在的路径得到的结果完全一样,而且是故意这么设计的:扫描端口的人分不清这是一台关掉了该功能的实例,还是一台根本没有这个功能的实例。
第 2 步:客户订阅时发放密钥
三个端点都是 POST,都接受 JSON 请求体,都需要在 Authorization 头里以 Bearer 凭证的形式带上 token。添加接口需要一个名称。只传名称,密钥值由 CapSkip 生成,这正是这里想要的:客户不该自己挑选凭证。
# No install needed. Name the key after the customer, not after the plan.
curl -X POST http://YOUR_SERVER:8080/admin/keys/add \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" -d '{"name":"cust_10482"}'
# Response carries the value to show the customer once:
# {"errorId":0,"key":{"name":"cust_10482","key":"..."}}名称要取自你自己系统里稳定的东西,比如客户 ID 或订阅 ID。名称是唯一的,而且这条规则在添加时就会强制执行,所以名称成了后续所有操作的可靠抓手。改用套餐名或日期来命名,才是让退订那天变得难受的根源。
这条唯一性规则同时也是你的幂等保障。支付服务商会重发 webhook,而重复到达的订阅事件会返回 409 和 ERROR_KEY_EXISTS,不会悄悄给同一个客户再建一个密钥。在处理程序里把这个 409 当成功处理,重放问题就消失了。
密钥值只在那一次响应里返回一次。把它展示给客户,或者存进你的后台读取的地方,因为事后列出密钥属于管理操作,你不会想为了找回某一个客户的凭证去跑它。
第 3 步:客户退订时回收密钥
删除只接受一个选择条件,要么是密钥值,要么是名称。既然你按客户来命名,那就用名称:
# By name, which is unambiguous because names are unique.
curl -X POST http://YOUR_SERVER:8080/admin/keys/delete \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" -d '{"name":"cust_10482"}'
# By value works too, if that is what your records hold:
# -d '{"key":"the-old-value"}'回收在下一次识别请求时生效,所以调用一返回,访问权限就没了。从那一刻起该客户的调用会失败并返回 ERROR_KEY_DOES_NOT_EXIST,这个信号足够清楚,他们的集成可以把它呈现为订阅到期,而不是服务故障。
什么时候触发回收,要想清楚再定。在退订事件上删除会立刻切断访问,尽管客户通常已经付到了本周期结束。在周期结束事件上删除,才是多数人真正想要的。扣款失败是第三种情况:在回收之前留一小段宽限期,比让密钥在一次重新扣款时突然消失少惹很多工单。
第 4 步:把密钥列表和计费系统对账
列出接口传一个空对象,返回该实例当前接受的每一个密钥。
# The audit: exactly who can solve right now.
curl -X POST http://YOUR_SERVER:8080/admin/keys/list \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" -d '{}'{
"errorId": 0,
"keys": [
{ "name": "cust_10482", "key": "..." },
{ "name": "cust_10515", "key": "..." }
]
}errorId 为 0 表示成功,和识别端点用的是同一套约定。定期跑一次,把名称和你的有效订阅做差集比对。有密钥却没有对应订阅,说明有人还在免费识别;有订阅却没有密钥,说明这个客户的开通 webhook 掉了,而且他多半马上就要来开工单。这两种情况在你去看之前都是无声的,而这个调用是唯一能给出真相的地方。
把它接进订阅 webhook
拼在一起,整个集成就是两个处理函数加一条幂等规则:
# pip install requests
import requests
BASE = "http://YOUR_SERVER:8080"
AUTH = {"Authorization": "Bearer YOUR_TOKEN"}
def admin(path, body):
r = requests.post(f"{BASE}/admin/keys/{path}", json=body,
headers=AUTH, timeout=10)
# 409 on add means the key already exists, which is what a
# retried webhook looks like. Treat it as success, not failure.
if r.status_code == 409:
return None
r.raise_for_status()
return r.json()
def on_subscription_active(customer_id):
created = admin("add", {"name": f"cust_{customer_id}"})
return created["key"]["key"] if created else None
def on_subscription_ended(customer_id):
admin("delete", {"name": f"cust_{customer_id}"})重放的订阅事件返回 None 时要认真处理。它意味着密钥存在,但你再也看不到它的值了,所以如果第一次没存下来,正确的补救是删掉再重建,而不是去列出。第一次收到时就把值存下来,就完全不会遇到这个局面。
不要把管理 API 留在面向客户的端口上
对这种部署来说,这是最关键的一节,因为同主机同端口的便利是双向的。你的客户需要访问识别端点,而管理端点就在同一个端口上,除了 Bearer token 之外没有别的保护,并且没有任何传输层校验,所以在自定义端口上这个 token 是以明文 HTTP 传输的。任何能监听这段流量的人都能读到它,而任何拿到它的人都能给自己开一个密钥。
在实例前面放一个反向代理,把两类访问者分开:
- 只把识别相关的路径发布到公网,由代理终结 HTTPS,并让管理路径下的一切都返回 404。
- 从你自己的后端通过内部通道访问管理端点:如果计费服务就跑在同一台 Windows 机器上,那就走回环地址;否则走内网、VPN 或者一条 SSH 隧道。
经验法则是:管理 token 绝不应离开你的基础设施,它所访问的端口也绝不应是客户能碰到的那个。无论功能开没开,都不要把管理路径暴露到公网上。
还有一条边界值得知道,因为客户面板正是这里最容易想到要做的东西。浏览器调不了这些端点,因为管理接口的响应按设计不带任何 CORS 头。你的面板必须经由你自己的后端转发,而 token 本来也该待在那里。
密钥究竟写到了哪里
密钥存在你原本存密钥的地方。Direct Input(直接输入)和 From File(从文件读取)是两份互相独立的列表,API 读写的是当前选中的那一份。在设置窗口里切换来源,端点看到的内容也会跟着变,所以在你纳闷刚开通的密钥怎么不在列表里之前,先确认自己处在哪种模式。
文件模式有一个行为值得提前知道:第一次写入会把纯文本密钥文件改写成 JSON 格式。内容不会丢,但格式是永久性改变的,所以如果还有别的程序在读这个文件,请先备份。要是压根没有选中文件,写入就无处落地,你会拿到一个 500 和 ERROR_ADMIN_STORE_FAILURE。这里的 500 基本上都是这个原因。
这个功能出现之前建立的列表里,仍然可能存在重名,因为唯一性只在添加时才校验。对这类重名按名称删除会返回 ERROR_KEY_NAME_AMBIGUOUS,并且什么都不会改。改成按值删除,歧义就消失了。
Windows 下的 shell 引号问题,如果你就在这台机器上做测试
CapSkip 是一个 Windows 应用,所以你用来测试的机器往往也是 Windows。命令提示符不把单引号当作引号字符。复制过去的命令会把引号本身当成 JSON 请求体的一部分发出去,解析器拒收,你就在一条看起来完全正确的命令上拿到了 ERROR_ADMIN_BAD_REQUEST。
| Shell | 空响应体 | 带字段时 |
|---|---|---|
| 命令提示符 | -d "{}" | -d "{\"name\":\"prod\"}" |
| PowerShell,使用 curl.exe | -d '{}' | -d '{\"name\":\"prod\"}' |
| Git Bash、macOS、Linux | -d '{}' | -d '{"name":"prod"}' |
在 PowerShell 里要写全名 curl.exe。那里的 curl 是 Invoke-WebRequest 的别名,参数完全不一样,报出来的错和这套 API 毫无关系。
错误码
| 状态码 | 代码 | 原因 |
|---|---|---|
| 404 | 纯文本 | 功能未开启,或者路径不存在。两者被刻意做成无法区分。 |
| 401 | ERROR_ADMIN_UNAUTHORIZED | token 缺失、格式不对,或者不正确。 |
| 405 | ERROR_ADMIN_METHOD_NOT_ALLOWED | 你发的不是 POST。 |
| 400 | ERROR_ADMIN_BAD_REQUEST | 请求体不是合法 JSON,或者添加时没给名称,又或者删除时两个选择条件都给了、都没给。 |
| 409 | ERROR_KEY_EXISTS | 该名称或该值已经在用了。在订阅处理程序里,这通常意味着一次重放的 webhook。 |
| 409 | ERROR_KEY_NAME_AMBIGUOUS | 按名称删除匹配到了多条。请改成按值删除。 |
| 404 | ERROR_KEY_NOT_FOUND | 删除没有匹配到任何内容。通常是一次已经处理过的退订。 |
| 500 | ERROR_ADMIN_STORE_FAILURE | 保存失败。通常是文件模式下没有选中文件。 |
这些错误码和识别用的错误码是并列关系,不是替代关系,完整清单见 CapSkip API 文档.
常见问题
新密钥多久才真正生效?
下一次识别请求就生效。没有任何缓存,也不需要重新加载,所以一个刚在你的结账页拿到密钥的客户,同一分钟内就能用它。反方向的回收同样即时,正因如此,退订处理程序的触发时机是一个策略决定,而不是技术限制。
每多一个客户密钥要额外花钱吗?
不花钱。CapSkip 跑在你自己的硬件上,没有按次配额,所以一个密钥只是给调用方贴的标签,而不是一个计费身份。每个客户建一个,或者想区分他们的不同环境就建好几个,删起来也一样随意。 CAPTCHA 识别 SDK 你给它哪个密钥它就用哪个,所以怎么划分完全由你设计。
我能通过这套 API 给客户计量或限流吗?
这套 API 做不到。它只负责添加、列出和删除密钥,其他任何设置都无法通过它读取或修改。如果你的套餐按用量区分,就在你本来就要放在实例前面的那个反向代理上按客户发来的 API 密钥计数。密钥标识调用方,代理决定这个调用方能做什么。
我把管理 token 弄丢了,现在怎么办?
在同一个设置面板里重新生成一个。旧 token 会立刻失效,所以没有清理步骤,也不存在两个 token 同时有效的窗口期。你的客户不受影响:他们的密钥原封不动,识别照常进行。唯一中断的是你自己的开通流程,直到你把 webhook 处理程序读取的那个机密更新为止。
最短的版本
Remote Key Management 只需开启一次,把 token 和你其他的计费机密放在一起。客户订阅生效时添加一个以客户命名的密钥,重放时把 409 当成功处理,订阅结束时用同一个名称删除。定期列出并与有效订阅做比对,因为那是唯一能给出真实答案的地方。然后在前面放一个代理,让管理路径远离客户能看到的端口。做到这些, 验证码绕过 就变成了你可以用自己的硬件转售的东西,开通速度取决于你的结账流程触发 webhook 有多快。
