> ## 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 샌드박스에서 결과가 일정한 테스트를 하는 방법을 안내합니다. 시나리오, 매직 값, arm endpoint, 샘플 문서, OTP 코드를 다루며 개인정보 처리와 과금은 없습니다.

Didit의 **샌드박스**는 Stripe의 테스트 카드나 Sumsub의 문서 템플릿과 같은 역할을 합니다. 실제 제공업체를 호출하거나 실제 개인정보를 사용하거나 잔액에 영향을 주지 않고 원하는 인증 결과를 결정적으로 재현할 수 있습니다.

> [!NOTE]
>
> 이 데이터의 기준 원본은 verification 서비스의 `serialize_catalog()`이며, `GET /v1/sandbox/scenarios/` 엔드포인트에서 실시간으로 제공됩니다. `service-didit-verification` 저장소의 `docs/sandbox/SANDBOX_REFERENCE.md`는 `pipenv run python scripts/generate_sandbox_reference.py`로 드리프트 테스트를 거친 사본이 생성됩니다. 이 페이지는 해당 참조 문서를 그대로 옮긴 것이므로, 내용이 오래된 것 같으면 항상 실시간 카탈로그 엔드포인트를 기준으로 하십시오. 카탈로그 엔드포인트는 인증이 필요합니다. 다른 호출과 같은 방식으로 API 키(`x-api-key`)를 보내거나, 실행 중인 세션 안에서 조회하는 경우 세션 토큰을 사용하십시오.

## 환경 모델

샌드박스는 **애플리케이션별 모드**(`ApplicationDetail.mode == SANDBOX`)이며, Stripe의 테스트 모드와 같은 개념입니다. 샌드박스 애플리케이션에서 세션이 실행되는 동안에는 다음이 적용됩니다.

* 모든 외부 제공업체(OCR, AML, 얼굴 대조, 라이브니스, NFC, 전화, 이메일, IP, 주소 증명, 데이터베이스 검증, 문서 AI)가 **모의(mock)** 처리됩니다. 어떤 제3자도 호출되지 않습니다.
* **라이브니스는 실제 환경과 똑같이 동작합니다.** 능동형 라이브니스 워크플로는 호스팅 흐름과 네이티브 SDK의 웹 핸드오프 모두에서 샌드박스에서도 실제 능동형 캡처를 실행하므로, 사용자가 실제로 겪게 될 경험을 테스트할 수 있습니다. 라이브니스 *결과*만 시나리오 또는 매직 값으로 시뮬레이션되며, 보고서에는 워크플로에 설정된 방식이 기록됩니다.
* **커스텀 도메인은 라이브 환경에서만 사용할 수 있습니다.** 샌드박스 애플리케이션에는 [커스텀 도메인](https://docs.didit.me/ko/console/custom-domain.md)을 추가할 수 없으며, 샌드박스 세션은 항상 기본 호스팅 인증 도메인에서 실행됩니다.
* 세션은 **청구되지 않으며**, 잔액 확인도 건너뜁니다.
* 애플리케이션별로 24시간 동안 세션 생성 500회 할당량이 적용됩니다.
* webhook에는 `"environment": "sandbox"`가 포함됩니다(라이브 세션의 webhook에는 `"environment": "live"`가 포함됩니다).
* 호스팅 흐름은 세션 환경 정보를 노출하므로, UI에서 사용자에게 실제 개인정보를 업로드하지 말라고 경고할 수 있습니다.
* **결과는 픽셀에 의존하지 않습니다.** 추출된 데이터와 상태는 시나리오 또는 매직 값에서 가져옵니다. 캡처된 미디어 자체(문서, 셀피, 영상, 주소 증명 파일)는 라이브 세션과 똑같이 실제로 저장됩니다.

## 샌드박스의 거래 모니터링

샌드박스는 **세션 형태**로 동작하며, 검증 세션이 호출하는 제공업체를 모의합니다. 거래 모니터링에는 모의할 제공업체가 없으므로, 샌드박스 애플리케이션은 다른 방식을 택합니다. 거래는 저장되지만, 그 거래에 대해 판단, 스크리닝, 알림, 청구를 수행하는 처리는 **아무것도 실행되지 않습니다**.

샌드박스 애플리케이션에서 생성된 거래는 평소와 같이 저장되고 응답으로 반환되지만, 다음 사항은 발생하지 않습니다.

* **규칙 평가 없음.** 규칙 모드와 관계없이 `rule_runs`는 비어 있습니다. 샌드박스 애플리케이션에서는 `ACTIVE` 규칙이 절대 발동하지 않으므로, 거래는 점수 `0`과 함께 `APPROVED` 상태를 유지하며 알림이나 케이스가 생성되지 않습니다.
* **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/ko/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/ko/transaction-monitoring/travel-rule.md#선언된-자체-보관-지갑)으로의 전송은 `UNCONFIRMED_OWNERSHIP` 상태가 되며 아무것도 전송되지 않습니다. 이 전송은 샌드박스에서 [지갑 소유권 위젯](https://docs.didit.me/ko/transaction-monitoring/wallet-ownership-widget.md)을 통해 완료할 수 있고, 대상 지갑이 이미 주소록에 있다면 [소유권 확인](https://docs.didit.me/ko/management-api/travel-rule/confirm-ownership.md)을 통해 완료할 수 있습니다. 임계값을 초과하는 그 밖의 전송은 `TEST_VASP` 채널을 통해 Didit Test VASP로 전달되며, Didit Test VASP는 수취 지갑 또는 이름으로 선택한 시나리오에 따라 Didit 내부에서 응답합니다. 전송은 완료되거나, 거부되거나, 불일치가 발생하거나, `COUNTERPARTY_VASP_NOT_REACHABLE` 상태가 되거나, 만료되거나(이후 `timeout_outcome`이 거래에 적용됨), 지갑 소유권 확인을 기다릴 수 있습니다. 모든 시나리오는 [샌드박스에서 Travel Rule 테스트](https://docs.didit.me/ko/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/ko/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` | 만료 확인이 1분 기한 이후에 실행될 때 `EXPIRED` |
| `self_hosted` | `Didit Test VASP Self Hosted` | `UNCONFIRMED_OWNERSHIP` |

수취인 이름 대신 수취인 지갑 주소로 시나리오를 선택할 수도 있습니다(둘 다 설정된 경우 지갑이 우선합니다). 이름도 지갑도 지정하지 않은 전송은 `accept`로 완료됩니다. [샌드박스에서 Travel Rule 테스트](https://docs.didit.me/ko/transaction-monitoring/travel-rule-sandbox.md)에서 각 시나리오의 지갑과 정확한 결과를 확인할 수 있습니다.

[수신 Test VASP 트리거](https://docs.didit.me/ko/management-api/travel-rule/trigger-test-vasp-inbound.md)는 `scenario` 필드에서 `accept`와 `name_mismatch`만 허용합니다.

> [!NOTE]
>
> **라이브 전환 전에 규칙을 검증하는 방법.** [Backtest](https://docs.didit.me/ko/transaction-monitoring/rules-api.md#생성-백테스트-활성화)를 사용하십시오. 이 기능은 규칙을 실제 거래 내역에 다시 적용하고, 어떤 거래의 상태도 변경하지 않은 채 `evaluated` / `matched` / `affected_entities`를 보고합니다. 라이브 애플리케이션에서 `TEST` 모드와 함께 사용하십시오. `TEST` 규칙은 새로 들어오는 모든 라이브 거래를 평가하고, 일치했을 내용을 점수나 상태를 변경하지 않고 기록합니다. 선별성이 적절하다고 확인되면 `ACTIVE`로 전환하십시오.

## 라이브와 샌드박스 전환 방법

라이브와 샌드박스는 **별도의 애플리케이션**이므로 테스트 트래픽과 프로덕션 데이터가 섞이지 않습니다. 각 애플리케이션에는 `live` 또는 `sandbox`의 `mode`가 있습니다.

* **샌드박스 모드** 애플리케이션의 API 키를 사용하면 모의 세션을 얻을 수 있습니다. 이 세션은 과금되지 않으며 시뮬레이션할 수 있습니다.
* **라이브 모드** 애플리케이션의 API 키는 실제 인증에 사용합니다.

`sandbox_scenario` 필드(세션 생성 시 및 단독 AML API에서 사용)와 시뮬레이션 엔드포인트는 샌드박스 애플리케이션에서만 허용됩니다. 라이브 애플리케이션에서는 거부됩니다.

## 호스팅 인증 흐름의 샌드박스

샌드박스 애플리케이션에서 생성한 세션은 호스팅 인증 흐름(`POST /v3/session/`이 반환하는 `url`)에 추가 UI를 표시합니다.

* **카드 내 샌드박스 배너.** 모든 화면의 카드 안에 "테스트 데이터만 사용 - 실제 호출 없음" 배너가 표시됩니다. 따라서 이 세션이 실제 제공업체를 호출하지 않고 잔액에도 영향을 주지 않는다는 점이 항상 분명합니다. 캡처된 미디어는 일반 세션과 같은 방식으로 저장되므로, 실제 신분증이나 개인정보 대신 샘플 문서와 테스트 데이터를 사용하십시오.
* **흐름 시작 전 시나리오 선택기.** 캡처를 시작하기 전에 카드 내 선택기가 사용자가 테스트 결과를 고르도록 안내합니다. 먼저 결과를 선택하고, 그 결과에 대한 트리거를 고릅니다. 확인하면 `POST /v3/session/{session_id}/sandbox/arm/`이 호출되고([흐름 시작 전 세션 설정(또는 재설정)](#3-플로우-시작-전-시나리오-사전-설정-또는-재설정) 참조), 해당 시나리오의 모의 값이 이미 적용된 상태로 흐름이 이어집니다.
* **샘플 문서 스트립.** 문서 업로드/캡처 컴포넌트 바로 아래에 샌드박스 샘플 문서가 한 줄로 최대 3개까지 표시됩니다. 하나를 탭하면 실제 업로드 대신 그 문서를 사용합니다.

샌드박스 세션은 **실제** 캡처 미디어를 저장합니다. 문서, 셀피, 라이브니스 영상, 주소 증명 파일은 라이브 세션과 똑같이 업로드되고 보존됩니다. 반면 *추출된* 데이터(신분증 인증 필드, 얼굴 대조 점수, 라이브니스 결과 등)와 세션 결과 자체(승인됨 / 검토 중 / 거부됨)는 시나리오 또는 모의 값에 의해 시뮬레이션되며, 캡처된 미디어에서 도출되지 않습니다. Business Console은 샌드박스 세션의 추출 데이터 섹션에 **시뮬레이션 데이터** 칩을 표시하므로, 검토자는 세션의 어느 부분이 실제 캡처이고 어느 부분이 시나리오에 따른 결과인지 한눈에 구분할 수 있습니다.

## 시나리오

각 시나리오는 이름이 지정된 매직 값 묶음입니다. 결과를 재현하려면 세션 생성 시(또는 시뮬레이션 endpoint에) 해당 슬러그를 `sandbox_scenario`로 전달하세요.

| 슬러그 | 제목 | 기본 상태 | 설명 |
| - | - | - | - |
| `approve` | 정상 경로 — 모든 기능 승인 | 승인됨 | 기본 샌드박스 동작입니다. 모의 기능이 모두 깨끗한 APPROVED 결과를 반환합니다. 세션 상태는 APPROVED로 끝납니다. |
| `decline_aml_hit` | AML — 정치적 주요 인물(PEP) | 거부됨 | AML 모의 기능이 일치 항목 하나를 반환하고, 세션은 AML 기능에서 거부됩니다. 이 일치 항목은 실제 AML 제공업체와 같은 [Hit 스키마](https://docs.didit.me/ko/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 분석 모의 기능이 IP\_ADDRESS\_IN\_BLOCKLIST로 위치를 거부합니다. 세션은 IP 분석에서 거부됩니다. |
| `decline_poa_address_mismatch` | 주소 증명(POA) — 제공된 주소와 불일치 | 거부됨 | POA 모의 기능이 ADDRESS\_MISMATCH\_WITH\_PROVIDED로 주소 증명을 거부합니다. 세션은 POA에서 거부됩니다. |
| `decline_liveness_attack` | 라이브니스 — 얼굴 공격 감지 | 거부됨 | 수동형 라이브니스 모의 기능이 얼굴 공격 신호를 감지하고 LIVENESS\_FACE\_ATTACK을 발생시킵니다. 세션은 라이브니스에서 거부됩니다. |
| `decline_nfc_chip_not_verified` | NFC — ePassport 칩 검증 실패 | 거부됨 | ePassport 모의 기능이 SOD/DG 서명의 무결성 검사에 실패하는 칩을 반환합니다. 프로덕션은 NFC\_CHIP\_NOT\_VERIFIED를 발생시키며, 워크플로가 검증되지 않은 칩에서 거부하도록 설정된 경우 세션은 NFC에서 거부됩니다. |
| `decline_nfc_trust_anchor_missing` | NFC — 발급기관의 신뢰 앵커를 사용할 수 없음 | 거부됨 | ePassport 모의 기능이 발급 CSCA가 신뢰 저장소에 없는 DSC로 서명된 칩을 반환합니다. 프로덕션은 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은 DECLINE이 아닌 REVIEW로 설정되므로, 세션은 거부되지 않고 신분증 인증에서 검토 중 상태가 됩니다. |
| `review_poa_partial_match` | 주소 증명(POA) — 부분 주소 일치 | 검토 중 | POA 모의 기능이 제공된 주소와 부분적으로만 일치하는 주소를 반환합니다. 세션은 주소 증명에서 검토 중 상태가 됩니다. |
| `review_database_partial_match` | 데이터베이스 검증 — 부분 일치 | 검토 중 | 데이터베이스 서비스가 이름과 생년월일에 대해 부분 일치를 반환합니다. 세션은 데이터베이스 검증에서 검토 중 상태가 됩니다. |
| `decline_kyb_registry_mismatch` | KYB - 법인 등기부 데이터 불일치 | 거부됨 | 법인 등기부 모의 기능이 예상 세부 정보와 등록 데이터가 일치하지 않는 기업을 반환합니다. 기업 세션은 법인 등기부 검사에서 거부됩니다. |
| `review_kyb_registry_partial` | KYB - 법인 등기부 데이터 부분 일치 | 검토 중 | 법인 등기부 모의 기능이 이름만 부분적으로 일치하는 기업을 반환합니다. 기업 세션은 법인 등기부 검사에서 검토 중 상태가 됩니다. |

`review_*` 시나리오는 거부 수준의 입력 없이도 수동 검토 UX(콘솔 검토 대기열, `In Review` 웹훅, 검토자의 승인/거부 작업)를 테스트할 수 있도록 존재합니다. 위의 두 `kyb_*` 시나리오는 `expected_details.first_name` 대신 `expected_details.registration_number`를 설정합니다. 이들은 기업 확인(KYB) 세션에만 적용됩니다.

## 매직 값

매직 값은 하나의 모의 기능이 특정 위험을 발생시키도록 하는 특정 입력값입니다. 입력 필드를 매직 값으로 설정하면(직접 설정하거나 시나리오를 통해) 해당 위험이 트리거됩니다.

| 기능 | 입력 필드 | 매직 값 | 위험 | 설명 |
| - | - | - | - | - |
| AML | `user_email` | `sandbox+aml_hit@didit.test` | `POSSIBLE_MATCH_FOUND` | AML: 정치적 주요 인물(PEP). AML 모의 기능이 일치 결과 1건을 반환하며, 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: 문서에 QR 코드가 필요하지만 찾지 못했습니다. |
| ID\_VERIFICATION | `expected_details.first_name` | `SANDBOX_OCR_QR_VALIDATION_FAILED` | `QR_VALIDATION_FAILED` | OCR: QR 코드는 디코딩되었지만 필드 간 교차 검증에 실패했습니다. |
| 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` | 주소 증명(POA): 업로드한 문서의 주소가 고객이 `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: 전자여권 모의 기능이 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` | 주소 증명(POA): 업로드한 문서의 주소가 제공된 주소와 부분적으로 일치합니다. 세션이 검토 중 상태가 됩니다. |
| 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` 시나리오는 외부 지갑에 로그인하지 않고 검증된 신원을 시뮬레이션합니다.
결과는 세션에 저장되며 워크플로의 일반적인 다음 단계를 따릅니다.
샌드박스는 실제 지갑 자격 증명을 검증하지 않으며, 시뮬레이션된 시도에 대해 요금을 부과하지 않습니다.

조회 요청은 여전히 입력된 필드를 검증하며, 기본값이 1인 설정된 시도 한도를 적용합니다.
재시도와 대체 규칙을 테스트하려면 다음 시나리오를 사용할 수 있습니다.

| 시나리오 | 시뮬레이션된 결과 |
| - | - |
| `lookup_partial_match` | 등록 기관 부분 일치 결과이며, 시도 한도에 포함됩니다. |
| `lookup_no_match` | 등록 기관이 일치 항목 없이 응답합니다; 시도 한도에 포함됩니다. |
| `lookup_provider_error` | 등록 기관이 응답하지 않습니다. 사용자에게 다시 시도하도록 요청하며, 응답 없이 세 번 시도하면 제공자 오류 대체 규칙이 적용됩니다. |
| `wallet_cancelled` | 사용자가 지갑 로그인을 취소합니다. |
| `wallet_timeout` | 지갑 로그인이 만료됩니다. |
| `wallet_provider_error` | 지갑이 로그인을 완료할 수 없습니다. |

지갑 실패는 국가 설정에 따라 문서 촬영으로 대체되거나 거부됩니다.
완료된 조회 시도는 별도로 기록되므로, 허용된 시도가 두 번이면 대체 전에 결과가 두 개 생성됩니다.
모든 샌드박스 사용량은 계속 무료입니다.

## 은행 인증 계좌 확인

샌드박스 은행 인증 단계는 모든 국가에서 [두 가지 방식](https://docs.didit.me/ko/core-technology/bank-verification/verification-methods.md)을 제공하며, 단계에서 설정한 순서대로 진행됩니다. 실제 은행이나 비용이 없는 시뮬레이션 서비스를 사용합니다.
기본 순서에서는 사용자가 은행 로그인부터 시작하고, **계좌 정보 입력**을 선택해 [계좌 확인](https://docs.didit.me/ko/core-technology/bank-verification/account-check.md)을 실행합니다. `bank_method_order`를 `["account_lookup", "open_banking"]`로 설정하면 계좌 확인부터 시작합니다.
입력한 계좌의 마지막 네 글자가 응답을 결정합니다. 모든 끝자리와 형식별 유효한 계좌는 [샌드박스에서 테스트하기](https://docs.didit.me/ko/core-technology/bank-verification/account-check.md#샌드박스에서-테스트하기)를 참조하십시오.
[계좌 확인 API](https://docs.didit.me/ko/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/ko/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/`) 시나리오 슬러그를 설정합니다. 생성 과정에서 시나리오에 포함된 매직 값이 세션의 입력 필드로 확장되므로, 모의 제공자가 실제 워크플로 파이프라인을 거쳐 의도한 결과를 반환합니다.

```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` 상태를 지나지 않았다면, 해당 세션에 설정된 시나리오를 지정하거나 교체할 수 있습니다. 이 endpoint는 [플로우 시작 전 시나리오 선택기](#호스팅-인증-흐름의-샌드박스)가 호출하는 endpoint이며, 사용자 지정 선택기나 테스트 하네스를 직접 구성하려는 경우 직접 호출할 수 있습니다.

```
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"
}
```

* 애플리케이션 API 키가 아니라, 세션 생성 시 `session_token`으로 반환되는 세션 자체의 `Session-Token`으로 인증합니다. 이 endpoint는 클라이언트용 endpoint입니다.
* 알 수 없는 시나리오 슬러그이면 `400`을 반환하고, 세션이 `Not Started` 상태를 지난 후에는 `400`을 반환합니다(`"Scenario can only be armed before the verification starts."`).
* `Session-Token`이 라이브(샌드박스가 아닌) 애플리케이션에 속하거나 경로의 `session_id`와 일치하지 않으면 `404`를 반환합니다.
* **시나리오를 다시 설정하면 이전 시나리오가 대체됩니다.** 이전 시나리오가 기록한 매직 값만 지워지고, 사용자가 직접 입력한 값(이메일, 이름 등)은 그대로 유지됩니다. 예를 들어 `decline_phone_high_risk`를 설정한 뒤 `approve`를 설정하면 전화번호는 미설정 상태로 돌아가지만, 고객이 입력한 이메일은 두 호출 모두에서 유지됩니다.

### 4. 시뮬레이션 endpoint

사후에 최종 상태를 강제로 설정합니다.

* 콘솔 / API 키(`write:sessions` 권한 필요, 범위가 제한된 API 키에는 세션 쓰기 권한이 있어야 함): `POST /v3/session/{session_id}/simulate/`
* 호스팅 흐름(`Session-Token` 헤더): `POST /v3/session/{session_id}/sandbox/simulate/`

두 방식 모두 KYC 및 KYB 샌드박스 세션에서 사용할 수 있습니다.
라이브(샌드박스가 아닌) 세션은 `404`를 반환하며, 경로의 `session_id`와 일치하지 않는 `Session-Token`의 경우에도 마찬가지입니다.

요청 본문의 모든 필드는 선택 사항이지만, `new_status`, `scenario`, `risk` 중 하나 이상은 반드시 포함해야 합니다.

| 필드 | 유형 | 기능 |
| - | - | - |
| `new_status` | 문자열 | `Approved`, `Declined`, `In Review`, `Expired` 또는 `Abandoned`. 생략하면 위험 사례(`risk`, 또는 시나리오 매직 값에 포함된 첫 번째 위험)의 기본 상태로 설정되고, 그것도 없으면 시나리오의 기본 상태로 설정됩니다. |
| `scenario` | 문자열, 최대 100자 | 시나리오 슬러그입니다. `risk`를 지정하지 않으면 시나리오 매직 값에 포함된 첫 번째 위험이 적용됩니다. |
| `risk` | 문자열, 최대 128자 | 위험 사례입니다. 해당 경고가 세션에 추가됩니다. |
| `node_id` | 문자열, 최대 128자 | 경고가 연결될 워크플로 노드입니다. 기본값은 세션의 현재 노드입니다. |
| `comment` | 문자열, 최대 500자 | 상태 변경 시 기록되는 활동 항목에 추가되는 메모입니다. |
| `send_webhooks` | 불리언, 기본값 `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`는 요청한 슬러그를 그대로 반환하고, `risk`는 적용된 위험 사례입니다. 해당하는 값이 없으면 각각 `null`입니다.
* `send_webhooks`는 적용된 값을 반환합니다.
* 알 수 없는 시나리오나 위험 사례이거나, `new_status`를 도출할 수 없거나, 지원하지 않는 `new_status`이거나, 세션이 이미 요청된 상태를 가지고 있으면 `400`을 반환합니다(`["The new status is the same as the current status."]`).

### 5. 독립형 AML API의 `sandbox_scenario`

백엔드 AML 스크리닝에는 최종 사용자나 설정할 세션이 없으므로, 슬러그를 스크리닝 요청 자체에 포함합니다(`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/`에서 조회할 수 있습니다.
* 알 수 없는 시나리오 슬러그이면 `400`을 반환하고, API 키가 라이브 애플리케이션에 속하면 `400`을 반환합니다.

## `sandbox_scenario`가 표시되는 위치

설정된 시나리오 슬러그(설정된 시나리오가 없으면 `null`)는 일부 인터페이스에서 다시 반환되며, 다른 인터페이스에서는 의도적으로 제외됩니다.

| 인터페이스 | 노출 여부 |
| - | - |
| 현재 단계 payload(KYC 및 KYB) | 예, `sandbox_scenario`로 노출 |
| `GET /v1/session/{id}/decision/` | 예, `sandbox_scenario`로 노출 |
| `GET /v3/session/{id}/decision/` ([세션 조회](https://docs.didit.me/ko/sessions-api/retrieve-session.md) 참조) | 예, 최상위 `sandbox_scenario`로 노출(설정되지 않았으면 null) |
| 세션 webhook(`status.updated`, `data.updated`) | 예, `sandbox_scenario`로 노출. 라이브 세션이거나 설정되지 않은 샌드박스 세션이면 `null` |

서버 측에서 설정된 시나리오에 따라 분기 처리해야 한다면, webhook 엔벨로프나 두 가지 결정 endpoint(`v1` 또는 `v3`) 중 하나에서 값을 읽으세요.

## 사용량 및 결제

샌드박스 사용량은 집계되지만 청구되지 않습니다.

* 모의 호출마다 사용량 이벤트가 기록됩니다(`is_sandbox: true`, 가격 `0.0000`). 디버깅을 위해 샌드박스 사용량을 확인할 수 있도록 하기 위함이며, 사용량 합계에 더해지지 않고, 잔액에서 차감되지 않으며, 청구서에도 나타나지 않습니다.
* Business Console의 잔액, 충전, 실시간 사용량 화면은 기본적으로 샌드박스 사용량을 제외합니다. 충전 사용량 endpoint는 `environment` 파라미터(`live`가 기본값이고 `sandbox` 선택 가능)를 받으므로, 샌드박스 사용량을 따로 조회할 수 있습니다. 이때 비용은 항상 0이며 무료 요금제 사용량도 차감되지 않습니다.
* 이메일 및 전화번호 인증은 세션 결과 단위가 아니라 요청 단위로 결제됩니다. 샌드박스는 웹훅 계층에서 이를 완전히 반영할 수 없습니다. 샌드박스의 이메일/전화번호 인증도 다른 모의 기능과 마찬가지로 요청마다 가격 `0.0000`의 사용량 이벤트를 기록합니다. 이는 실제 결제 동작과의 알려진 차이로, 의도된 설계이며 버그가 아닙니다.
* 데이터베이스 검증의 샌드박스 응답 형식은 실제 제공자가 없으므로 프로덕션 환경의 제공자별 형식과 의도적으로 다릅니다. 샌드박스의 데이터베이스 검증 payload는 결과(상태 및 경고 코드)를 대신하는 값으로 취급하고, 실제 제공자 응답을 바이트 단위로 미리 보여주는 것으로 간주하지 마십시오.


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