换一条链接给终端用户,他在我方的屏上填十五个字段、传证件照片 —— 数据不经过你。
L1 实名:托管屏与资料清单
你要做的只有一步
POST /v1/kyc/sessions
x-on-behalf-of: <external_member_id>
x-idempotency-key: <UUID>
拿回 201:
{
"hosted_url": "https://api.ziseinfotest.com/hosted/kyc/kyc_352ffcf7efe94531b3da9f902f2e4348",
"expires_at": "2026-08-14T15:47:53.000Z"
}
⚠
x-idempotency-key是必填的(所有写端点都是)。漏了直接 400
idempotency_key_required—— 这是你在这条线上会遇到的第一个错误。
⚠
expires_at是 ISO8601 字符串,不是 unix 秒。
把这条链接交给终端用户(App 内嵌 WebView、短信、邮件都行)。剩下的事在我方的页面上发生。
这张票绑三样
| 绑什么 | 少了会怎样 |
|---|---|
| 会员 | 你能拿 A 的链接给 B 建档 |
| 商户 | 一张票被另一个商户拿去消费 |
| 24 小时 + 一次性 | 链接躺在聊天记录里被转发重用 |
⚠ 有效期是 24 小时,不是 5 分钟。它要经你转交给终端用户、
他再去翻证件、拍照片。但别指望它更久 —— 拿到这条 URL 的人
就能以那个会员的名义提交一份实名档案。过期了补发一次即可。
终端用户在那一屏上填什么
十五个文本 / 选择字段(全部必填)
| 字段 | 类型 | 备注 |
|---|---|---|
first_name / last_name | 文本 ≤60 | |
sex | 选择 | male | female |
birth_date | 日期 | 有年龄窗口,见下 |
nationality | 文本 2 位 | 国家码 |
occupation | 文本 ≤60 | |
phone_zone_number | 文本 ≤6 | 区号 |
phone | 电话 ≤20 | |
identity_type | 选择 | id_card | passport | resident_card | drivers_license |
identity_number | 文本 ≤60 | 我方不校验号码格式 —— 各国规则不一,误拦的代价大于放过 |
cardholder_residence | 选择 | chinese-mainland | chinese-mainland-residents-living-overseas | overseas-countries —— 见下面那条警告 |
address_province / address_city | 文本 ≤60 | |
address | 文本 ≤160 | |
street_number | 文本 ≤40 |
邮箱是预填的、不可改 —— 审核结果会发到会员建档时那个邮箱。
⚠ 证件号与邮箱都会跨人查重,撞了是提交期 400
(
kyc_identity_taken/kyc_email_taken),没有人工通道,且你这一侧收不到任何信号。成因与预防见
KYC 概览那一节。
⚠ 年龄窗口默认是 18–65 岁(后台可配)。超出直接拒,
而这个上限不在任何出参里 —— 你没有办法提前判断。
66 岁以上的用户会填完 15 个字段、传完三张证件照才在提交那一刻被挡住。
面向年长用户的产品请在引导文案里先说。
⚠
chinese-mainland目前会被拒(「暂不支持中国大陆居民注册」),手机号那一侧也拒中国大陆号码。这是一个后台开关
(
allow_mainland),不是永久规则 —— 所以选项还在表里。你的用户引导里最好提前说清楚,别让他填完十五个字段才被挡。
三张照片
| 项 | 表单名 | 必填 |
|---|---|---|
| 证件正面 | photo_front | 是 |
| 证件背面 | photo_back | 否 —— 护照只有信息页 |
| 手持证件自拍 | photo_handheld | 是 |
⚠
back在页面上不标必填,但非护照证件实际要求有背面 ——判据在服务端,页面不重复判一遍(那会变成第二个事实源)。
单张 ≤ 8 MB。我方按魔数嗅探真实类型,不信客户端声明的Content-Type。影像直传我方私有存储,只有本人与审核员读得回来。
⚠ 按居住地类型,审核时可能追加要求居留许可文件 ——
那走补件,不在首次表单里。
表单填完之后,数据怎么提交?
不经过你。这一步你什么都不用做。
那张页面是我方渲染的,它的 <form> 指向的就是 hosted_url 本身:
<form method="post"
action="/hosted/kyc/kyc_352ffcf7…"
enctype="multipart/form-data">
终端用户点「提交」→ 浏览器把 15 个文本字段与照片以multipart/form-data 同源 POST 回我方(照片的表单名是photo_front / photo_back / photo_handheld)。
⚠⚠ 这正是托管屏存在的全部理由:证件影像与证件号从头到尾没有经过
你的服务器、你的日志、你的 CDN、你的错误上报。
你手上永远只有一条 URL 和一个后来的审核结论。
所以不要去找「提交资料」的 API —— 没有那个端点,也不该有。
提交之后会发生什么
| 结果 | 页面 | 票据 |
|---|---|---|
| 校验通过 | 「已提交」终止页 | 此时才被消费 |
| 校验失败 | 原样回填文本、显示错误、可以直接重填 | 仍然有效 |
⚠ 校验失败不会烧掉这张票 —— 否则用户填错一个字就得回来找你重开链接。
但照片必须重选:浏览器不允许回填
file控件,页面上那句提示说的就是这件事。
提交成功之后再调 POST /v1/kyc/sessions,会拿到400 state_invalid(这个会员已经有一份在途/已通过的档案了)。
会员的 kyc_level 不会当场变
提交只是把资料交上来,审核是异步的。在结论出来之前GET /v1/members/{id} 的 kyc_level 仍然是 0。别把「提交成功」当成「实名通过」去放行业务 —— 等kyc.result.updated。
你实际能看到什么
⚠⚠ 先说这一条,因为它决定你的状态机怎么写。
GET /v1/kyc 的出参只有四个字段,status 只有三档:
status | 什么时候 |
|---|---|
approved | level ≥ 1 |
pending | 有一份档案在审 |
rejected | 被驳回了,且名下没有在审的档案 |
none | 从没提交过 |
⚠
pending优先于rejected。 驳回之后重新提交的人名下同时有两行,而他此刻的真实状态是「在审」—— 别再催他一次。
⚠ 拿到
rejected就该引导用户重新走一遍:调POST /v1/kyc/sessions换一条新链接(没有「改几个字段再提交」那种能力,见下)。
⚠
fail_reason(驳回原因)开放 API 一律不下发。 事件体的红线是只带 ID 与状态。别去找这个字段,也别在界面上承诺给用户看原因。
我方内部的五档你拿不到
UNREVIEWED / APPROVED / REJECTED / SUPPLEMENT_REQUIRED / SUPPLEMENT_SUBMITTED 是我方内部的流转,写在这里只是为了让你理解事件为什么会来好几次。
⚠ 照它们写
switch (status) { case "APPROVED": … }的话,一个分支都不会命中,且零报错 —— 事件正常到达、签名正确、你回 2xx。
表现是「用户实名通过了,但我们系统里一直是待审」。
事件不是每一次流转都发
kyc.result.updated 只在两种时刻发:
- 审核出结论(通过或驳回)
- 我方发起补件
不发的两处:
| 流转 | 后果 |
|---|---|
| 用户提交完 L1(→ 待审) | 没有「他交了」这条事件 —— 要知道就轮询 GET /v1/kyc 看 pending |
| 用户交完补件(→ 待复审) | 同上,没有事件 |
⚠ 把「收到事件」当成推进自己状态机的唯一触发,这两档会永久卡住,
而你排查时会先怀疑自己的 webhook 端点。
被驳回之后怎么办
没有「回到上一份档案改几个字段」这种能力(那条路要会员自己的登录态,而经 POST /v1/members 建的会员拿的是随机占位密码,登不进我方 App)。
开放 API 上唯一的路是:重新调 POST /v1/kyc/sessions 换一条新链接。
⚠ 新链接打开的是一张空表单 —— 上一次填的内容不会回填。
界面上要提前告诉用户「需要重新填一遍」。