> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Didit 沙盒测试数据：场景、魔法值与证件

> 在 Didit 沙盒中测试：场景、魔法值、arm endpoint、样例证件与一次性验证码，获得确定性结果，不涉及个人信息，也不产生计费。

> [!NOTE]
>
> 本页数据的权威来源是验证服务中的 `serialize_catalog()`，并通过 `GET /v1/sandbox/scenarios/` 实时提供。`service-didit-verification` 仓库中的 `docs/sandbox/SANDBOX_REFERENCE.md` 是由 `pipenv run python scripts/generate_sandbox_reference.py` 生成的经过漂移测试的副本。本页内容与该参考文档保持一致。如果此处内容看起来过时，请始终以实时的目录接口为准。该目录接口需要身份验证：请像调用其他接口一样，通过 `x-api-key` 发送您的 API 密钥；如果您是在正在运行的会话内部读取，则可以使用会话令牌。

Didit 的**沙盒**相当于 Stripe 的测试卡或 Sumsub 的证件模板：它可以在不接触真实服务提供商、真实 PII 或您的余额的情况下，确定性地复现任意验证结果。

## 环境模型

沙盒是**按应用划分的模式**（`ApplicationDetail.mode == SANDBOX`），与 Stripe 测试模式的理念相同。当会话在沙盒应用下运行时：

* 所有外部服务提供商（OCR、AML、人脸比对、活体检测、NFC、电话验证、邮箱验证、IP、地址证明、数据库核验、Document AI）都会被**模拟**，不会调用任何第三方。
* **活体检测的运行方式与生产环境完全相同。** 主动活体检测工作流会在沙盒中运行真实的主动采集，无论是托管流程还是通过原生 SDK 的 Web 跳转，都是如此，因此您可以测试终端用户将获得的真实体验。只有活体检测**结果**由场景或魔法值模拟，报告中记录的是您的工作流所配置的检测方式。
* **自定义域名仅限生产环境使用。** 沙盒应用无法添加[自定义域名](https://docs.didit.me/zh/console/custom-domain.md)；沙盒会话始终运行在默认的托管验证域名上。
* 会话**从不计费**，并且会跳过余额检查。
* 每个应用每 24 小时最多可创建 500 个会话。
* Webhook 会携带 `"environment": "sandbox"`（生产环境会话携带 `"environment": "live"`）。
* 托管流程会展示会话所处的环境，以便界面提醒用户不要上传真实的个人身份信息（PII）。
* **结果永远不取决于像素内容。** 提取的数据和状态均来自场景或魔法值。采集到的媒体本身（证件、自拍照、视频、地址证明文件）会像生产环境中的会话一样实际存储。

## 沙盒中的交易监控

沙盒是**以会话为模型**的：它模拟验证会话会调用的服务商。交易监控没有可以模拟的服务商，因此沙盒应用走另一条路径：交易会被存储，但**针对它的任何判定、筛查、通知或计费都不会运行**。

在沙盒应用上创建的交易会照常保存并返回，但不会发生以下任何情况：

* **不执行规则评估。** `rule_runs` 为空，无论你的规则处于哪种模式。沙盒应用上的 `ACTIVE` 规则永远不会触发，因此交易保持 `APPROVED`，分数为 `0`，也不会触发警报或创建案件。
* **不进行 AML / 钱包筛查。** 即使在已启用监控的应用上发起加密货币交易，`provider_results` 也为空。
* **不发送 Webhook。** 沙盒交易不会投递 `transaction.created` 或 `transaction.status.updated`。
* **不计费。** 会记录一条单价为 `0.0000` 的用量事件，你的余额不变。

每个交易响应都包含 `environment` 字段（`"live"` 或 `"sandbox"`），因此空的 `rule_runs` 永远不会产生歧义：`"sandbox"` 表示规则未被评估，而不是规则都未命中。Business Console 在交易的 *Risk & decision* 卡片中也是同样的含义，而不是只显示一句 *"No rules evaluated"*。

> [!NOTE]
>
> **Travel Rule 是“什么都不运行”的唯一例外。** 在启用了 Travel Rule 的应用上，沙盒中的 `travelRule` 交易仍会运行交换引擎：会创建转账记录，并使其依次经过各个状态。沙盒从不与 Didit 之外的任何人通信。其唯一的交易对手是内置于平台中的 [Didit Test VASP](https://docs.didit.me/zh/transaction-monitoring/travel-rule-sandbox.md)：任何轨道（`TRP`、你已连接的网络成员资格、电子邮件或 `INTERNAL`）都不会把合规消息发送给真实的交易对手 VASP，跳过交易对手 VASP 归属筛查，取消交易也不会向交易对手发送任何内容。`travel_rule.status.updated` Webhook 会照常投递，并标记为 `"environment": "sandbox"`。
>
> **出账**：你提交的 `travelRule` 交易。系统先执行 `threshold_amount` 检查。在沙盒中，只有当你发送 `transaction_details.amount_in_default_currency` 时它才生效，因为沙盒不转换货币；否则沙盒中的每笔转账都会被视为超过阈值。未应答的转账会在 `confirmation_timeout_hours` 后过期，随后你的 `timeout_outcome` 适用于该交易。超过阈值后，自托管钱包检查照常适用：转给[已声明的自托管钱包](https://docs.didit.me/zh/transaction-monitoring/travel-rule.md#已声明的自托管钱包)的转账会进入 `UNCONFIRMED_OWNERSHIP`，且不发送任何内容。你可以在沙盒中通过[钱包所有权小组件](https://docs.didit.me/zh/transaction-monitoring/wallet-ownership-widget.md)完成该转账；当目标钱包已在你的钱包地址簿中时，也可以通过[确认所有权](https://docs.didit.me/zh/management-api/travel-rule/confirm-ownership.md)完成。任何其他超过阈值的转账都会通过 `TEST_VASP` 轨道发送给 Didit Test VASP。它会在 Didit 内部对该转账作出响应，具体场景由你通过收款钱包或收款人姓名选择：转账可能完成、被拒绝、信息不匹配、进入 `COUNTERPARTY_VASP_NOT_REACHABLE`、过期（随后你的 `timeout_outcome` 适用于该交易），或等待钱包所有权。查看每个场景，请参阅[在沙盒中测试 Travel Rule](https://docs.didit.me/zh/transaction-monitoring/travel-rule-sandbox.md)。
>
> **入账**：`POST /v3/travel-rule/inbound/`。登记从不通过轨道发送，因此登记本身没有任何模拟：沙盒会将存款与你的钱包地址簿进行比对，并同步结算，与正式环境完全一致。结果是真实的实际结果，而不是 `AWAITING_COUNTERPARTY`：`ON_HOLD`（地址簿中无匹配）、`UNCONFIRMED_OWNERSHIP`（匹配的条目未经所有权验证）、`COUNTERPARTY_MISMATCHED_DATA`（收款人姓名与钱包持有人不符），或 `COMPLETED` 后接 `FINISHED`（匹配到已验证的条目）。要改为接收来自 Didit Test VASP 的转账，请使用[触发 Test VASP 入账转账](https://docs.didit.me/zh/management-api/travel-rule/trigger-test-vasp-inbound.md)，并指定你自己地址簿中的一个钱包。

### Travel Rule 测试 VASP 场景

目录中的 `travel_rule.scenarios` 列出了以下出账场景。在沙盒应用上启用 Travel Rule 后，请在出账 `travelRule` 交易上设置下表中的收款人姓名。上文描述的阈值检查和已声明自托管钱包检查会先运行。

| 场景 | 收款人姓名 | 结果 |
| - | - | - |
| `accept` | `Didit Test VASP` | `COMPLETED` |
| `decline` | `Didit Test VASP Decline` | `COUNTERPARTY_VASP_GENERAL_DECLINE` |
| `name_mismatch` | `Didit Test VASP Name Mismatch` | `COUNTERPARTY_MISMATCHED_DATA` |
| `not_reachable` | `Didit Test VASP Not Reachable` | 投递重试后为 `COUNTERPARTY_VASP_NOT_REACHABLE` |
| `timeout` | `Didit Test VASP Timeout` | 到期检查在一分钟截止时间之后运行时为 `EXPIRED` |
| `self_hosted` | `Didit Test VASP Self Hosted` | `UNCONFIRMED_OWNERSHIP` |

你也可以用收款钱包地址代替姓名来选择场景（两者都设置时以钱包为准）。未指定任何一项的转账会以 `accept` 完成。[在沙盒中测试 Travel Rule](https://docs.didit.me/zh/transaction-monitoring/travel-rule-sandbox.md) 列出了每个场景的钱包及其确切结果。

[入账测试 VASP 触发器](https://docs.didit.me/zh/management-api/travel-rule/trigger-test-vasp-inbound.md) 的 `scenario` 字段只接受 `accept` 和 `name_mismatch`。

> [!NOTE]
>
> **如何在规则上线前进行测试。** 使用[回测](https://docs.didit.me/zh/transaction-monitoring/rules-api.md#创建、回测、启用)：它会用你真实的交易历史重放规则，并报告 `evaluated` / `matched` / `affected_entities`，且不改变任何交易的状态。将其与正式应用上的 `TEST` 模式搭配使用：`TEST` 规则会对每笔新的正式交易进行评估，并记录它本会命中的内容，但不改变分数或状态。待规则的筛选效果看起来合适后，再切换到 `ACTIVE`。

## 如何在生产环境与沙盒之间切换

生产环境和沙盒是**独立的应用**，因此测试流量与生产数据绝不会混在一起。每个应用都有一个 `mode`，取值为 `live` 或 `sandbox`：

* 使用**沙盒模式**应用的 API 密钥，可获得用于模拟的、不计费的、可模拟结果的会话。
* 使用**生产模式**应用的 API 密钥，进行真实的验证。

`sandbox_scenario` 字段（用于会话创建请求和独立的 AML API）和模拟接口仅被沙盒应用接受；在生产应用上会被拒绝。

## 托管验证流程中的沙盒

在沙盒应用下创建的会话，会在托管验证流程中呈现额外的界面（即 `POST /v3/session/` 返回的 `url`）：

* **卡片内沙盒横幅。** 每个界面的卡片内都会显示一条横幅，文字为“仅限测试数据，无真实调用”，以便始终清楚地表明该会话从不调用真实服务商，也不会影响您的余额。采集到的媒体与任何会话一样会被存储，因此请使用示例证件和测试数据，而不要使用真实身份证件或个人信息。
* **流程前场景选择器。** 在开始采集之前，卡片内的选择器会引导用户选择一个测试结果：先选择结果，再选择该结果对应的触发器。确认后会调用 `POST /v3/session/{session_id}/sandbox/arm/`（见下文 [流程开始前设置（或重新设置）会话](#3-在流程开始前预设（或重新预设）会话)），然后进入流程，并已应用该场景的魔法值。
* **示例证件条。** 文档上传/采集组件正下方直接显示一排沙盒示例证件（最多 3 个）。点击其中一个即可代替真实上传使用。

沙盒会话会存储**真实**采集的媒体：证件、自拍照、活体视频和地址证明文件，都会像生产会话一样被上传并保留。*提取的*数据（证件验证字段、人脸比对分数、活体检测结果等类似内容）以及会话结果本身（已通过 / 审核中 / 已拒绝），都由场景或魔法值模拟生成，绝不源自采集的媒体。Business Console 会在沙盒会话的提取数据部分标注一个**模拟数据**标签，方便审核人员一眼分辨会话中哪些部分是真实采集的，哪些部分由场景驱动。

## 场景

每个场景都是一组命名的魔法值。在创建会话时（或调用模拟接口时）将其标识符作为 `sandbox_scenario` 传入，即可复现对应结果。

| Slug | 标题 | 默认状态 | 描述 |
| - | - | - | - |
| `approve` | 正常流程：所有功能均通过 | 已通过 | 默认沙盒行为：每个模拟功能都返回其无异常的 APPROVED 结果。会话状态最终为 APPROVED。 |
| `decline_aml_hit` | AML：政治公众人物 | 已拒绝 | AML 模拟返回一条命中，会话在 AML 功能上被拒绝。该命中使用与生产环境 AML 服务商相同的 [Hit 结构](https://docs.didit.me/zh/core-technology/aml-screening/report-aml-screening.md#各匹配项字段)，其中 `url`、`match_score`、`risk_score`、`review_status`、`properties`、`pep_matches[]` 和 `score_breakdown` 均有值，并写入一条 POSSIBLE\_MATCH\_FOUND 警告级日志。 |
| `decline_document_expired` | 身份证件验证：证件已过期 | 已拒绝 | 身份证件验证模拟返回一本有效期已过的护照。会话在身份证件验证上被拒绝，并写入一条 DOCUMENT\_EXPIRED 警告级日志。 |
| `decline_minimum_age` | 身份证件验证：低于最低年龄要求 | 已拒绝 | 身份证件验证模拟返回一个出生日期，使用户年龄约为 10 岁。生产环境的年龄检查会触发 MINIMUM\_AGE\_NOT\_MET，工作流随即拒绝。 |
| `decline_mrz_validation` | 身份证件验证：MRZ 校验和失败 | 已拒绝 | 身份证件验证模拟返回一本 MRZ 校验和已损坏的护照。生产环境的 MRZ 校验器会触发 MRZ\_VALIDATION\_FAILED，会话被拒绝。 |
| `decline_could_not_recognize_document` | 身份证件验证：无法识别的证件 | 已拒绝 | 身份证件验证模拟返回空响应。生产环境会抛出 COULD\_NOT\_RECOGNIZE\_DOCUMENT，并将其呈现给 SDK。 |
| `decline_face_match_low_similarity` | 人脸比对：相似度低于拒绝阈值 | 已拒绝 | 人脸比对模拟返回较低分数。生产环境会触发 LOW\_FACE\_MATCH\_SIMILARITY，工作流节点的操作映射将其拒绝。 |
| `decline_database_no_match` | 数据库核验：所有服务均无匹配 | 已拒绝 | 每个所选数据库服务都返回 NO\_MATCH。生产环境会触发 DATABASE\_VALIDATION\_NO\_MATCH，工作流随即拒绝。 |
| `decline_phone_high_risk` | 电话验证：保留的高风险测试号码 | 已拒绝 | 电话验证模拟会阻止此次验证（保留测试号段）。电话验证结束状态为 BLOCKED，并触发 HIGH\_RISK\_PHONE\_NUMBER。 |
| `decline_email_breach` | 邮箱：数据泄露数据库命中 | 已拒绝 | 邮箱泄露模拟返回该地址的一条数据泄露命中。邮箱验证会触发 BREACHED\_EMAIL\_DETECTED。 |
| `decline_ip_blocklist` | IP 分析：地址在黑名单中 | 已拒绝 | IP 分析模拟以 IP\_ADDRESS\_IN\_BLOCKLIST 拒绝该位置。会话在 IP 分析上被拒绝。 |
| `decline_poa_address_mismatch` | 地址证明（POA）：地址与所提供地址不符 | 已拒绝 | 地址证明模拟以 ADDRESS\_MISMATCH\_WITH\_PROVIDED 拒绝该地址证明。会话在地址证明上被拒绝。 |
| `decline_liveness_attack` | 活体检测：检测到人脸攻击 | 已拒绝 | 被动活体检测模拟检测到人脸攻击信号，并触发 LIVENESS\_FACE\_ATTACK。会话在活体检测上被拒绝。 |
| `decline_nfc_chip_not_verified` | NFC：电子护照芯片未验证 | 已拒绝 | 电子护照模拟返回一个 SOD/DG 签名未通过完整性校验的芯片。生产环境会触发 NFC\_CHIP\_NOT\_VERIFIED。当工作流在芯片未验证时拒绝，会话将在 NFC 上被拒绝。 |
| `decline_nfc_trust_anchor_missing` | NFC：签发方信任锚不可用 | 已拒绝 | 电子护照模拟返回一个由 DSC 签名的芯片，其签发 CSCA 不在信任库中。生产环境会触发 NFC\_TRUST\_ANCHOR\_MISSING。当工作流在芯片未验证时拒绝，会话即被拒绝。 |
| `decline_document_ai_unreadable` | Document AI：无法读取的证件 | 已拒绝 | Document AI 模拟未返回任何可读字段。生产环境会触发 DOCUMENT\_AI\_UNREADABLE\_DOCUMENT，重试次数耗尽后，证件最终状态为已拒绝。 |
| `review_aml_possible_match` | AML：可能匹配，需审核 | 审核中 | AML 模拟返回一条置信度较低的命中。会话进入审核中状态，并写入一条警告级 POSSIBLE\_MATCH\_FOUND 日志，供分析人员处理。 |
| `review_face_match_borderline` | 人脸比对：相似度处于临界值 | 审核中 | 人脸比对模拟返回一个介于审核阈值和拒绝阈值之间的分数。会话在人脸比对上进入审核中状态。 |
| `review_document_borderline_age` | 身份证件验证：未达最低年龄，转入审核 | 审核中 | 身份证件验证模拟返回一个表明用户年龄低于工作流所配置最低年龄的出生日期。工作流的 minimum\_age\_action 设置为 REVIEW 而非 DECLINE，因此会话在身份证件验证上进入审核中状态，而不是被拒绝。 |
| `review_poa_partial_match` | 地址证明：地址部分匹配 | 审核中 | 地址证明模拟返回一个仅与所提供地址部分匹配的地址。会话在地址证明上进入审核中状态。 |
| `review_database_partial_match` | 数据库核验：部分匹配 | 审核中 | 数据库服务在姓名和出生日期上返回部分匹配。会话在数据库核验上进入审核中状态。 |
| `decline_kyb_registry_mismatch` | KYB：登记数据不一致 | 已拒绝 | 公司登记模拟返回一家公司，其登记数据与预期详情不一致。企业会话在公司登记核验上被拒绝。 |
| `review_kyb_registry_partial` | KYB：登记数据部分匹配 | 审核中 | 公司登记模拟返回一家公司，其名称仅部分匹配。企业会话在公司登记核验上进入审核中状态。 |

`review_*` 场景的存在，是为了让你无需提供足以被拒绝的输入，即可测试人工审核的用户体验（控制台审核队列、`In Review` Webhook、审核员的通过/拒绝操作）。上述两个 `kyb_*` 场景设置的是 `expected_details.registration_number`，而不是 `expected_details.first_name`：它们仅适用于企业验证（KYB）会话。

## 魔法值

魔法值是一种特定输入，可使某个模拟功能输出特定风险。将输入字段设为魔法值（可直接设置，也可通过场景设置）即可触发该风险。

| 功能 | 输入字段 | 魔法值 | 风险 | 说明 |
| - | - | - | - | - |
| AML | `user_email` | `sandbox+aml_hit@didit.test` | `POSSIBLE_MATCH_FOUND` | AML：政治公众人物；AML 模拟返回一条命中记录，会话在 AML 筛查中被拒绝。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_DOCUMENT_EXPIRED` | `DOCUMENT_EXPIRED` | OCR：证件已过期；会话在身份证件验证中被拒绝。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_MINIMUM_AGE_NOT_MET` | `MINIMUM_AGE_NOT_MET` | OCR：根据出生日期，用户年龄低于配置的最低年龄；会话被拒绝。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_MAXIMUM_AGE_EXCEEDED` | `MAXIMUM_AGE_EXCEEDED` | OCR：根据提取的出生日期，用户年龄超过配置的最高年龄限制。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_DOCUMENT_NUMBER_FORMAT_MISMATCH` | `DOCUMENT_NUMBER_FORMAT_MISMATCH` | OCR：提取的证件号码与证件注册表中该证件的预期格式正则表达式不匹配。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_PERSONAL_NUMBER_FORMAT_MISMATCH` | `PERSONAL_NUMBER_FORMAT_MISMATCH` | OCR：提取的个人编号未通过证件注册表的格式检查。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION` | `DOCUMENT_NOT_SUPPORTED_FOR_APPLICATION` | OCR：提取的证件类型不在应用的 `documents_allowed` 策略范围内。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_FULL_NAME_MISMATCH_WITH_PROVIDED` | `FULL_NAME_MISMATCH_WITH_PROVIDED` | OCR：证件上的姓名与客户在 `expected_details` 中预填的姓名不匹配，且差异超过姓名匹配阈值。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_GENDER_MISMATCH_WITH_PROVIDED` | `GENDER_MISMATCH_WITH_PROVIDED` | OCR：提取的性别与 `expected_details.gender` 不一致。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_DOB_MISMATCH_WITH_PROVIDED` | `DOB_MISMATCH_WITH_PROVIDED` | OCR：提取的出生日期与 `expected_details.date_of_birth` 不一致。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_COUNTRY_MISMATCH_WITH_PROVIDED` | `COUNTRY_MISMATCH_WITH_PROVIDED` | OCR：签发国家/地区与 `expected_details.country`（或 `id_country`）不一致。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_NATIONALITY_MISMATCH_WITH_PROVIDED` | `NATIONALITY_MISMATCH_WITH_PROVIDED` | OCR：提取的国籍与 `expected_details.nationality` 不一致。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_IDENTIFICATION_NUMBER_MISMATCH_WITH_PROVIDED` | `IDENTIFICATION_NUMBER_MISMATCH_WITH_PROVIDED` | OCR：提取的证件号、个人编号或税号与 `expected_details.identification_number` 不一致。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_COULD_NOT_RECOGNIZE_DOCUMENT` | `COULD_NOT_RECOGNIZE_DOCUMENT` | OCR 流程返回空结果或无法识别的结果；生产环境会抛出 COULD\_NOT\_RECOGNIZE\_DOCUMENT。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_MRZ_NOT_DETECTED` | `MRZ_NOT_DETECTED` | OCR：证件需要 MRZ，但流程未能找到。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_MRZ_VALIDATION_FAILED` | `MRZ_VALIDATION_FAILED` | OCR：找到了 MRZ，但未通过校验和或格式校验。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME` | `MRZ_AND_DATA_EXTRACTED_FROM_OCR_NOT_SAME` | OCR：MRZ 中的身份字段与从 VIZ 提取的字段不一致。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_BARCODE_NOT_DETECTED` | `BARCODE_NOT_DETECTED` | OCR：证件需要条形码，但未找到。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_BARCODE_VALIDATION_FAILED` | `BARCODE_VALIDATION_FAILED` | OCR：条形码已解码，但未通过跨字段校验。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_QR_NOT_DETECTED` | `QR_NOT_DETECTED` | OCR：证件需要二维码，但未找到。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_QR_VALIDATION_FAILED` | `QR_VALIDATION_FAILED` | OCR：二维码已解码，但未通过跨字段校验。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_DATA_INCONSISTENT` | `DATA_INCONSISTENT` | OCR：VIZ 字段的内部一致性检查结果不一致。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_DOCUMENT_SIDES_MISMATCH` | `DOCUMENT_SIDES_MISMATCH` | OCR：证件正面和背面的 document\_name 无法配对为同一证件。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_NAME_NOT_DETECTED` | `NAME_NOT_DETECTED` | OCR：无法从证件中提取名字和姓氏。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_DATE_OF_BIRTH_NOT_DETECTED` | `DATE_OF_BIRTH_NOT_DETECTED` | OCR：证件类别需要出生日期，但未提取到。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_EXPIRATION_DATE_NOT_DETECTED` | `EXPIRATION_DATE_NOT_DETECTED` | OCR：证件类别需要到期日，但未提取到。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_DOCUMENT_NUMBER_NOT_DETECTED` | `DOCUMENT_NUMBER_NOT_DETECTED` | OCR：证件号和个人编号均无法提取。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_COULD_NOT_DETECT_DOCUMENT_TYPE` | `COULD_NOT_DETECT_DOCUMENT_TYPE` | OCR：分类器无法将采集结果映射到受支持的证件类型。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_PORTRAIT_IMAGE_NOT_DETECTED` | `PORTRAIT_IMAGE_NOT_DETECTED` | OCR：流程无法在证件上定位人像区域。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_UNPARSED_ADDRESS` | `UNPARSED_ADDRESS` | OCR：证件上有地址字段，但地理编码或解析没有返回任何可用结果。 |
| DATABASE\_VALIDATION | `expected_details.first_name` | `SANDBOX_DB_NO_MATCH` | `DATABASE_VALIDATION_NO_MATCH` | 数据库核验：每个政府或登记机构服务都返回 NO\_MATCH，会话在数据库核验环节被拒绝。 |
| FACEMATCH | `expected_details.first_name` | `SANDBOX_FACE_LOW_MATCH` | `LOW_FACE_MATCH_SIMILARITY` | 人脸比对：相似度分数低于拒绝阈值，会话在人脸比对环节被拒绝。 |
| PHONE | `phone_number` | `+15005550009` | `HIGH_RISK_PHONE_NUMBER` | 电话：保留的无法送达或高风险测试号码，电话验证模拟返回失败。 |
| EMAIL | `user_email` | `sandbox+breach@didit.test` | `BREACHED_EMAIL_DETECTED` | 邮箱：泄露数据库对该地址返回命中结果。 |
| LOCATION | `expected_details.first_name` | `SANDBOX_IP_BLOCKLIST` | `IP_ADDRESS_IN_BLOCKLIST` | IP 分析：调用方 IP 出现在应用黑名单中，会话在 IP 分析环节被拒绝。 |
| PROOF\_OF\_ADDRESS | `expected_details.first_name` | `SANDBOX_POA_ADDRESS_MISMATCH` | `ADDRESS_MISMATCH_WITH_PROVIDED` | 地址证明：上传证件上的地址与客户在 `expected_details` 中预填的地址不一致，会话在地址证明环节被拒绝。 |
| LIVENESS | `expected_details.first_name` | `SANDBOX_LIVENESS_ATTACK` | `LIVENESS_FACE_ATTACK` | 活体检测：被动活体模拟检测到人脸攻击信号，会话在活体检测环节被拒绝。 |
| NFC | `expected_details.first_name` | `SANDBOX_NFC_CHIP_NOT_VERIFIED` | `NFC_CHIP_NOT_VERIFIED` | NFC：ePassport 模拟返回的芯片，其 SOD/DG 签名未通过完整性校验。生产环境会输出 NFC\_CHIP\_NOT\_VERIFIED，并在工作流因芯片未验证而拒绝时拒绝会话。 |
| NFC | `expected_details.first_name` | `SANDBOX_NFC_TRUST_ANCHOR_MISSING` | `NFC_TRUST_ANCHOR_MISSING` | NFC：DSC 链指向一个不在信任库中的 CSCA。生产环境会输出 NFC\_TRUST\_ANCHOR\_MISSING，并执行已配置的未验证芯片操作。 |
| DOCUMENT\_AI | `expected_details.first_name` | `SANDBOX_DOCUMENT_AI_UNREADABLE` | `DOCUMENT_AI_UNREADABLE_DOCUMENT` | Document AI：提取没有返回任何可读字段。生产环境会输出 DOCUMENT\_AI\_UNREADABLE\_DOCUMENT，重试次数用尽后，证件状态结算为已拒绝。 |
| AML | `user_email` | `sandbox+aml_review@didit.test` | `POSSIBLE_MATCH_FOUND` | AML：可能匹配，需要人工核实，会话进入审核中。 |
| FACEMATCH | `expected_details.first_name` | `SANDBOX_FACE_BORDERLINE_MATCH` | `LOW_FACE_MATCH_SIMILARITY` | 人脸比对：相似度处于审核阈值与拒绝阈值之间的边界区间，会话进入审核中。 |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_BORDERLINE_AGE` | `MINIMUM_AGE_NOT_MET` | OCR：出生日期低于最低年龄，但工作流对最低年龄未达标的情况进行审核而非拒绝，会话进入审核中。 |
| DATABASE\_VALIDATION | `expected_details.first_name` | `SANDBOX_DB_PARTIAL_MATCH` | `DATABASE_VALIDATION_PARTIAL_MATCH` | 数据库核验：身份匹配，但姓名和出生日期仅部分匹配，会话进入审核中。 |
| PROOF\_OF\_ADDRESS | `expected_details.first_name` | `SANDBOX_POA_PARTIAL_MATCH` | `ADDRESS_MISMATCH_WITH_PROVIDED` | 地址证明：上传证件上的地址与提供的地址部分匹配，会话进入审核中。 |
| KYB\_REGISTRY | `expected_details.registration_number` | `SANDBOX-KYB-MISMATCH` | `KYB_REGISTRY_DATA_MISMATCH_WITH_EXPECTED` | KYB：公司登记中的企业数据与预期信息不一致，会话被拒绝。 |
| KYB\_REGISTRY | `expected_details.registration_number` | `SANDBOX-KYB-REVIEW` | `KYB_REGISTRY_DATA_PARTIAL_MATCH_WITH_EXPECTED` | KYB：公司登记中的公司数据与预期详情部分匹配，会话进入审核中。 |

## 非证件查询与身份钱包

沙盒应用可以启用国家目录中的每一种身份钱包，包括在生产环境应用中不可用的钱包。
默认的 `approve` 场景模拟已验证的身份，无需登录外部钱包。
结果会存入会话，并按工作流的常规下一步继续。
沙盒不会验证真实的钱包凭证，模拟尝试也不收费。

查询请求仍会校验所填字段，并采用已配置的尝试次数上限，默认为一次。
请使用以下场景测试重试和回退规则：

| 场景 | 模拟结果 |
| - | - |
| `lookup_partial_match` | 登记机构部分匹配；计入尝试次数上限。 |
| `lookup_no_match` | 登记机构返回无匹配结果；计入尝试次数上限。 |
| `lookup_provider_error` | 登记机构没有返回结果；系统提示用户重试，连续三次无应答后，适用服务商错误回退规则。 |
| `wallet_cancelled` | 用户取消钱包登录。 |
| `wallet_timeout` | 钱包登录超时。 |
| `wallet_provider_error` | 钱包无法完成登录。 |

钱包失败时，将按国家设置回退到证件拍摄或直接拒绝。
已完成的查询尝试会分别记录，因此在回退之前，允许的两次尝试会产生两条结果。
所有沙盒用量仍然免费。

## 银行验证账户核验

沙盒中的银行验证步骤在每个国家都提供[两种方式](https://docs.didit.me/zh/core-technology/bank-verification/verification-methods.md)，顺序与该步骤的设置一致，使用模拟服务，不涉及真实银行，也不产生费用。
默认顺序下，用户先进入银行登录，并选择 **输入您的账户信息** 来运行[账户核验](https://docs.didit.me/zh/core-technology/bank-verification/account-check.md)；将 `bank_method_order` 设置为 `["account_lookup", "open_banking"]` 则从账户核验开始。
输入账户的末四位字符决定结果；各种结尾以及每种格式的有效账户，请参阅[在沙盒中测试](https://docs.didit.me/zh/core-technology/bank-verification/account-check.md#在沙盒中测试)。
[账户核验 API](https://docs.didit.me/zh/standalone-apis/bank-account-check.md) 根据相同的末四位返回相应的沙盒密钥。

## 示例证件

> [!WARNING]
>
> 切勿在沙盒中上传真实身份证件。请改用内置的示例证件。

托管流程提供三份示例证件，每种证件类型一份：

| 国家 | 证件类型 |
| - | - |
| 西班牙 | 身份证 |
| 阿根廷 | 护照 |
| 波兰 | 驾照 |

在证件条中点击示例，即可代替真实采集使用它。提取的数据和会话结果始终来自所选场景或魔法值，因此任何示例都可用于任何场景。

## 测试示例身份与 `expected_details`

沙盒中的每份证件都由同一个**固定**身份伪造而成。
提取的值在每次会话中都相同，且从不由当前日期推算，因此你可以用它们预填 `expected_details`，并端到端地检查字段比对和警告处理。
传入以下值时，比对检查会通过；传入其他任何值时，对应的 `*_MISMATCH_WITH_PROVIDED` 警告会被有意触发。

所有示例证件共用以下日期：

| 字段 | 值 |
| - | - |
| `date_of_birth` | `1990-06-15` |
| 签发日期 | `2025-06-15` |
| 有效期截止日期 | `2035-06-15` |

其余身份信息取决于你使用的证件：

| `sandbox_sample` | 证件 | `first_name` | `last_name` | `gender` | `nationality` | `id_country` | `identification_number` |
| - | - | - | - | - | - | - | - |
| *（默认，未选择示例）* | 巴西电子护照 | `GUSTAVO` | `MATSUMOTO` | `M` | `BRA` | `BRA` | `YC531181` |
| `spain-id-card-2021` | 西班牙身份证 | `CARMEN` | `ESPAÑOLA ESPAÑOLA` | `F` | `ESP` | `ESP` | `CAA000000` |
| `argentina-passport-2009` | 阿根廷护照 | `MARIA VICTORIA` | `VILLAREAL` | `F` | `ARG` | `ARG` | `AAF794880` |
| `poland-driving-license-2019` | 波兰驾照 | `JAN` | `KOWALSKI` | 此证件上没有该字段 | `POL` | `POL` | `SP005/89/5` |

```json theme={null}
{
  "workflow_id": "<your-workflow-uuid>",
  "expected_details": {
    "date_of_birth": "1990-06-15",
    "gender": "M",
    "nationality": "BRA",
    "identification_number": "YC531181"
  }
}
```

`first_name` 和 `last_name` 是例外：沙盒会把你在 `expected_details` 中发送的姓名回显到伪造的证件上，因此姓名比对可以通过任何姓名。
使用 `SANDBOX_OCR_FULL_NAME_MISMATCH_WITH_PROVIDED` 魔法值来测试姓名不匹配，使用 `SANDBOX_OCR_DOB_MISMATCH_WITH_PROVIDED` 来测试出生日期不匹配。

> [!NOTE]
>
> 如果你的工作流将 `documents_allowed` 限制为排除巴西电子护照的国家或证件类型，沙盒会改为从第一个允许的国家伪造证件，因此 `nationality`、`id_country` 和证件名称都将跟随该国家。上述日期保持不变。

### 伪造证件不包含的信息

伪造证件是所允许国家和证件类型的通用模板，因此只有真实的国家签发证件才会提供的字段保持为空。其中对路由影响最大的是**地区 / 美国州**（`kyc.region`）：沙盒中的美国驾照或身份证没有州信息，因此 `Region equals IL` 这类分支条件在沙盒中永远无法匹配。如果该节点的其他分支也不匹配，流程会走该节点的回退分支（即没有条件的分支）。这并不是审核，也不是分支失败，只是条件无从读取。缺失字段同样会使 `not equals` 和 `not in` 判定失败；关于缺失字段与空字符串的区别，请参阅[分支条件如何读取会话数据](https://docs.didit.me/zh/console/workflows.md#分支条件如何读取会话数据)。

要在沙盒中测试依赖州信息的流程，请从你可控的会话值而非证件出发来决定分支：

* 创建会话时在 `metadata` 中发送州信息（例如 `"metadata": {"credential_state": "IL"}`），并基于 `metadata.credential_state` 分支。元数据在沙盒中的求值方式与生产环境完全相同。
* 如果你还需要由证件证明州信息，请在生产流量中保留基于证件的 `kyc.region` 条件；在生产模式下，地区会从真实证件中读取。

路由的其他所有方面都与生产环境相同：工作流图、分支条件、`metadata` 和 `expected_details` 的值、操作节点与状态节点都会真实运行。沙盒本身不会强制将会话转入审核：只有工作流将其路由到审核时，沙盒会话才会进入审核中（即触发 `review_*` 场景、映射到审核操作的魔法值，或图中存在状态节点）。

## OTP 验证码与邮件选择加入

* **电话 OTP：**沙盒电话验证接受任何格式正确的验证码（4 到 8 位数字，包括 `123456`）。格式错误的输入仍返回 `400`。
* **邮箱 OTP：**沙盒邮箱验证接受任何格式正确的验证码（包括 `123456`）。格式错误的输入仍返回 `400`。

默认情况下，沙盒会话**不会**发送邮件（包括审核提醒、状态变更、OTP、重新提交、新会话链接等）。如需仍然发送邮件，可对收件人地址使用以下任一选择加入模式：

| 选择加入模式 | 示例 |
| - | - |
| `<local>+sandbox@<domain>` | `alice+sandbox@example.com` |
| `*@didit.test` | `alice@didit.test` |

## 控制沙盒结果：场景、魔法值、预设与模拟 endpoint

有五种方式可以控制沙盒结果。

### 1. 创建会话时设置 `sandbox_scenario`

在创建会话（`POST /v3/session/`）时设置场景 slug。创建流程会把该场景捆绑的魔法值展开到会话的输入字段中，使模拟的供应商通过真实的工作流管道输出预期结果。

```json theme={null}
{
  "workflow_id": "<your-workflow-uuid>",
  "sandbox_scenario": "decline_document_expired"
}
```

### 2. 手动输入魔法值

自行设置各个输入字段（例如 `expected_details.first_name = "SANDBOX_OCR_DOCUMENT_EXPIRED"`，或 `user_email = "sandbox+aml_hit@didit.test"`）。完整的注册表见上方的魔法值表格。

### 3. 在流程开始前预设（或重新预设）会话

对已存在且尚未超过 `Not Started` 状态的会话，设置或替换预设场景。这是[流程前场景选择器](#托管验证流程中的沙盒)调用的接口，您也可以自行调用，以构建自定义选择器或测试工具：

```
POST /v3/session/{session_id}/sandbox/arm/
Session-Token: <session_token>
```

```json theme={null}
{
  "scenario": "decline_document_expired"
}
```

200 响应：

```json theme={null}
{
  "session_id": "<session_id>",
  "scenario": "decline_document_expired",
  "status": "Not Started"
}
```

* 使用会话自身的 `Session-Token` 进行身份验证（即会话创建返回的 `session_token`），而不是应用 API 密钥：这是面向客户端的 endpoint。
* 对未知场景 slug 返回 `400`；会话超过 `Not Started` 状态后同样返回 `400`，并附带消息 `"Scenario can only be armed before the verification starts."`。
* 当 `Session-Token` 属于生产（非沙盒）应用，或与路径中的 `session_id` 不匹配时，返回 `404`。
* **重新预设会替换之前的场景。** 它只清除先前场景写入的魔法值，保留用户已手动输入的内容（邮箱、名字等）。例如：先预设 `decline_phone_high_risk`，再预设 `approve`，电话号码会恢复为未设置，但客户输入的邮箱在两次调用后都会保留。

### 4. 模拟接口

事后强制设置终态：

* 控制台 / API 密钥（需要 `write:sessions` 权限；限定范围的 API 密钥需要会话的写入权限）：`POST /v3/session/{session_id}/simulate/`
* 托管流程（`Session-Token` 请求头）：`POST /v3/session/{session_id}/sandbox/simulate/`

两者均适用于 KYC 和 KYB 沙盒会话。
生产（非沙盒）会话返回 `404`，`Session-Token` 与路径中的 `session_id` 不匹配时同样如此。

请求体中的每个字段都是可选的，但 `new_status`、`scenario`、`risk` 至少需要提供一个：

| 字段 | 类型 | 作用 |
| - | - | - |
| `new_status` | string | `Approved`、`Declined`、`In Review`、`Expired` 或 `Abandoned`。省略时，默认使用风险案件的默认状态（即 `risk`，或场景魔法值携带的第一个风险），否则使用场景的默认状态。 |
| `scenario` | string，最多 100 个字符 | 场景 slug。若未提供 `risk`，则应用其魔法值携带的第一个风险。 |
| `risk` | string，最多 128 个字符 | 风险案件。其警告会添加到会话中。 |
| `node_id` | string，最多 128 个字符 | 附加警告的工作流节点。默认为会话当前节点。 |
| `comment` | string，最多 500 个字符 | 添加到状态变更所记录的活动条目中的备注。 |
| `send_webhooks` | boolean，默认 `true` | 状态变更是否发送 Webhook。 |

`send_webhooks: true` 会发送真实状态变更所发送的完全相同的 Webhook：`status.updated`，以及该变更引起的 `user.status.updated` 和 `user.data.updated` 事件（若为企业会话，则为 `business.status.updated` 和 `business.data.updated`），发送给订阅这些事件的目标。
当 `send_webhooks: false` 时，状态仍会变更，但不会记录或发送任何 Webhook。
漏发 Webhook 的恢复机制永远不会重新发送该决定，控制台仍会实时更新。
会话的下一次状态变更将照常发送 Webhook。

```json theme={null}
{
  "scenario": "decline_aml_hit",
  "send_webhooks": false
}
```

200 响应：

```json theme={null}
{
  "session_id": "<session_id>",
  "previous_status": "In Progress",
  "status": "Declined",
  "scenario": "decline_aml_hit",
  "risk": "POSSIBLE_MATCH_FOUND",
  "send_webhooks": false
}
```

* `status` 为模拟设置的状态，`previous_status` 为会话之前的状态。
* `scenario` 回显您提供的 slug，`risk` 为所应用的风险案件；若无则为 `null`。
* `send_webhooks` 回显所应用的值。
* 对未知的场景或风险、无法推导出 `new_status`、`new_status` 为其他任何值，以及会话已处于所请求状态（`["The new status is the same as the current status."]`）时，返回 `400`。

### 5. 独立 AML API 上的 `sandbox_scenario`

后端 AML 筛查没有终端用户，也没有可预设的会话，因此场景 slug 需放在筛查请求本身中（`POST /v3/aml/`）：

```json theme={null}
{
  "full_name": "Ada Lovelace",
  "save_api_request": true,
  "sandbox_scenario": "decline_aml_hit"
}
```

* `decline_aml_hit` 返回一条符合生产格式的命中结果，并将筛查结论设为 `Declined`；`review_aml_possible_match` 则将其设为 `In Review`。未预设 AML 结果的场景会使筛查保持无命中。
* 当 `save_api_request: true`（默认值）时，筛查会像使用正式密钥一样完整保存，因此返回的 `request_id` 可通过 `GET /v3/session/{request_id}/decision/` 和 `GET /v3/session/{request_id}/generate-pdf/` 查询。
* 对未知场景 slug 返回 `400`；当 API 密钥属于生产应用时，同样返回 `400`。

## `sandbox_scenario` 出现的位置

已预设的场景 slug（若未预设则为 `null`）会在部分接口中回显，其他接口则有意省略：

| 接口 | 是否返回？ |
| - | - |
| 当前步骤 payload（KYC 和 KYB） | 是，以 `sandbox_scenario` 返回 |
| `GET /v1/session/{id}/decision/` | 是，以 `sandbox_scenario` 返回 |
| `GET /v3/session/{id}/decision/`（参见[获取会话](https://docs.didit.me/zh/sessions-api/retrieve-session.md)） | 是，顶层 `sandbox_scenario`（未预设时为 null） |
| 会话 Webhook（`status.updated`、`data.updated`） | 是，以 `sandbox_scenario` 返回；生产会话或未预设的沙盒会话中为 `null` |

如果您的集成需要在服务端根据已预设的场景进行分支判断，请从 Webhook 信封或任一决策接口（`v1` 或 `v3`）读取该值。

## 用量与计费

沙盒用量会被记录，但绝不收费：

* 每次模拟调用仍会记录一条用量事件（`is_sandbox: true`，单价为 `0.0000`），以便在调试时查看沙盒用量。但这些事件永远不会计入您的用量总计，永远不会从余额中扣除，也永远不会出现在发票上。
* Business Console 的余额、充值和生产环境用量页面默认会过滤掉沙盒用量。充值用量接口接受 `environment` 参数（`live` 为默认值，`sandbox` 为沙盒），以便单独查看沙盒用量。这些查看操作始终为零成本，且不占用免费额度。
* 邮箱验证和电话验证按请求而非按会话结果计费，而沙盒无法在 Webhook 层完全模拟这一计费方式。沙盒中的邮箱和电话验证检查仍会为每个请求记录一条用量事件，单价为 `0.0000`，与其他所有模拟功能相同。这是已知且已被接受的、与生产环境计费行为之间的差异，并非缺陷。
* 数据库核验在沙盒中的响应结构有意不同于生产环境中按服务商区分的结构，因为沙盒中没有真实的服务商可供模拟。请将沙盒中的数据库核验 payload 视为结果（状态和警告代码）的替代，而非真实服务商响应的逐字节预览。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.