API 对接文档 v1
本平台对外提供统一的 REST API。当前开放:用户端认证(含 Refresh Token 轮换)、应用服务资源读写、完整支付对接(创建订单 → 发起支付 → 回调通知 / 主动查单 → 退款)、营销能力(优惠券领取 / 核销 / 释放、广告位投放)、会员中心与积分(代用户开通 / 撤销会员、调整积分;登录用户可自查自己的会员与积分)、幂等写入与按应用限流。支付渠道由平台集中对接,应用只面对统一的订单与支付模型。
概述
Base URL
http(s)://your-domain/api/v1
统一响应格式
所有接口返回相同结构。HTTP 状态码表达传输层语义,code 表达业务语义(0 为成功)。
{
"code": 0, // 业务码,0 = 成功
"message": "success", // 提示信息
"data": { }, // 业务数据,失败时为 null
"request_id": "3f2a..." // 全链路追踪 ID,同时通过响应头 X-Request-Id 回传
}
分页类接口的 data 为:{"items": [], "total": 100, "page": 1, "page_size": 20}。
分层与身份
平台的设计初衷是一套后端服务所有应用:应用只需有自己的前端,即可直调平台 API。按调用方身份,接口分三层开放:
| 身份 | 凭证 | 可访问范围 |
|---|---|---|
| 公开(匿名) | 仅 X-App-Id 请求头(应用标识,公开信息) |
公开内容型资源的查询:contents / categories / tags / products / plans 的列表与详情 |
| 登录用户 | 用户 Access Token(登录获得) | 公开内容型资源全量可读 + 查看与更新自己的资料(昵称 / 头像 / 性别 / 生日 / 密码)+ 只读自查自己的会员(/memberships/me 等)与积分(/point-accounts/me 等) |
| 应用服务 (进阶,可选) |
API Key(仅存放于应用自己的服务器) | 按 Key 的权限白名单读写资源,支持创建订单、发起支付、接收支付回调、退款,优惠券代领 / 核销 / 释放、广告位投放,以及会员开通 / 撤销、积分调整,适用于有自建后端的应用做服务器间调用 |
快速开始
前端直调(绝大多数应用)
- 在后台「应用中心」找到应用的 app_id,前端请求携带
X-App-Id头即可拉取公开内容; - 需要用户身份时调用
POST /auth/login获取 Token,之后携带 Authorization 头访问用户接口; - 在日志中心按 Request-Id / 应用检索调用记录与安全事件。
# 未登录拉取内容列表
curl https://your-domain/api/v1/contents?page=1 -H "X-App-Id: 1"
服务器间调用(进阶)
- 应用有自建后端时,进入开发者中心 → API Keys 生成 API Key(明文仅展示一次),创建时按需勾选接口权限,并可配置限流配额与 IP 白名单;
- Key 仅保存在服务器环境,携带
Authorization: Bearer {API Key}调用。
认证:应用服务 API Key(进阶,可选)
仅当应用拥有自己的后端服务器(需要服务器间调用、批量任务等)时才需要 API Key。Key 用于向平台证明「请求来自该应用的服务器」,务必只保存在服务器环境,不要下发到前端。
| 要求 | 说明 |
|---|---|
| 认证头 | Authorization: Bearer {API Key} |
| 存储方式 | 平台仅保存 SHA-256 哈希,泄露无法回溯明文;请妥善保管创建时展示的明文 |
| 接口权限 | Key 的 permissions 决定可调用的资源与动作,格式 资源:动作(如 users:write)或 * 全量 |
| IP 白名单 | 配置后仅白名单内 IP(支持 CIDR)可调用;未配置则不限制 |
| X-App-Id(可选) | 一旦携带,必须与 Key 归属应用一致,否则返回 403,用于防止多应用凭据混用 |
| 限流 | 按应用每分钟计数(默认 60 次/分钟,可按 Key 配置),超限返回 429 |
认证:用户端(Access + Refresh Token)
应用内终端用户的登录采用 Access Token + Refresh Token 双凭证。Access Token 有效期 2 小时,Refresh Token 有效期 14 天,两者签发即绑定应用。
POST /auth/login
POST /api/v1/auth/login
Content-Type: application/json
X-App-Id: 1 // 必填(或在 body 传 app_id)
{
"login": "tom", // 用户名 / 手机号 / 邮箱
"password": "secret123",
"device_name": "miniapp", // 可选,设备名
"device_id": "uuid", // 可选
"platform": "wechat" // 可选
}
{
"code": 0,
"data": {
"token": "1|xxx...", // Access Token,2 小时
"expires_at": "2026-09-03T12:00:00.000000Z",
"refresh_token": "(64 位随机串)", // 仅本次响应可见,14 天
"refresh_expires_at": "2026-09-17T10:00:00.000000Z",
"user": { "id": "01J...", "username": "tom", ... }
}
}
POST /auth/refresh(轮换)
Access Token 过期前,用 Refresh Token 换取新的一对凭证。一次一换:旧 Refresh Token 立即失效。
POST /api/v1/auth/refresh
{ "refresh_token": "(上一次返回的 64 位串)" }
GET /auth/me · POST /auth/logout
GET /api/v1/auth/me # Authorization: Bearer {access_token}
POST /api/v1/auth/logout # 撤销当前 Access Token 与其会话
资源接口(读)
三种身份共用同一组资源路径,可访问范围由身份决定(见「分层与身份」):
GET /api/v1/{resource} # 列表(分页)
GET /api/v1/{resource}/{public_id} # 详情(按 ULID 定位)
# 公开身份:X-App-Id 头 + 白名单资源(contents/categories/tags/products/plans)
curl https://your-domain/api/v1/contents?page=1 -H "X-App-Id: 1"
# 登录用户 / 应用服务:Authorization 头
curl https://your-domain/api/v1/contents?page=1 -H "Authorization: Bearer {凭证}"
# 列表查询参数
page=1&page_size=20 # page_size 上限 100
status=normal # 按状态过滤
keyword=tom # users/contents/products 支持关键字
所有资源标识与引用字段均为 ULID public_id(26 位),金额字段以字符串输出,避免精度丢失。users 列表与订单接口不对公开 / 普通用户开放。
订单接口
orders 为应用服务专属资源:匿名与登录用户身份不可访问。支持 API 创建(见「支付对接」)与查询(需 Key 具备 orders:read 权限或 *):
GET /api/v1/orders?status=pending&page=1&page_size=20
GET /api/v1/orders/{public_id}
字段说明
| 字段 | 说明 |
|---|---|
| id | 订单 ULID public_id |
| order_no | 业务订单号(日期 + 应用位 + 随机,应用内唯一) |
| user_id | 下单用户 ULID |
| status | pending / paid / processing / shipped / completed / cancelled / closed / refunding / refunded |
| payment_status | unpaid / paid / refunded |
| shipping_status | unshipped / shipped / received |
| total_amount 等 | total_amount · discount_amount · payable_amount · paid_amount · refund_amount,均为字符串金额;currency 默认 CNY |
| paid_at / created_at | ISO-8601(UTC) |
| buyer_remark / metadata | 买家备注;创建订单时传入的第三方业务数据(如自有单号)原样回传 |
- 列表支持
status筛选与通用分页参数;keyword不适用于 orders。 - 订单商品明细与支付单流水暂不通过 API 开放,可在后台「订单中心」查看完整快照与流水。
写操作
开放资源:users / contents / categories / tags / products。写入的数据自动归属当前应用;category_id 等引用字段仅能指向同应用资源,跨应用引用返回 422。
- 应用服务身份:五类资源全量创建 / 更新 / 删除(软删规则同后台),需对应
{资源}:write权限。 - 登录用户身份:仅可更新自己的资料(PATCH /users/{自己的 public_id}),字段限昵称、头像、性别、生日、密码;username、手机号等关键字段由服务端裁剪,不可通过接口修改。
- 公开身份:不可调用任何写接口。
POST /api/v1/{resource} # 创建
PATCH /api/v1/{resource}/{public_id} # 更新(部分字段)
DELETE /api/v1/{resource}/{public_id} # 删除(users/contents/products 为软删)
字段说明(* 为创建必填)
| 资源 | 字段 |
|---|---|
| users | username(应用内唯一) · nickname · password(6~64 位) · phone(唯一) · email(唯一) · gender(0/1/2) · birthday · avatar · status(normal/disabled)。API 创建的用户 register_source 固定为 api |
| contents | type* · title* · subtitle · summary · body(JSON 正文形态) · cover · category_id · status(draft/published/offline) · sort · publish_at · expire_at · metadata |
| categories | name* · slug* · type · parent_id · icon · sort · status(active/disabled) · metadata |
| tags | name* · slug* · type · sort |
| products | type* · name* · subtitle · description · cover · category_id · status(draft/listed/off_shelf) · sort · price · market_price · cost_price · stock · metadata |
支付对接
平台采用集中收款模式:支付宝(当面付 / 网站支付)、微信(Native / H5)、易支付、余额等渠道由平台统一配置与对接,应用只需面对统一的订单与支付模型。第三方应用(持有 API Key 的服务端)的完整支付流程:
- 创建订单:
POST /orders(权限orders:write),返回订单 public_id; - 发起支付:
POST /orders/{public_id}/pay(权限orders:pay),返回收款凭证(二维码内容 / 跳转链接),前端渲染给用户; - 用户完成支付:渠道回调由平台验签并自动结算,订单推进为
paid; - 同步结果:双通道保障——平台向你订阅的地址投递
order.paidWebhook(见「Webhook 通知」),或轮询GET /orders/{public_id}(建议间隔 ≥ 5 秒,进入终态后停止); - 退款(可选):
POST /orders/{public_id}/refund(权限orders:refund),不传金额默认按可退余额全额退款。
POST /orders(创建订单)
POST /api/v1/orders
Authorization: Bearer {API Key} // 权限 orders:write
X-App-Id: 1
Idempotency-Key: {UUID} // 强烈建议,防止重复下单
{
"user": "01J...", // 必填:下单用户的 public_id(经 POST /users 创建或已有用户)
"subject": "会员月卡", // 必填:订单标题(支付凭证展示),同时写入 metadata.subject
"total_amount": "199.00", // 必填:字符串金额
"discount_amount": "10.00", // 可选:优惠金额,payable = total - discount
"currency": "CNY",
"buyer_remark": "买家备注",
"metadata": { // 可选:第三方业务数据(如自有单号),查询时原样回传
"client_order_no": "EXT-1001"
}
}
返回 data 为完整订单对象(字段见「订单接口」)。同时投递 order.created Webhook。
POST /orders/{public_id}/pay(发起支付)
POST /api/v1/orders/{public_id}/pay
Authorization: Bearer {API Key} // 权限 orders:pay
{ "channel": "alipay", "scene": "f2f" }
channel 取值以支付设置中已启用的渠道为准(alipay / wechat / epay / balance);scene 可选(如 alipay 的 f2f / page / wap,缺省取渠道默认场景)。响应 data:
{
"payment_no": "P2026...", // 平台支付单号
"type": "qrcode", // qrcode = 二维码内容;redirect = 跳转链接;none = 已同步完成(余额)
"qrcode": "https://qr.alipay.com/...", // type=qrcode 时:将该串渲染为二维码展示给用户
"redirect_url": null, // type=redirect 时:引导用户跳转完成支付
"amount": "189.00", "payment_status": "pending", "order_status": "pending"
}
- 一笔订单允许多次支付尝试(换渠道 / 重新出码),平台仅以验签成功的支付单驱动订单状态,历史尝试保留用于对账;
- 渠道未启用 / 场景不支持 / 状态不允许等业务原因返回
5001(HTTP 422),message含具体原因。
POST /orders/{public_id}/refund(退款)
POST /api/v1/orders/{public_id}/refund
Authorization: Bearer {API Key} // 权限 orders:refund
{ "amount": "50.00", "reason": "部分退款" } // 不传 amount = 按可退余额全额
同步返回退款结果:status=success 表示资金已原路退回(含 transaction_id);渠道暂不支持在线退款时返回 422 并说明原因,可在后台人工处理。退款成功自动累加订单 refund_amount,并投递 order.refunded Webhook。
payment_status=paid(服务端数据)触发,不要信任任何来自前端或第三方的「支付成功」通知;渠道错误(5001)与回调验签失败(5002)由平台记录流水并处理,重试由支付网关按协议自动执行。
优惠券
营销接口为应用服务专属(匿名与登录用户身份不可访问),数据按应用隔离:只能查询与操作本应用创建的券模板、券实例与广告。模型分两层——券模板(coupon,运营在后台「营销中心」配置)与券实例(coupon-redemption,用户领取后生成的券码)。典型流程:
- 查券:
GET /coupons?claimable=1拉取可领取的券模板,在前端展示券列表; - 领取:
POST /coupons/{public_id}/claim(权限coupons:write)代用户领取,生成券实例; - 核销:
POST /coupon-redemptions/{public_id}/redeem(权限coupons:write),传入订单金额,平台校验门槛并计算优惠金额;可传入订单 public_id 完成绑定; - 释放(可选):订单取消时调用
POST /coupon-redemptions/{public_id}/release,券回退为未使用并回退券模板的核销计数; - 查询:
GET /coupon-redemptions按 user / coupon / status 查询券实例。
GET /coupons(券模板列表)
GET /api/v1/coupons?claimable=1&page=1&page_size=20
Authorization: Bearer {API Key} // 权限 coupons:read
# 查询参数
claimable=1 # 仅返回可领取的券(启用中 + 未过发放截止 + 有余量)
status=active # 按状态过滤(active / disabled)
type=reduction # 按类型过滤(reduction 满减 / discount 折扣)
keyword=新人 # 按名称或编码模糊搜索
GET /api/v1/coupons/{public_id} # 券模板详情
POST /coupons/{public_id}/claim(代用户领取)
POST /api/v1/coupons/{public_id}/claim
Authorization: Bearer {API Key} // 权限 coupons:write
Idempotency-Key: {UUID}
{ "user": "01J..." } // 必填:领取用户的 public_id
返回 201 与券实例对象。服务端按启用状态、发放余量、每人限领数依次校验,不满足时返回 422 与对应业务码(6001 / 6002 / 6003)。券实例领取后进入有效期(valid_type=days 时从领取时刻起算)。
GET /coupon-redemptions(券实例列表)
GET /api/v1/coupon-redemptions?user=01J...&status=unused
Authorization: Bearer {API Key} // 权限 coupons:read
# 查询参数
user=01J... # 按用户 public_id 过滤
coupon=01J... # 按券模板 public_id 过滤
status=unused # unused / used / expired / voided
POST /coupon-redemptions/{public_id}/redeem(核销)
POST /api/v1/coupon-redemptions/{public_id}/redeem
Authorization: Bearer {API Key} // 权限 coupons:write
Idempotency-Key: {UUID}
{
"amount": "189.00", // 必填:订单金额(字符串)
"order": "01J..." // 可选:绑定订单 public_id(须属于持券用户)
}
响应 data:
{
"discount_amount": "10", // 本次优惠金额(字符串)
"redemption": { ... }, // 核销后的券实例(status=used)
"coupon": { ... } // 券模板(used_count 已累加)
}
- 优惠计算:满减券取面额(不超过订单金额);折扣券按
订单金额 × 折扣率 ÷ 10计算(8.5 表示 85 折),不超过max_discount封顶; - 仅
unused状态的券可核销(6004);订单金额未达min_spend门槛返回 6005; - 传入
order后券与订单绑定,订单取消时可用release回退; - 核销前平台自动将已过失效时间的券置为
expired(过期券不占限领名额,可重新领取)。
POST /coupon-redemptions/{public_id}/release(释放)
POST /api/v1/coupon-redemptions/{public_id}/release
Authorization: Bearer {API Key} // 权限 coupons:write
Idempotency-Key: {UUID}
{ "order": "01J..." } // 必填:核销时绑定的订单 public_id
仅限已使用且绑定到该订单的券:回退为 unused、券模板 used_count 减一。用于订单取消 / 退款关闭后的券回退,其他情形返回 422(6004)。
字段说明
| 券模板字段 | 说明 |
|---|---|
| id / name / code | 券模板 ULID、名称、唯一编码 |
| type | reduction 满减券(amount 为面额)/ discount 折扣券(discount 为折扣率,8.5 = 85 折,max_discount 为单笔封顶) |
| min_spend | 使用门槛(字符串金额,0 = 无门槛) |
| total_quantity / issued_count / used_count / remaining | 发放总量(0 = 不限量)、已发放、已核销、剩余可发放(不限量时为 null) |
| per_user_limit | 每人限领数(仅统计 unused / used 的有效券) |
| valid_type | fixed 固定时间段(valid_start_at ~ valid_end_at)/ days 领取后 N 天内有效(valid_days) |
| status / description | active 启用 / disabled 停用;券描述 |
| 券实例字段 | 说明 |
|---|---|
| id / code | 券实例 ULID、唯一券码(CR- 前缀) |
| status | unused 未使用 / used 已使用 / expired 已过期 / voided 已作废 |
| source | admin 后台发放 / system API 领取 / code 兑换码 |
| coupon_id / coupon_name | 所属券模板 ULID 与名称 |
| user_id / order_id | 持券用户 ULID;核销绑定的订单 ULID(未绑定为 null) |
| used_at / expired_at | 核销时刻、失效时刻(ISO-8601 UTC) |
discount_amount 由平台按券规则计算(含封顶与「不超过订单金额」约束),应用应直接使用该值计算应付金额,不要在前端复算。领取 / 核销 / 释放均建议携带 Idempotency-Key 防止重复操作。
广告
应用服务身份拉取本应用配置的广告投放(后台「营销中心 → 广告管理」维护)。仅返回投放中且在投放期内(start_at / end_at 任一为空表示不限)的广告,按 sort 升序排列:
GET /api/v1/ads?position=banner&page=1&page_size=50
Authorization: Bearer {API Key} // 权限 ads:read
# 查询参数
position=banner # 广告位:banner 首页轮播 / popup 弹窗 / splash 启动图 / feed 信息流
type=image # 素材类型:image 图片 / text 文字 / code 自定义代码
曝光与点击上报
POST /api/v1/ads/{public_id}/impression # 广告被展示时上报(权限 ads:write)
POST /api/v1/ads/{public_id}/click # 用户点击广告时上报(权限 ads:write)
仅投放中的广告可上报(已下线 / 过期返回 404),计数原子累加,返回当前累计值:
{ "id": "01J...", "impression_count": 128, "click_count": 9 }
字段说明
| 字段 | 说明 |
|---|---|
| id / title / position | 广告 ULID、标题、广告位(banner / popup / splash / feed) |
| type | image 图片 / text 文字 / code 自定义代码 |
| 素材字段 | 按 type 取对应字段:image_url(图片地址,配合 link_url 跳转)、text_content(文字内容)、code_content(自定义 HTML/代码,前端按白名单规则渲染) |
| sort / start_at / end_at | 排序值(升序)、投放起止时间(ISO-8601 UTC,null 表示不限) |
| status | online 投放中 / offline 已下线(接口仅返回 online) |
| impression_count / click_count | 累计曝光 / 点击数(由上报接口累加) |
code_content 为运营在后台录入的自定义代码,渲染于应用内时请使用 WebView 沙箱或白名单标签过滤,避免直接注入页面 DOM。
会员中心
会员能力分两层——会员计划(plan,运营在后台「会员中心」配置,已通过「资源接口」的 plans 公开开放)与用户会员关系(membership,用户开通后的状态)。应用服务身份可查询本应用的会员关系、变动记录与用户权益,并代用户开通 / 续费 / 撤销;登录用户可只读自查(/me 端点,仅需用户 Access Token)。数据按应用隔离。
GET /memberships(会员关系列表)
GET /api/v1/memberships?user={用户 ULID}&status=active
Authorization: Bearer {API Key} // 权限 memberships:read
# 查询参数
user={ULID} # 按用户筛选
plan={ULID} # 按计划筛选
status= # active 生效中 / expired 已过期 / cancelled 已取消
登录用户自查:GET /memberships/me(返回自己的会员,含生效中与历史)。
POST /memberships/grant(代用户开通 / 续费)
POST /api/v1/memberships/grant
Authorization: Bearer {API Key} // 权限 memberships:write
Idempotency-Key: {UUID}
{
"user": "01J...", // 必填:用户 ULID
"plan": "01J...", // 必填:会员计划 ULID
"source": "order", // 可选:order 订单 / purchase 直接购买 / admin 管理员 / gift 赠送(默认 order)
"order": "01J...", // 可选:关联订单 ULID
"remark": "年卡活动发放" // 可选:备注
}
同一用户同一计划已生效的会员自动顺延(从当前到期时间续加天数,已过期则从现在起算),并按计划配置同步发放权益;计划已停用返回 422(7002)。成功返回 201 与完整会员对象。撤销:POST /memberships/{public_id}/revoke(权限 memberships:write),终止会员并同步撤销其发放的权益;状态非 active 返回 422(7003)。
GET /membership-records(变动记录)· GET /user-entitlements(用户权益)
均为分页接口。变动记录支持 user / plan / action(purchase 购买 / renew 续费 / gift 赠送 / expire 过期 / cancel 取消)筛选;用户权益支持 user / status(active 生效中 / expired 已过期 / revoked 已撤销)筛选。登录用户自查对应 GET /membership-records/me、GET /user-entitlements/me。
字段说明
| 会员关系字段 | 说明 |
|---|---|
| id / status | 会员关系 ULID、状态(active / expired / cancelled) |
| user_id / plan_id / plan_name | 用户 ULID、计划 ULID 与名称 |
| source / order_id | 开通来源(order / purchase / admin / gift)、关联订单 ULID |
| started_at / expired_at | 生效、到期时间(ISO-8601 UTC,null 表示永久) |
| 变动记录字段 | action 动作、days 本次天数、remark 备注、created_at 时刻 |
| 用户权益字段 | code / name 权益编码与名称、value 权益值(配额型为额度)、source_type 来源、起止时间与状态 |
积分
积分账户按用户建立,余额一律由流水驱动(平台铁律,不存在直接改余额的通道)。应用服务身份可查询本应用用户的积分账户 / 流水 / 规则,并代用户调整;登录用户可只读自查。每次调整在事务中写入流水并同步余额,余额不足时整体回滚(422 / 7001)。数据按应用隔离。
GET /point-accounts(账户)· GET /point-transactions(流水)· GET /point-rules(规则)
GET /api/v1/point-accounts?user={用户 ULID}
Authorization: Bearer {API Key} // 权限 points:read
# point-transactions 查询参数
user={ULID} # 按用户筛选
type= # earn 获得 / spend 消费 / expire 过期 / adjust 调整
# point-rules 查询参数:status=active 仅返回启用规则
登录用户自查:GET /point-accounts/me(从未获得过积分时返回零账户)、GET /point-transactions/me。
POST /points/adjust(代用户调整)
POST /api/v1/points/adjust
Authorization: Bearer {API Key} // 权限 points:write
Idempotency-Key: {UUID}
{
"user": "01J...", // 必填:用户 ULID
"amount": 100, // 必填:带符号整数(正数增加 / 负数扣减,0 无效,|amount| ≤ 1000000)
"type": "adjust", // 可选:earn / spend / adjust / expire(默认 adjust,语义标注,方向以符号为准)
"remark": "签到奖励补发" // 可选:备注
}
// 201 响应:返回新余额与本次流水
{ "balance": 320, "transaction": { "id": "01J...", "type": "adjust", "amount": 100,
"balance_before": 220, "balance_after": 320, ... } }
扣减超过余额时整体失败(422 / 7001),不产生部分变动。账户与流水字段:
| 账户字段 | 说明 |
|---|---|
| user_id | 用户 ULID |
| balance / frozen | 当前余额 / 冻结积分 |
| total_earned / total_spent | 累计获得 / 累计消费 |
| 流水字段 | type 类型、amount 变动额(正收益负支出)、balance_before / balance_after 变动前后余额、source_type / remark 来源与备注、created_at 时刻 |
Webhook 通知
进入开发者中心 → Webhooks 订阅事件并填写接收地址,事件发生时平台 POST JSON 到该地址。可订阅事件:
| 事件 | 触发时机 | data 主要字段 |
|---|---|---|
| user.created / user.updated | 通过 API 创建 / 更新用户 | user_id · username · nickname · status |
| order.created | API 创建订单 | order_id · order_no · subject · payable_amount |
| payment.success | 渠道回调结算 / 查单补偿结算 | payment_no · order_id · provider · amount · transaction_id |
| order.paid | 订单推进为已支付 | order_id · order_no · payable_amount · paid_amount · paid_at |
| order.completed | 订单流转为已完成 | order_id · order_no · completed_at |
| order.refunded | 退款成功 | refund_no · order_id · amount · transaction_id · completed_at |
| membership.created | 用户开通会员(续费不通知) | user_id · plan_id · plan_name · expired_at |
报文与验签
POST {订阅的接收地址}
Content-Type: application/json
X-Event: order.paid
X-Event-Id: 01M... // 事件唯一 ID(用于幂等去重)
X-Signature: 9f8a... // HMAC-SHA256(原始请求体, 订阅密钥)
{ "event": "order.paid", "event_id": "01M...", "occurred_at": "...", "data": { ... } }
// PHP 验签示例
$expected = hash_hmac('sha256', file_get_contents('php://input'), $secret);
if (! hash_equals($expected, $_SERVER['HTTP_X_SIGNATURE'] ?? '')) {
http_response_code(400); exit; // 验签失败直接拒绝
}
- 订阅密钥在创建订阅时仅展示一次;收到 2xx 即视为投递成功,请先落库再做业务处理;
- 以
event_id幂等去重;投递失败按 2 / 4 / 8 / 16 / 32 分钟指数退避重试,最多 5 次,可在「开发者中心 → 投递记录」查看; - Webhook 可能因网络问题延迟或重复,订单最终状态以查询接口为准,回调与轮询互为补充。
幂等
对 POST 创建类请求携带 Idempotency-Key 请求头(应用内唯一,建议 UUID),可安全应对重复点击与网络重试:
- 首次请求正常执行,响应被缓存 24 小时;
- 相同 Key 重放直接返回首次响应(不产生新数据);
- 相同 Key 并发请求返回
1006 幂等冲突(HTTP 409); - 服务端 5xx 失败不缓存,可使用相同 Key 重试。
限流
Redis 固定窗口计数,按调用方身份选择维度,每次响应携带:
X-RateLimit-Limit: 60 # 配额
X-RateLimit-Remaining: 42 # 剩余
- 应用服务:按 API Key 计数,配额取 Key 的 rate_limit 配置(默认 60 次/分钟,0 = 不限);
- 登录用户:按用户 120 次/分钟;
- 公开匿名:按 应用 + IP 30 次/分钟(防刷从严)。
超限返回 HTTP 429(code=1005)与 Retry-After 头,同时写入安全日志(category=rate_limit)。用户登录 / 刷新接口另有独立的基础限流保护。
资源标识(ULID public_id)
内部主键为自增数字,对外一律使用 26 位 ULID(Crockford Base32,时间有序、全局唯一)。API 请求路径、响应字段、资源间引用均为 public_id,连续数字 ID 不对外暴露,防止资源枚举。
错误码
| code | 说明 | HTTP |
|---|---|---|
| 0 | 成功 | 200 / 201 |
| 1001 | 参数错误(message 含具体原因) | 422 |
| 1002 | 未认证(Key / Token 缺失或无效) | 401 |
| 1003 | 无权限(permissions 不足 / IP 不在白名单 / X-App-Id 不一致) | 403 |
| 1004 | 资源不存在 | 404 |
| 1005 | 请求过于频繁(限流) | 429 |
| 1006 | 幂等冲突(相同 Key 处理中) | 409 |
| 1999 | 系统错误 | 500 |
| 2001 | 账号或密码错误 | 401 |
| 2002 | Token 已过期 | 401 |
| 2003 | 账号已禁用 | 403 |
| 4001 / 4002 | 库存不足 / 订单状态不允许该操作 | 422 |
| 5001 / 5002 | 支付渠道错误 / 回调验签失败 | 422 / 400 |
| 6001 | 优惠券不可用(已停用 / 已过发放截止) | 422 |
| 6002 | 优惠券已发完 | 422 |
| 6003 | 已达每人限领上限 | 422 |
| 6004 | 券不可核销 / 不可释放(状态或订单绑定不匹配) | 422 |
| 6005 | 未达到使用门槛 | 422 |
| 7001 | 积分余额不足 | 422 |
| 7002 / 7003 | 会员计划不可用 / 会员状态不允许该操作 | 422 |
安全规范
- 所有请求必须经 HTTPS 传输;凭据不得出现在 URL 或日志明文中。
- 按应用分配最小权限的 Key:只读业务不发放 write 权限;调用方网络固定时务必配置 IP 白名单。
- 写操作建议一律携带 Idempotency-Key;客户端需实现 Refresh Token 轮换与 401 重新登录逻辑。
- 调用日志(含脱敏请求体)保留于平台,供安全审计与用量分析。