跳到主要内容

瀑布流

使用 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 包含 verificationIdresult(PASS 或 FAIL)作为查询字符串参数。

passIfOverfailIfUnder 参数让您可以控制面部年龄估计结果中允许的方差。执行面部年龄估计扫描时:

  • 如果估计年龄达到或超过 passIfOver,验证将通过并确定年龄信号。
  • 如果估计年龄低于 failIfUnder,验证将失败并确定年龄信号。
  • 如果估计年龄在 failIfUnderpassIfOver 之间,结果被视为不确定,用户可以重试面部年龄估计。

这允许您设置一个置信范围,在该范围内结果足够明确以做出判断,同时在估计落在不确定范围内时给用户重试的机会。例如,如果您需要验证用户为 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)将控制权返回给您的移动应用
important

仅当验证 URL 在浏览器或 Web 视图中直接打开时(未嵌入 iframe)才会发生重定向。当嵌入在 iframe 中时,验证结果通过 DOM 事件传递。

发生重定向时,重定向 URL 包含以下查询字符串参数:

  • verificationId:唯一的验证 ID
  • result:验证结果,PASSFAIL

重定向 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-getpublickey-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);
important

对于数据完整性,始终通过 webhook 事件或调用 /age-verification/get-status 来验证结果,而不是仅依赖 DOM 事件。DOM 事件最适合响应式 UI 更新。

窗口事件和 webhook 事件的数据元素包含以下属性。

属性说明
id这是结果的验证 ID。
status指示 PASSFAIL 状态,基于用户是否满足年龄标准。
ageCategory指示用户在请求中指定的司法管辖区中属于的年龄类别。支持的值是 adultdigital-youthdigital-minor
method指示用于验证的方法。支持的值是 id-documentage-estimationage-attestationcredit-cardsocial-security-number
failureReason验证失败的原因。支持的值是 age-criteria-not-metmax-attempts-exceededfraudulent-activity-detected。仅在 statusFAIL 时设置
age返回估计或验证年龄的下限和上限,作为 lowhigh

示例:

{
"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 失败。此响应不包含 methodageageCategory 字段。

示例有效负载:

{
"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 调用,并有助于保护您的系统免受滥用。

验证方法

有关所有可用验证方法的详细信息,请参阅 验证方法