Skip to content
全部文章

围绕异步验证结果设计您的产品

查尔斯·阿奇邦

, 联合创始人

将提交视为状态机的开始,而非请求的结束。显示一个基于验证 ID 的待处理状态,通过 Webhook 更新该状态,并允许状态多次更改。

标题“面向异步验证的设计”,旁边配以连接步骤路径的插图,背景为深靛蓝色渐变。

要点

  • onSubmit 表示验证已创建,而非验证已通过。
  • 根据状态采取行动,并将 checkStatus 存储在旁边;他们完全可以意见不一致。
  • 验证状态在结算后可能会发生变化,因此要取最新变化,而不是第一个事件。
  • 重新提交会保留相同的验证 ID;更新记录而不是创建新记录。

从提交到最终结果出来之前,您的应用应该处于待处理状态,该状态基于验证 ID,由后端事件驱动,并且状态可能会多次更改。 SDK 呼叫onSubmit它只会告诉你验证已创建,但不会告诉你验证是否通过。

这听起来显而易见,但当验证过程被搁置一天,需要重新上传自拍照片,再次提交后才获得批准时,情况就变得复杂了。基于单一“成功或失败”回调机制的产品根本无处容纳这些细节。而基于小型状态机的产品则无需特殊处理即可应对这种情况。

为什么提交后结果无法到达?

身份验证所需时间长短不一,原因有很多。需要阅读文件、查询政府记录、进行人脸比对。之后,决策图表 (在新标签页中打开)可能停留在监控名单界面,企业可能需要等待董事进行身份验证,团队成员也可能需要查看。有些步骤只需几秒钟,而有些则需要几天时间。

所以 Toden 验证创建完成后立即返回,状态为:processing并通过Webhook (在新标签页中打开)或状态轮询 (在新标签页中打开)告知后端系统最终结果。产品设计的关键问题在于,在这段时间里应该显示什么,以及应该允许哪些操作。

两个领域,两个问题

每次提交的验证都包含两个状态字段,您需要同时提交这两个字段。

status这就是结果。这是你采取行动的领域,它在整个生命周期中使用了十个值:not_started,in_progress,processing,in_review,awaiting_resubmission,approved,declined,abandoned,expired和error。

checkStatus这是自动检查发现的情况:pending,verified,failed,not_found或者error决策规则和审核人员从不更改。

只要决定事项并非通过核查得出,他们就会产生分歧。核实工作可以进行。approved和checkStatus: "failed"因为审核员接受了一张在光线不足的情况下得分略低于及格线的自拍照。两张照片都存起来。总有一天,有人会问为什么那位顾客会被放行。checkStatus和reasonCode答案就是这些。

状态决定按固定顺序进行:首先是审核员的决定,然后是工作流程的决定,最后是仅由检查决定的结果。

将状态映射到用户看到的内容

大多数产品只需要少量面向用户的状态。以下是基于文档中已记录的值的映射,作为起点:

status

您的应用显示的内容

您的应用允许什么

processing

我们正在核实您的信息。

浏览,而非受限操作

in_review

“我们正在审核您的信息;这可能需要更长时间。”

与处理相同

awaiting_resubmission

“我们需要您重新执行一个步骤”,并附上链接。

仅重新提交

approved

已验证

门控动作

declined

下一步,措辞来自reasonCode

请重试或联系客服

error

“我们这边出了点问题,请重试。”

重试

abandoned,expired

“重新开始”

新的链接或新的尝试

同样的规则也适用于代码,只需一个函数即可实现所有屏幕共享:

type UiState = "checking" | "action_needed" | "verified" | "not_verified" | "start_again";

function uiStateFor(status: string): UiState {
  switch (status) {
    case "approved": return "verified";
    case "declined": return "not_verified";
    case "awaiting_resubmission": return "action_needed";
    case "abandoned":
    case "expired":
    case "error": return "start_again";
    default: return "checking"; // processing, in_review and anything unrecognised
  }
}

默认分支是故意设置的,它涵盖了not_started,in_progress,processing和in_review如果某个你无法识别的值到达该位置,“仍在检查”比直接跳过更安全。

措辞上的下降

在failed或者not_found结果,reason这是为申请人写的一句话,其中没有任何分数或门槛,您可以直接展示它。reasonCode是用于分支的稳定代币:document_expired可能会触发“上传当前文档”selfie_mismatch提示在光线更好的地方重新自拍。error结果reason例如,如果邮件是关于您的信用余额,那么请向对方显示一条通用消息,并将原因转交给您的团队。

区分“几秒钟”和“几天”

processing涵盖正在读取的文档和监视列表屏幕上保留的决定。状态端点的waitingOn字段说明:null检查仍在进行时,"screening","key_people"对于一家等待董事和所有者的企业而言,或者"address_presence"对于已启用的、持续数天的地址检查,请使用此功能来设定预期(例如“通常会在一分钟内完成”或“我们会通过电子邮件通知您”),并将不熟悉的值视为尚未了解的等待时间。

应该相信哪个信号,以及何时相信。

你收到了三个信号,但它们的权威性并不相同。

  1. 客户端 信号。SDK 的onSubmit或者托管页面的submitted此事件会在申请人完成流程时触发。请使用此事件立即将用户跳转到待处理页面。请勿使用此事件授予任何权限。

  2. 向后端发送 Webhook。这些是记录。根据这些记录更新数据库,并将更改推送至用户屏幕。

  3. 状态端点。 GET /api/kyc/status/:verificationId返回当前状态并附带一个可发布的密钥,不包含任何个人数据、分数或结果数据。例如,当应用程序在后端推送任何内容之前重新打开时,可以使用此方法填补数据空白。

第一个 webhook 不一定是最后一个

如果你的工作流程包含决策规则,verification.completed在检查完成后、工作流程做出决定之前发送。status仅反映检查结果。工作流程的最终结论将在之后得出。verification.status_updated和source: "workflow"这就是需要采取行动的状态。之后,审核人员可能会做出决定或更改之前的决定,每次更改都会作为另一个状态出现。verification.status_updated。

邮件会独立重试,并且可能乱序送达,所以请保持联系。changedAt只保留你持有的找零,忽略任何旧的找零。如果没有决策规则,检查结果即为决策,状态即为……verification.completed除非审稿人之后修改,否则此结论有效。

重新提交会保留相同的 ID

当审核员发送验证结果后,申请人只需重新执行要求的步骤,重新提交即可完成相同的验证。您将收到另一个结果事件,其中包含相同的信息。verificationId,attempt数了一遍,然后checkStatus返回pending在检查再次运行期间,更新您持有的记录。如果为每个事件都创建一个新行,则会导致实际上只有一个客户却有两个客户。

如果非要投票的话,那就投票吧。

Webhook 应该用于生产环境。轮询则用于应对突发情况:例如移动屏幕重新打开,或者暂时无法接收 Webhook 的环境。如果您使用轮询:

  • 逐渐后退,从几秒钟开始,逐渐拉长距离,直到状态稳定后停止。

  • 请记住文档中规定的默认限制,即每个客户端每 15 分钟最多只能发出 100 个请求,并阅读以下内容:RateLimit-Remaining和RateLimit-Reset与其猜测,不如使用标题。

  • 尽量减少投票点的数量。如果同时打开十几个屏幕,每个屏幕都进行相同的验证,那么最终得到一个答案所需的流量就会是原来的十二倍。

途中会进行重试

提交端也存在同样的问题。移动网络可能会丢弃创建请求的响应,导致应用无法判断验证是否存在。发送一个稳定的数据。metadata.requestId对每个逻辑验证进行验证,并在任何重试中重复使用。 API 返回现有验证信息,而不是创建第二个验证信息,并且绝不会收取两次费用。

一次巡视

假设有一个储蓄应用程序,其工作流程会对每个已验证用户进行筛选。申请人于上午 9:00 通过手机提交申请。应用程序收到申请。onSubmit并显示“我们正在核实您的信息”。几秒钟后,后端收到verification.completed和checkStatus: "verified"。 它是status阅读approved因为该事件仅反映检查结果,所以后端尚未授予任何权限:工作流仍在屏幕上等待,状态轮询显示processing和waitingOn: "screening"屏幕返回一个可能的匹配项,工作流程将其路由到审核环节,verification.status_updated到达时in_review应用程序更改了提示信息,称审核可能需要更长时间。14:30,合规官清除了误报并批准了申请;第二个verification.status_updated到达时approved后端支持存款功能并发送推送通知。

该序列中的任何内容都不需要特殊处理。它只有一条记录,以一个 ID 为键,并由每个事件依次更新。

待处理状态的核对清单

  1. 创建以以下内容为键的本地记录verificationId那一刻onSubmit火灾。

  2. 只能通过后端 Webhook 进行更新。

  3. 采取行动status; 店铺checkStatus,reason和reasonCode旁边。

  4. 保持最新changedAt忽略之前的更改。

  5. 处理未知状态和waitingOn值显示为“仍在检查”。

  6. 重新提交时,更新同一条记录并显示此次尝试。

  7. 词语衰落reasonCode并且永远不要表现出error给申请人的理由。

  8. 重复使用requestId仅在重试时进行轮询,并且仅用于填补空缺。

参考来源

查尔斯·阿奇邦

关于作者

Charles Archibong

联合创始人

Charles Archibong是Toden的联合创始人。他撰写有关身份验证、金融科技以及构建可信数字服务的实际工作的文章。

  • 开发者

    正确验证 webhook 签名并处理重试

    对原始请求字节验证 V2 签名,拒绝重放窗口之外的时间戳,在执行任何工作之前,在唯一约束下记录事件 ID,并且只有在事件被持久存储后才返回 2xx。

  • 开发者

    可公开密钥和私钥:保护客户端免受验证数据的影响。

    将可发布密钥放在您的应用程序中,并将私钥保存在您的服务器上。可发布密钥可以启动验证并读取最少的状态信息,但绝不会读取身份数据,因此请做好密钥泄露的准备,并尽可能降低泄露造成的损失。

打造您的产品。其余交给我们。

端到端身份验证与合规服务,遵循全球标准,为创业者提供合理定价。