2026 年做 AI 服务的人都清楚一件事:API 变现的最后一米是支付。传统路径要过商户资质、类目审核、跨境结汇三道门,对个人开发者和小团队极不友好。x402 协议提供了另一种可能——HTTP 402 状态码复活了,Agent 之间用稳定币微支付,收款方只需要一个钱包地址,没有类目审核这回事。这篇文章记录我们把三条业务腿(提示词查询、视频解析、语料检索)按 x402 协议上线的全过程,包括 Express 路由、支付中间件、 facilitator 验收的四个实测坑,以及 Bazaar 目录竞品情报的副产品。所有金额以 $0.01/次为例,均为真实定价。

zxcyw-x402封面

一、为什么是 x402

先看一个现实约束:某类业务(比如媒体解析工具)在传统支付服务商(MoR)的 AUP 里属于明文禁售类目——借已过关的其他业务店铺收款,一旦被查出,连累的是整个商户账户的冻结资金。这类”技术方案都通了、就是收不了钱”的死局,靠法币通道无解。

x402 的形态完全不同:

  • 客户端(人或 Agent)请求付费资源 → 服务端返回 HTTP 402 + 付费要求头(价格、收款地址、网络、过期时间);
  • 客户端用钱包对该要求签名付款(Base 链 USDC 之类)→ 原样重试请求,附带 X-PAYMENT 头;
  • 服务端把付款凭证交给 facilitator 结算上链 → 确认入账 → 返回 200 和资源内容。

收款方全程只暴露一个钱包地址。没有 KYC 类目、没有店铺审核、没有结算周期——链上收款天然免类目审核,这正是治 AUP 死穴的那条腿。风险形态也变了:从”账号被关”变成”私钥安全”,大额前人工核对地址/网络/金额即可。

二、服务端最小实现:三条设计规则

Express 实现(零重依赖,一个 .mjs 文件跑起服务),一周内三条腿复用同一份骨架,只改后端反代地址。踩出来的规则:

规则 1:路由参数用 [param] 不是 :param

x402 的路径匹配器(用于按 URL pattern 挂价格)用 Express v5 风格方括号:

// 正确:/x402/parse/[id]
// 错误:/x402/parse/:id —— 永不匹配,且**不报任何错**

第二条是最阴险的:价格挂不上,所有请求都 404 或裸 402 死循环,没有任何日志提示。上线前逐条自测 catalog 返回的 pattern 与真实路由是否一致。

规则 2:paymentMiddleware 必须在业务路由之前

app.use(x402().paymentMiddleware);   // ← 先挂
app.get('/x402/parse/:id', handler); // ← 后挂业务

顺序反了,付费路由等于没保护。

规则 3:长 hex 别走 shell 环境变量

收款地址这种 42 位 hex,通过 export PAYTO=0x... 传参时被 shell/剪贴板链路混入 U+2026(省略号字符)截断污染——服务启动不报错,付出去的款进了黑洞。改成落文件读:

const payTo = fs.readFileSync(SECRET_DIR + '/.payto', 'utf8').trim();

凭据、地址、密钥,一律文件进、命令行不进(和上一篇 Matomo token 的教训同源)。

zxcyw-x402-threelegs

三、facilitator 验收:四个实测坑

服务端写完,用自测页做端到端付款验证。四个坑挨个踩:

坑 1:自测页报价禁止内嵌静态值

第一版自测页把 $0.01 写死在 HTML 里。后来把某端点调价到 $0.05,自测页仍按 $0.01 生成支付要求——facilitator 拒付,用户白签一次钱包,还以为是协议坏了。

正解:自测页打开时实时 fetch 生产端点的 402 响应头,从 X-PAYMENT-REQUIRED 里解码真实报价再生成付款请求。报价的单一事实来源只能是服务端。

坑 2:报价头里的 resource.url 是路由模板

402 头里 resource.url 字段是 /parse/:id 这种模板字面量,不是具体请求 URL。自测页把它原样塞进回放,paywall 校验 URL 不匹配直接拒。要从当前实际请求 URL 重建 resource 字段。

坑 3:自付测试里”余额没变”≠”没付款”

用自己钱包付给自己钱包(from == to)做链路验证,签名、广播、确认全过了,钱包 UI 显示余额纹丝不动——差点误判成付款失败重签一次。

x402 结算的本质是 EIP-3009 transferWithAuthorization,转账永远发生,只是 from==to 时净变化为零。判成交的唯一铁证是 Transfer 事件 + 服务端 200:

// eth_getLogs,topic0 = USDC Transfer 事件哈希
// ddf252ad1be2c89b69c2b068fc378daa952ba7f637c03875984d7c0dc6f785e0

对 payTo 地址查 Transfer 日志,from/to 双 pad 到同一地址、value 对得上,就是成交。余额、钱包 UI 都不算数。

坑 4:Bazaar 目录收录是小时级批处理

首笔成交后立刻去 x402 Bazaar(端点目录)搜自己的服务——0 命中。链上 tx 已确认(status=0x1),目录里就是找不到。查了 3000 条全量目录后接受现实:目录收录按小时级批处理,不是即时的。别把”目录没收录”当成”服务没上线”来报警。

四、部署形态:三条腿共用一套模式

腿 服务 端口 入口
提示词查询 x402-prompt 9100 nginx location ^~ /x402/ 反代
视频解析 x402-video 9101 独立 vhost 片段插入
语料检索 x402-corpus 9102 面板 extension 目录 conf

统一要点:

  • systemd Restart=always + MemoryMax 限额,node 服务本体保持无状态;
  • nginx 反代处加 Cache-Control: no-store——付费头绝不能被边缘缓存,否则 A 用户的付款要求头被 B 用户拿去付一个过期报价;
  • 限速放在上游业务端(按访客 IP 分桶,XFF 透传真实 IP),x402 层只管收钱;
  • 每条腿配一个”成交增量哨兵”:状态文件记下已报事件数,只在链上出现新 Transfer 时播报(老订单绝不重复报),把收款信号接到值班通道。

zxcyw-x402-loop

五、副产品:把竞品目录全量拉下来看

Bazaar 是公开目录。一次性脚本拉全量(实测 19,080 个端点),做了一张竞品分布表,得到三条可直接抄的打法:

  1. 端点数即货架:头部供给方不是单价高,而是把同一个能力拆成几十个粒度不同的端点——买”一次查询”的人远比买”订阅”的人多;
  2. Use-when 文案:好端点的描述全部是”当你需要 X 时用我”的意图句式,而不是功能罗列——Agent 是按意图选服务的,不是按介绍页;
  3. 价格带:微服务集中在 $0.001–$0.10,$0.01 是心理甜点位。

新腿定价直接参考这个分布定在 $0.01/次——先跑起来,让真实成交说话。

六、给 Agent 开发者的一句话

x402 目前最大的限制不是技术,是流量来源还主要是其他 Agent。对你的服务做 API 化改造(可寻址的资源、清晰的 catalog、按次计价的粒度),无论来的是人还是爬虫还是 Agent,路都提前修好了。收款这件事,第一次跑通 $0.01 的意义不在钱,在于证明整个闭环——报价、签名、结算、供货——在没有支付服务商参与的情况下也能自己转起来。

检查项 上线前必测
catalog 返回的 pattern 与实际路由逐条一致 防 [param] 陷阱
自测页报价实时来自生产 402 头 防静态值过期
成交判定 = Transfer 事件 + 服务端 200 防 from==to 误判
反代 no-store 防付款头被缓存
Bazaar 收录按小时等 防误报警

zxcyw-x402-event