瀑布流
使用 k-ID 进行年龄验证是一个隐私保护型过程,允许用户在不透露个人信息的情况下证明其年龄。此方法使用瀑布流模型。
瀑布流
AgeKit+ 作为年龄检查的单点编排器,自动级联通过验证提供商的瀑布流来确认用户的年龄。实际上,对 k-ID 的一次 API 调用按顺序呈现配置的方法。例如,从电子邮件推理或面部年龄估计开始,然后根据需要回退到 ID 文档扫描或其他方法,直到用户的年龄得到验证或所有选项都用尽。这意味着开发者只需与 k-ID 的 API 集成一次,平台会在幕后处理尝试多种验证技术,组合方法以最大化成功验证的机会。
验证流程通过返回要在 iframe 或移动 Web 视图中托管的 URL 的 API 调用启动,用户在此界面中完成验证过程。可用验证方法由您在 Compliance Studio 中的产品配置确定,确保符合司法管辖区要求。
| API | 场景 |
|---|---|
/age-verification/perform-access-age-verification | 在获得对功能、成熟内容或产品本身的访问之前验证用户年龄。 |
/age-verification/perform-trusted-adult-verification | 执行可信成人(父母或监护人)验证。 |
/age-verification/perform-age-appeal | 对于年龄验证失败但想要对决定提出上诉的用户。 |
年龄验证 API 在请求和响应格式方面是标准化的。
请求正文
| 属性 | 说明 | 必需? |
|---|---|---|
jurisdiction | 应进行年龄验证的司法管辖区 | 是 |
criteria | 年龄验证的标准 | 是 |
subject.email | 如果用户在任何其他上下文中使用电子邮件地址通过 k-ID 验证了其年龄,则返回原始年龄而不是要求用户再次估计或证明其年龄。 | 否 |
subject.claimedAge | 如果用户在年龄门控中被询问其年龄,用于通知年龄估计过程 | 否 |
subject.id | 用于跨多个验证方法报告多次失败尝试的标识符。这可以是临时会话 ID 或哈希用户 ID。 | 否 |
options.facialAgeEstimation.passIfOver | 自动通过面部年龄估计所需的估计年龄阈值。如果估计年龄达到或超过此值,验证将通过。 | 否 |
options.facialAgeEstimation.failIfUnder | 低于此值的估计年龄将导致验证失败。如果估计年龄低于此值,验证将失败。如果省略,则默认为验证标准年龄。 | 否 |
options.redirectUrl | 验证完成后重定向到的 URL。支持 HTTP/HTTPS URL 或使用自定义协议方案的移动深度链接。仅当验证 URL 在浏览器或 Web 视图中直接打开时(未嵌入 iframe)才会发生重定向。发生重定向时,URL 包含 verificationId 和 result(PASS 或 FAIL)作为查询字符串参数。 | 否 |
passIfOver 和 failIfUnder 参数让您可以控制面部年龄估计结果中允许的方差。执行面部年龄估计扫描时:
- 如果估计年龄达到或超过
passIfOver,验证将通过并确定年龄信号。 - 如果估计年龄低于
failIfUnder,验证将失败并确定年龄信号。 - 如果估计年龄在
failIfUnder和passIfOver之间,结果被视为不确定,用户可以重试面部年龄估计。
这允许您设置一个置信范围,在该范围内结果足够明确以做出判断,同时在估计落在不确定范围内时给用户重试的机会。例如,如果您需要验证用户为 18+,可以将 passIfOver 设置为 25,将 failIfUnder 设置为 12。这意味着估计为 25 岁或以上的用户将立即通过,估计低于 12 岁的用户将立即失败,估计为 12-24 岁的用户可以重试扫描或尝试其他验证方法。
示例:
{
"jurisdiction": "US-CA",
"criteria": {
"ageCategory": "ADULT"
},
"options": {
"facialAgeEstimation": {
"passIfOver": 25,
"failIfUnder": 12
},
"redirectUrl": "https://example.com/verification-complete"
}
}
重定向 URL
redirectUrl 参数允许您指定用户在完成验证后应重定向到的位置。这对于以下情况很有用:
- 基于浏览器的流程:验证完成后重定向到另一个网页
- 自定义成功屏幕:显示您自己的自定义成功或失败页面
- 移动应用深度链接:使用自定义协议方案(例如,
myapp://verification-complete)将控制权返回给您的移动应用
仅当验证 URL 在浏览器或 Web 视图中直接打开时(未嵌入 iframe)才会发生重定向。当嵌入在 iframe 中时,验证结果通过 DOM 事件传递。
发生重定向时,重定向 URL 包含以下查询字符串参数:
verificationId:唯一的验证 IDresult:验证结果,PASS或FAIL
重定向 URL 示例:
https://example.com/verification-complete?verificationId=7854909b-9124-4bed-9282-24b44c4a3c97&result=PASS
响应正文
对年龄验证 API 的成功请求返回以下响应。
| 属性 | 说明 |
|---|---|
id | 由年龄验证服务生成的唯一验证 ID |
url | 必须嵌入 iframe 中并呈现给用户的年龄验证 URL,供他们验证自己。 |
示例:
{
"id": "7854909b-9124-4bed-9282-24b44c4a3c97",
"url": "https://family.k-id.com/verify?token=eyJ..."
}
嵌入验证界面
使用返回的 URL 在您的网站或应用中创建 iframe。用户通过此界面完成验证,可用方法自动适应司法管辖区要求。

<div id="verification-container">
<iframe
id="verification-widget"
src="VERIFICATION_URL"
width="100%"
height="600"
frameborder="0"
allow="camera;payment;publickey-credentials-get;publickey-credentials-create">
</iframe>
</div>
allow 属性需要启用以下功能:
camera:面部年龄估计所需payment:信用卡验证所需publickey-credentials-get和publickey-credentials-create:基于 WebAuthn 的验证方法所需
验证结果
一旦用户成功完成年龄验证,或用户已达到最大重试次数但未成功,年龄验证结果通过客户端和服务器端渠道传递。实现应结合使用两者:客户端事件最适合控制 UI 元素,而对于数据完整性,实际结果应来自 webhook 或调用 /age-verification/get-status。
客户端(DOM 事件) - 如果响应正文中的 URL 包含在 iframe 中,它会作为窗口消息(MessageEvent)发送到父框架,具有 Verification.Result 结构。
服务器端(webhooks) - 事件以 Verification.Result 事件的形式发送到注册的 webhook。
访问窗口消息的示例:
const handleMessage = (event: MessageEvent) => {
const message = event.data;
if (message.eventType === "Verification.Result") {
// 使用 DOM 事件进行即时 UI 更新
updateUI(message);
}
};
window.addEventListener("message", handleMessage);
对于数据完整性,始终通过 webhook 事件或调用 /age-verification/get-status 来验证结果,而不是仅依赖 DOM 事件。DOM 事件最适合响应式 UI 更新。
窗口事件和 webhook 事件的数据元素包含以下属性。
| 属性 | 说明 |
|---|---|
id | 这是结果的验证 ID。 |
status | 指示 PASS 或 FAIL 状态,基于用户是否满足年龄标准。 |
ageCategory | 指示用户在请求中指定的司法管辖区中属于的年龄类别。支持的值是 adult、digital-youth 或 digital-minor |
method | 指示用于验证的方法。支持的值是 id-document、age-estimation、age-attestation、credit-card、social-security-number |
failureReason | 验证失败的原因。支持的值是 age-criteria-not-met、max-attempts-exceeded 或 fraudulent-activity-detected。仅在 status 为 FAIL 时设置 |
age | 返回估计或验证年龄的下限和上限,作为 low 和 high。 |
示例:
{
"eventType": "Verification.Result",
"data": {
"id": "5a58e98a-e477-484b-b36a-3857ea9daaba",
"status": "PASS",
"ageCategory": "adult",
"method": "id-document",
"age": {
"low": 25,
"high": 25,
}
}
}
处理窗口事件:
const handleMessage = (event: MessageEvent) => {
const message = event.data;
if (message.eventType === "Verification.Result") {
if (message.data.status === "PASS") {
window.location.href = `https://www.example.com/success?verificationId=${message.data.id}`
}
if (message.data.status === "FAIL") {
window.location.href = `https://www.example.com/fail?verificationId=${message.data.id}`
}
}
};
window.addEventListener("message", handleMessage);
验证错误
如果发生意外错误,会触发 JavaScript 事件,以便您的实现可以优雅地处理错误。
有关事件结构的详细信息,请参阅 Verification.Error。
示例消息:
{
"eventType": "Verification.Error",
"method": "credit-card",
"status": "ERROR"
}
检查验证状态
除了在 JavaScript 中发送事件并通过 k-ID webhook 发送事件外,还可以查询验证的状态。这对于能够处理注册的 webhook 在一段时间内无法访问且状态事件从未发送的情况很有用。要获取验证的状态,请使用 /age-verification/get-status API。返回的数据结构遵循与 Verification.Result webhook 事件相同的契约,但在结构和字段存在方面有一些差异。有关分析验证结果的详细信息,包括字段存在规则、状态类型、webhook 和 API 端点响应之间的差异以及实现指南,请参阅验证事件契约。有关 webhook 事件的更多信息,请参阅 Webhooks。
边缘情况处理
在处理验证结果时,您必须考虑响应结构会根据验证结果和失败原因而有所不同。以下示例演示了不同响应变化的正确和错误处理模式。有关完整的字段存在规则,请参阅验证事件契约。
部分尝试(超过最大尝试次数)
当用户在没有确定性年龄判定的情况下用尽所有验证尝试时,验证会以 max-attempts-exceeded 失败。此响应不包含 method、age 或 ageCategory 字段。
示例有效负载:
{
"eventType": "Verification.Result",
"data": {
"id": "123e4567-e89b-12d3-a456-426614174002",
"status": "FAIL",
"failureReason": "max-attempts-exceeded"
}
}
age-criteria-not-met 的示例有效负载(用于比较):
{
"eventType": "Verification.Result",
"data": {
"id": "123e4567-e89b-12d3-a456-426614174001",
"status": "FAIL",
"method": "age-estimation-scan",
"failureReason": "age-criteria-not-met",
"age": {
"low": 16,
"high": 17
}
}
}
错误处理:
// ❌ 错误:假设 FAIL 时 method 和 age 始终存在
const handleVerification = (result: VerificationResult) => {
if (result.data.status === "FAIL") {
// 当 failureReason 为 max-attempts-exceeded 时,这些字段为 undefined
logFailedMethod(result.data.method);
recordFailedAge(result.data.age.low);
showRetryWithMethod(result.data.method);
}
};
正确处理:
// ✅ 正确:适当处理不同的失败场景
const handleVerification = (result: VerificationResult) => {
if (result.data.status === "FAIL") {
denyAccess();
switch (result.data.failureReason) {
case "max-attempts-exceeded":
// 没有做出年龄判定 - 提供替代选项
showMaxAttemptsMessage();
offerSupportContact();
// 考虑对未来尝试实施速率限制
break;
case "age-criteria-not-met":
// 年龄已确定但不满足标准
// method 和 age 字段可用
if (result.data.age) {
logDeterminedAge(result.data.age.low);
}
showAgeCriteriaNotMetMessage();
break;
case "fraudulent-activity-detected":
// 处理可疑活动(参见下一节)
handleSuspiciousActivity(result.data.id);
break;
default:
// 优雅地处理未知失败原因
logUnknownFailure(result.data.failureReason);
showGenericFailureMessage();
}
}
};
检测到可疑活动
当系统检测到潜在欺诈行为时,验证会以 fraudulent-activity-detected 失败。出于安全原因,此响应不包含年龄数据,也不包含 method 字段。
示例有效负载:
{
"eventType": "Verification.Result",
"data": {
"id": "123e4567-e89b-12d3-a456-426614174003",
"status": "FAIL",
"failureReason": "fraudulent-activity-detected"
}
}
错误处理:
// ❌ 错误:将欺诈活动视为正常失败
const handleVerification = (result: VerificationResult) => {
if (result.data.status === "FAIL") {
// 允许立即重试可能导致持续滥用
showRetryButton();
// 记录不存在的年龄数据
analytics.track("verification_failed", {
age: result.data.age?.low // 欺诈活动时为 undefined
});
}
};
正确处理:
// ✅ 正确:实施适当的安全措施
const handleVerification = (result: VerificationResult) => {
if (result.data.status === "FAIL") {
denyAccess();
if (result.data.failureReason === "fraudulent-activity-detected") {
// 记录安全事件以供审查
securityLog.warn("Fraudulent activity detected", {
verificationId: result.data.id,
timestamp: new Date().toISOString(),
subjectId: currentSubjectId
});
// 实施更严格的速率限制或临时阻止
applySecurityCooldown(currentSubjectId);
// 显示适当的消息,不透露检测详情
showVerificationUnavailableMessage();
// 不提供立即重试 - 这可能导致持续滥用
hideRetryOptions();
// 可选择标记为手动审查
flagForManualReview(result.data.id);
}
}
};
完整的边缘情况处理程序
以下示例展示了正确处理所有边缘情况的综合处理程序:
interface VerificationData {
id: string;
status: "PASS" | "FAIL";
method?: string;
ageCategory?: "adult" | "digital-youth" | "digital-minor"; // PASS 状态时始终存在
age?: { low: number; high: number }; // PASS 状态时始终存在
dob?: string;
failureReason?: string;
}
const handleVerificationResult = (data: VerificationData) => {
// 始终记录验证尝试
logVerificationAttempt(data.id, data.status);
if (data.status === "PASS") {
// 授予访问权限 - 用户满足年龄标准
grantAccess();
// age 和 ageCategory 在 PASS 状态时始终存在
storeAgeData(data.age.low, data.age.high);
applyPermissionsForCategory(data.ageCategory);
// 仅在存在时处理可选字段
if (data.dob) {
storeDateOfBirth(data.dob);
}
if (data.method) {
analytics.track("verification_passed", { method: data.method });
}
return;
}
// 处理 FAIL 状态
denyAccess();
// failureReason 在 FAIL 状态下始终存在
switch (data.failureReason) {
case "age-criteria-not-met":
// 此失败原因下 method 和 age 可用
handleAgeCriteriaFailure(data);
break;
case "max-attempts-exceeded":
// 没有年龄判定 - method 和 age 不可用
handleMaxAttemptsFailure(data.id);
break;
case "fraudulent-activity-detected":
// 安全事件 - method 和 age 不可用
handleFraudulentActivity(data.id);
break;
default:
// 始终优雅地处理未知失败原因
handleUnknownFailure(data);
}
};
在访问可选字段之前,始终检查字段是否存在。验证事件契约提供了每种状态和失败原因组合的完整字段存在规则。
限制验证尝试
每个验证请求允许用户对每个可用验证方法进行三次尝试。如果出现以下情况,验证将失败:
- 所有可用验证方法都已用尽,无法确定年龄
- 确定了年龄但低于您标准的所需阈值
当验证失败时,您可以允许用户启动新的验证尝试。但是,为了防止滥用和滥用验证系统,您应该对额外的验证尝试实施速率限制。例如,您可能将用户限制为在 24 小时内进行三次验证尝试。
使用验证请求中的 subject.id 字段来跟踪跨多个验证请求的尝试。此字段应包含用户的一致标识符(如临时会话 ID 或哈希用户 ID),允许您:
- 跟踪每个用户的验证尝试次数
- 实施基于时间的速率限制(例如,每 24 小时 3 次尝试)
- 防止用户通过创建新会话来绕过限制
在启动验证请求之前,在服务器上实施速率限制。这可以防止不必要的 API 调用,并有助于保护您的系统免受滥用。
验证方法
有关所有可用验证方法的详细信息,请参阅 验证方法。