AgeKit+

瀑布流

使用 k-ID 进行年龄验证是一个隐私保护型过程,允许用户在不透露个人信息的情况下证明其年龄。此方法使用瀑布流模型。

瀑布流

AgeKit+ 作为年龄检查的单点编排器,自动级联通过验证提供商的瀑布流来确认用户的年龄。实际上,对 k-ID 的一次 API 调用按顺序呈现配置的方法。例如,从电子邮件推理或面部年龄估计开始,然后根据需要回退到 ID 文档扫描或其他方法,直到用户的年龄得到验证或所有选项都用尽。这意味着开发者只需与 k-ID 的 API 集成一次,平台会在幕后处理尝试多种验证技术,组合方法以最大化成功验证的机会。

验证流程通过 API 调用启动,该调用返回一个供用户完成验证过程的 URL。您可以在不同环境中展示此 URL(参见展示验证 URL)。可用验证方法由您在 Compliance Studio 中的产品配置确定,确保符合司法管辖区要求。

在用户开始流程时再创建验证

请等用户选择开始验证(例如,在访问功能前点击用于开始验证的控件)。然后再调用 API 创建验证并获取 URL,可使用 年龄验证 API 端点 等端点。不要为用户可能不会进入的路径提前创建验证或获取 URL。更多说明请参阅 最佳实践。

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.locale验证 UI 的可选 IETF BCP 47 语言标签。省略时遵循访客浏览器的语言偏好;当应用已提供用户选择的语言且希望验证与之保持一致时设置。否
options.redirectUrl验证完成后重定向到的 URL。支持 HTTP/HTTPS URL 或使用自定义协议方案的移动深度链接。仅当验证 URL 在浏览器或 Web 视图中直接打开时(未嵌入 iframe)才会发生重定向。发生重定向时,URL 包含 verificationId 和 result(PASS 或 FAIL)作为查询字符串参数。否

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

  • 如果估计年龄达到或超过 passIfOver,验证将通过并确定年龄信号。
  • 如果估计年龄低于 failIfUnder,验证将失败并确定年龄信号。
  • 如果估计年龄在 failIfUnder 和 passIfOver 之间,结果被视为不确定。在该次验证的后续流程中,面部年龄估计将被禁用,但其余验证方法仍可供用户继续使用。只有在所有方法都用尽后,验证才会以 max-attempts-exceeded 失败。

这允许您设置一个置信范围,在该范围内结果足够明确以做出判断,同时在估计落在不确定范围内时回退到您的其他验证方法。例如,如果您需要验证用户为 18+,可以将 passIfOver 设置为 25,将 failIfUnder 设置为 12。这意味着估计为 25 岁或以上的用户将立即通过,估计低于 12 岁的用户将立即失败,估计为 12-24 岁的用户可以继续使用其他验证方法(面部年龄估计本身不会重试)。

示例:

{
  "jurisdiction": "US-CA",
  "criteria": {
    "ageCategory": "ADULT"
  },
  "options": {
    "facialAgeEstimation": {
      "passIfOver": 25,
      "failIfUnder": 12
    },
    "locale": "en-US",
    "redirectUrl": "https://example.com/verification-complete"
  }
}

重定向 URL

redirectUrl 参数允许您指定用户在完成验证后应重定向到的位置。这对于以下情况很有用:

  • 基于浏览器的流程:验证完成后重定向到另一个网页
  • 自定义成功屏幕:显示您自己的自定义成功或失败页面
  • 移动应用深度链接:使用自定义协议方案(例如,myapp://verification-complete)将控制权返回给您的移动应用

Important

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

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

  • verificationId:唯一的验证 ID
  • result:验证结果,PASS 或 FAIL

重定向 URL 示例:

https://example.com/verification-complete?verificationId=7854909b-9124-4bed-9282-24b44c4a3c97&result=PASS

响应正文

对年龄验证 API 的成功请求返回以下响应。

属性说明
id由年龄验证服务生成的唯一验证 ID
url呈现给用户供其验证自己的托管年龄验证 URL。将其嵌入 iframe,或在浏览器或 Web 视图中打开。参见展示验证 URL。
shortUrl适用于二维码的短 URL,会重定向到完整的验证 URL

示例:

{
  "id": "7854909b-9124-4bed-9282-24b44c4a3c97",
  "url": "https://family.k-id.com/verify?token=eyJ...",
  "shortUrl": "https://family.k-id.com/v/7854909b-9124-4bed-9282-24b44c4a3c97?pid=42&s=qr"
}

在部分平台(例如客厅游戏主机)上,若您选择不在嵌入式浏览器中运行完整验证流程,可以使用返回的 shortUrl 生成二维码,让用户在手机等另一台设备上继续完成验证。完整的 url 在其 token 查询参数中包含表示会话的 JWT。不要自行缩短或重新构造。请将 shortUrl 视为不透明值,按原样展示或编码,不要依赖其路径或查询结构,短链接的呈现方式今后可能会变化。

验证 URL 有效期

验证 URL 自创建起 2 周内有效。过期时间编码在 URL 的 token 查询参数的 JWT 中。若要在不打开 URL 的情况下判断此前生成的 URL 是否仍有效,请解码 JWT 并查看包含过期时间的标准 exp 声明。

若用户在 URL 过期后返回,请使用保存的验证 ID 调用 /age-verification/get-status。若该验证已不存在(例如从未完成并被移除),请再次调用 perform 端点以创建新的验证。

展示验证 URL

验证 URL 是一个托管的网页。请在适合您应用程序的环境中展示它:

  • Web 应用:将其嵌入到 iframe 中(参见下方示例)、在弹出窗口中打开,或整页重定向。
  • 移动应用:在系统浏览器组件(Android Custom Tabs、iOS ASWebAuthenticationSession)中打开验证 URL,并通过 redirectUrl 回调接收结果。有关推荐的平台组件、AgeKey 支持矩阵和设备方向指南,请参阅移动应用快速入门指南。
  • 主机游戏(Switch、PlayStation、Xbox):主机浏览器通常受限或不存在。将 shortUrl 显示为二维码,让玩家在配对的移动设备上完成验证,然后通过 webhook 或 /age-verification/get-status 轮询确认结果,因为移动设备的重定向无法返回到主机。

验证界面内可用的方法会自动适应司法管辖区要求,与展示环境无关。

Web 示例

使用返回的 URL 在您的网站或应用中创建 iframe。用户通过此界面完成验证,可用方法自动适应司法管辖区要求。

Age Verification Widget

<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);

Important

对于数据完整性,始终通过 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"
}

检查验证状态

您可以通过使用验证 ID 调用 /age-verification/get-status独立于验证 URL 获取验证状态。这在以下情况下很有用:

  • 注册的 webhook 无法访问且从未收到状态事件
  • 需要在不将用户重定向到验证 URL 的情况下检查状态
  • 之前生成的 URL 已过期,想确认验证是否已完成

返回的数据结构遵循与 Verification.Result webhook 事件相同的契约,但在结构和字段存在方面有一些差异。有关分析验证结果的详细信息,包括字段存在规则、状态类型、webhook 和 API 端点响应之间的差异以及实现指南,请参阅验证事件契约。有关 webhook 事件的更多信息,请参阅 Webhooks。

处理过期的验证 URL

若用户持有已过期的验证 URL(自创建起 2 周内有效):

  1. 使用保存的验证 ID 调用 /age-verification/get-status。
  2. 若验证存在,使用返回的状态(例如 PASS 或 FAIL)。
  3. 若 get-status 返回 400 且错误代码为 INVALID_INPUT,请再次调用 perform 端点以创建新验证,并向用户展示新 URL。

验证保留期限

超过 2 周仍处于 PENDING 状态的验证会从系统中删除。删除后,/age-verification/get-status 将返回 400 及错误代码 INVALID_INPUT;调用方应将该响应视为需要创建新验证。

边缘情况处理

在处理验证结果时,您必须考虑响应结构会根据验证结果和失败原因而有所不同。以下示例演示了不同响应变化的正确和错误处理模式。有关完整的字段存在规则,请参阅验证事件契约。

部分尝试(超过最大尝试次数)

当用户在没有确定性年龄判定的情况下用尽所有验证尝试时,验证会以 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 调用,并有助于保护您的系统免受滥用。

验证方法

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

On this page