무료
개발, 테스트 및 초기 사용자 확보에 적합합니다.
- 매월 500건의 전체 KYC 인증
- 신분증, 라이브니스, 얼굴 매칭, 기기 및 IP 확인
- 200개 이상의 사기 신호, 차단 목록, 중복 확인
- Didit 네트워크 전반에서 KYC 재사용 가능
- 워크플로우 빌더, 케이스 관리, SDK
- AI 지원 콘솔 내 AI 에이전트, 문서, 커뮤니티.
전 세계 3,000개 이상의 기관에서 신뢰합니다.
재사용 가능한 신원이 제공하는 이점
모든 Didit KYC는 서명된 재사용 가능한 KYC 인증으로 다른 Didit 기반 앱과 공유할 수 있습니다. 모든 수신 플랫폼은 이를 무료로 읽을 수 있습니다. 한 번의 인증으로 Didit을 수락하는 모든 비즈니스에서 사용 가능합니다. 지금 무료로 시작하세요.
신분증, 라이브니스, 얼굴 매칭, 제재 목록, 주소, 연령, 전화번호, 이메일, 맞춤 질문 등 원하는 검사를 선택하세요. 대시보드에서 플로우로 드래그하거나, 동일한 플로우를 API에 게시하세요. 조건에 따라 분기하고, A/B 테스트를 실행하며, 코드가 필요 없습니다.
curl -X POST https://verification.didit.me/v3/session/$SESSION_ID/share/ \
-H "x-api-key: $DIDIT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"for_application_id": "<receiving-application-id>",
"ttl_in_seconds": 3600
}'{ "share_token": "eyJ…" }curl -X POST https://verification.didit.me/v3/session/import-shared/ \
-H "x-api-key: $RECEIVING_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"share_token": "<share-token>",
"trust_review": false,
"workflow_id": "<receiving-workflow-uuid>",
"vendor_data": "user-42"
}'{ "shared_from_session": "…" }# Didit Reusable KYC: verify a person once, share the finished session with another application
You are adding Reusable KYC to my_stack. A person completes one identity
verification; the finished session is then shared with a second Didit
application, which imports the full decision without running the checks
again. Every URL, header and enum value below is canonical. Do not paraphrase
or "improve" them.
## 0. What exists, and what does not
- Reuse is server to server, between two Didit applications: the source
application mints a share token for a finished session, the receiving
application redeems it. Both sides use their own x-api-key.
- There is NO reusable_identity object on the webhook or the decision, NO
metadata.request_fields or other selective-disclosure parameter, NO
workflow setting that "accepts" a credential, and NO revocation event. Do
not invent them. metadata on a session is free-form JSON that Didit echoes
back, nothing more. The receiving application gets the whole decision.
- This is not an EU Digital Identity (EUDI) Wallet integration. Didit does
not issue or accept EUDI Wallet credentials through these calls.
## 1. Provision
- Sign up: https://business.didit.me
- Source application: its API key. Receiving application: its API key and its
application id (a UUID: shown in the console, and sent as application_id
on every webhook that application receives). They are different
applications; sharing a session with the application that owns it is
refused.
## 2. Create the workflow for the first verification
POST https://verification.didit.me/v3/workflows/
-H "x-api-key: <source-api-key>"
-H "Content-Type: application/json"
{
"workflow_label": "KYC onboarding",
"features": [
{ "feature": "OCR" },
{ "feature": "LIVENESS" },
{ "feature": "FACE_MATCH" },
{ "feature": "IP_ANALYSIS" }
]
}
OCR is the ID Verification feature (uppercase, strict: ID_VERIFICATION is
rejected). Add { "feature": "AML" } for sanctions and PEP screening; it is
priced separately. Response: 201. The workflow id is uuid (workflow_id carries
the same value) and features comes back as one string, "OCR + LIVENESS + ...".
Create the workflow once and keep the id: every call makes a new one.
Prices: https://didit.me/pricing
## 3. Create a session
POST https://verification.didit.me/v3/session/
-H "x-api-key: <source-api-key>"
-H "Content-Type: application/json"
-d '{ "workflow_id": "<uuid from step 2>", "vendor_data": "<your user id>" }'
Response: 201 with session_id, session_token, url and status "Not Started".
Redirect the user to url (hosted flow) or open it in the Web, iOS, Android,
React Native or Flutter SDK. The field is named url on this response.
One unfinished session exists per workflow_id and vendor_data pair: calling
create again with the same pair answers 201 again with that same session.
Optional: callback, a URL the user's browser is sent back to when the flow
ends. It is a browser redirect, not a webhook: never trust its query string.
## 4. Webhook
Register a destination in the console (API & Webhooks), or over the API:
POST https://verification.didit.me/v3/webhook/destinations/
-H "x-api-key: <your-api-key>"
-H "Content-Type: application/json"
-d '{
"label": "Verification webhooks",
"url": "https://<your-public-host>/webhooks/didit",
"webhook_version": "v3",
"subscribed_events": ["status.updated", "data.updated"]
}'
label, url and subscribed_events are required. url must be a public HTTPS
address: Didit does not deliver to localhost or private addresses. Response:
201 with uuid and secret_shared_key. Store secret_shared_key as the webhook
secret (DIDIT_WEBHOOK_SECRET); it is unique to this destination. Remove a
destination with DELETE /v3/webhook/destinations/{uuid}/ (204).
What arrives:
- webhook_type is "status.updated" (the session changed status) or
"data.updated" (verification data was corrected after the fact)
- a destination receives the events of every session of the application,
so filter on workflow_id or vendor_data when several flows share it
- creating a session already sends status.updated with status
"Not Started". The decision key is present only when status is Approved,
Declined, In Review or Abandoned.
Verify every delivery:
Header: X-Signature-V2 (not X-Signature, not X-Signature-Simple)
Algorithm: HMAC-SHA256, hex digest, over the canonical JSON of the payload
(Python json.dumps(sort_keys=True, separators=(",", ":"),
ensure_ascii=False) after whole-valued floats become ints).
Never hash the raw request bytes under this header: that is
the older X-Signature.
Freshness: the signed body field timestamp is the dispatch time (Unix
seconds). Reject when abs(now - timestamp) > 300 seconds, and
reject when the X-Timestamp header does not equal it.
Idempotency: event_id is the same on every retry of one event, so store it
and skip a delivery you already processed. One session can
still send the same status under two event ids, and the
console's Try Webhook test deliveries carry no event_id, so
also make the handler safe to run twice for one
(session_id, status, webhook_type).
Compare: constant-time (crypto.timingSafeEqual)
Reference handler (Express). Keep the verification lines as written.
The handler is a fragment. Put this above it and app.listen(process.env.PORT)
below it. It needs Express and Node 21 or newer (an older Node rejects
every delivery). It expects a JSON body: answer 400 yourself if you accept
anything else on this route, and refuse to start without the secret.
const express = require("express");
const app = express();
const SECRET = process.env.DIDIT_WEBHOOK_SECRET; // secret_shared_key of the destination
// Your endpoint receives a signed payload
const crypto = require("node:crypto"); // ESM: import crypto from "node:crypto"
// X-Signature-V2 = HMAC over the canonical JSON, never the raw bytes. Match the sender byte for
// byte: keys sorted by code point, integers digit for digit, floats in Python's repr.
class Num { constructor(src) { this.src = src; } } // a number as written on the wire, not a double
const num = (s) => { if (/^-?\d+$/.test(s)) return BigInt(s).toString(); const n = +s; // ints stay exact
if (Number.isInteger(n)) return BigInt(n).toString(); const [m, e] = n.toExponential().split("e"); // 27.0 -> 27
return +e >= -4 ? String(n) : `${m}e-${String(-e).padStart(2, "0")}`; }; // 1e-05, not 0.00001
const byCodePoint = (a, b) => Buffer.compare(Buffer.from(a), Buffer.from(b)); // UTF-8 order = Python's
const canon = (v) => Array.isArray(v) ? `[${v.map(canon)}]` : v instanceof Num ? num(v.src)
: v && typeof v === "object" ? `{${Object.keys(v).sort(byCodePoint).map((k) => `${JSON.stringify(k)}:${canon(v[k])}`)}}`
: JSON.stringify(v);
// Read the body as text: express.json() would round 1000000000000000129 to a double first.
// Register this route ABOVE any global app.use(express.json()): the first parser to run
// consumes the stream, and a body it already parsed has lost the digits the signature covers.
app.post("/webhooks/didit", express.text({ type: "application/json" }), (req, res) => {
const exact = JSON.parse(req.body, (k, v, c) => typeof v === "number" ? new Num(c.source) : v); // Node 21+
const body = JSON.parse(req.body);
const mac = crypto.createHmac("sha256", SECRET).update(canon(exact), "utf8").digest("hex");
const sig = Buffer.from(String(req.headers["x-signature-v2"] ?? ""));
// Freshness comes from the signed body timestamp; the header alone is unsigned and replayable.
const ts = body.timestamp, fresh = String(ts) === req.headers["x-timestamp"]
&& Math.abs(Date.now() / 1000 - ts) <= 300;
if (!fresh || sig.length !== mac.length
|| !crypto.timingSafeEqual(sig, Buffer.from(mac))) return res.sendStatus(401);
const { session_id, status, webhook_type, vendor_data, decision } = body;
// decision is present on Approved, Declined, In Review and Abandoned.
res.sendStatus(200);
});
Status values (exact strings): Not Started, In Progress, Approved, Declined,
In Review, Resubmitted, Expired, Abandoned, Kyc Expired. Awaiting User only
appears on business verification sessions.
## 5. Read the decision
The same V3 decision reaches you two ways:
- webhook body: body.decision.id_verifications[]
- GET https://verification.didit.me/v3/session/{session_id}/decision/
-H "x-api-key: <your-api-key>"
This response IS the decision object. Read id_verifications at the top
level: there is no decision wrapper here.
Until the user finishes the ID step, status is "Not Started" or "In Progress"
and id_verifications is null, not an empty array. Guard for it.
The other feature results sit next to it as plural arrays: liveness_checks[],
face_matches[], ip_analyses[], aml_screenings[]. shared_from_session is null
on a session the person completed here.
## 6. Share the finished session (source application)
Only a session whose status is Approved, Declined or In Review can be shared.
POST https://verification.didit.me/v3/session/{session_id}/share/
-H "x-api-key: <source-api-key>"
-H "Content-Type: application/json"
-d '{ "for_application_id": "<receiving application id>", "ttl_in_seconds": 3600 }'
Response: 200 with share_token, for_application_id and session_kind ("user").
ttl_in_seconds: 60 to 86400, default 3600. Each call mints a new token; a
token cannot be revoked, it only expires. Send it to the receiving
application's backend, never to a browser.
## 7. Import it (receiving application)
POST https://verification.didit.me/v3/session/import-shared/
-H "x-api-key: <receiving-api-key>"
-H "Content-Type: application/json"
-d '{
"share_token": "<share_token from step 6>",
"trust_review": false,
"workflow_id": "<a workflow id of the receiving application>",
"vendor_data": "<user id on the receiving side>"
}'
share_token, trust_review and workflow_id are required. trust_review true
keeps the source status (Approved stays Approved); false puts the imported
session In Review so the receiving team decides. Response: 201 with the same
decision shape as step 5, a new session_id, and shared_from_session pointing
at the source session.
## 8. Errors to handle
The message is under detail for some errors and under the field name
(for_application_id, share_token) for others: read both.
- share, 400: "Cannot share a session with the same application."
- share, 400: "Target application does not exist."
- share, 400: "Only finished sessions ("Approved", "Declined", "In Review")
can be shared."
- import, 400 on share_token: "Invalid share token.", "Share token has
expired.", "This token is not valid for this application."
- import, 400: share_token, trust_review or workflow_id missing
- import, 403: "This session has already been shared with your
application." Mint a new token.
- import, 404: "Workflow does not exist for this application."
## 9. Hard rules
- base URL for v3 endpoints: verification.didit.me
- auth header: x-api-key, one key per application
- feature enum: OCR, LIVENESS, FACE_MATCH, IP_ANALYSIS, AML (uppercase)
- result path: decision.id_verifications[] in the webhook body and
id_verifications[] on the GET decision response
- webhook: X-Signature-V2 plus X-Timestamp, canonical JSON, freshness from
the signed body timestamp
## 10. Verify your integration
Sandbox (an application in sandbox mode: nothing is billed).
https://docs.didit.me/integration/sandbox-testing
- create the workflow, create a session, and read its decision: expect 201,
201 with url, and 200 with status "Not Started"
- to get a finished session without a person, call
POST https://verification.didit.me/v3/session/{session_id}/simulate/ with
your x-api-key and { "new_status": "Approved" }. It sets the status and
sends the webhook; the feature arrays stay null.
- share that session. With no second application yet, send a random UUID
as for_application_id and expect the 400 "Target application does not
exist.": that proves the call is wired. A full share and import needs
the second application's id and API key; do not fake that step.
- assert the webhook accepts a correctly signed payload and rejects a wrong
X-Signature-V2, a changed body, and a payload whose signed timestamp is
older than 300 seconds, even when X-Timestamp is refreshed
Docs:
- https://docs.didit.me/core-technology/reusable-kyc/overview
- https://docs.didit.me/sessions-api/create-session
- https://docs.didit.me/sessions-api/retrieve-session
- https://docs.didit.me/integration/webhooks
개발, 테스트 및 초기 사용자 확보에 적합합니다.
25개 이상의 모듈, 투명한 가격 정책. 자동 볼륨 할인.
대규모 볼륨 및 규제 프로그램에 적합합니다.
사용량이 증가하면 볼륨 할인이 자동으로 적용됩니다. 협상이나 영업팀과의 통화가 필요 없습니다.
Didit은 신원 및 사기 방지 인프라입니다. 저희가 직접 제품을 만들 때 있었으면 했던 플랫폼이죠. 개방적이고 유연하며 개발자 친화적이어서, 단순히 연동해야 하는 블랙박스가 아니라 스택의 실제 구성 요소처럼 작동합니다.
하나의 API로 개인 확인(KYC, know your customer), 기업 확인(KYB, know your business), 암호화폐 지갑 심사(KYT, know your transaction), 그리고 실시간 거래 모니터링을 처리하며, 다음과 같은 스택을 기반으로 구축되었습니다:
기반 기술로는 48개 이상의 언어로 된 14,000개 이상의 문서 유형, 1,000개 이상의 데이터 소스, 그리고 모든 세션에서 200개 이상의 사기 신호를 처리합니다. Didit 인프라는 모든 세션에서 동적으로 학습하며 매일 발전합니다.
eIDAS 2.0은 전자 신원 확인, 인증 및 신뢰 서비스(electronic IDentification, Authentication and trust Services)의 약자로, EU의 2024년 신원 규정집 업데이트입니다. 주요 변경 사항은 유럽 디지털 신원 지갑(European Digital Identity Wallet)(종종 EUDI Wallet으로 줄여 부름)으로, 모든 EU 시민과 거주자가 2026년 말까지 사용할 수 있게 될 스마트폰 앱입니다.
이 지갑은 신뢰할 수 있는 기관이 발급하고 암호화 증명과 함께 의존 당사자에게 제시되는 검증 가능한 자격 증명(디지털 운전면허증, 확인된 신원 증명, 학위 증명서 등)을 보관하며, 선택적으로 선택적 공개(생년월일 전체를 공개하지 않고 18세 이상임을 증명) 기능을 제공합니다.
기업의 경우, eIDAS 2.0은 지갑으로 제시된 자격 증명을 수락하는 것과 사용자가 소지할 자격 증명을 발급하는 두 가지 측면을 모두 다룹니다.
규정 (EU) 2024/1183 (eIDAS 2)에 명시된 내용은 다음과 같습니다:
다른 기업은 자발적으로 지갑을 수락할 수 있습니다. Didit의 EUDI 지갑 수락은 곧 지원될 예정입니다: 기업이 EUDI 지갑을 수락하기 위해 필요한 사항을 읽어보세요.
전체 과정은 일반적으로 30초 이내에 완료됩니다. 신분증을 들고, 서류를 촬영하고, 셀카를 찍으면 끝입니다. 이는 시장에서 가장 빠른 속도입니다. 기존 KYC 제공업체는 동일한 과정에 90초 이상이 소요되는 경우가 많습니다.
백엔드에서는 사용자가 셀카 촬영을 마친 시점부터 웹훅이 실행되는 시점까지, Didit은 p99 기준 2초 이내에 결과를 반환합니다. 모바일 캡처는 느린 휴대폰과 느린 네트워크 환경에 최적화되어 있습니다. 점진적 이미지 압축, 지연 로딩되는 SDK, 그리고 사용자가 웹에서 시작할 경우 QR 코드를 통해 휴대폰으로 한 번의 탭으로 전환하는 기능을 제공합니다.
Federated login은 신원 제공자에게 계정이 존재함을 증명하며, 이메일 주소와 이름 정도만 수신 기업에 전달됩니다. 이는 규제 수준의 신원 확인이 아닙니다.
재사용 가능한 신원(Reusable identity)은 다음을 포함합니다:
검증자는 자체 KYC를 실행하는 것과 동일한 규제적 안정성을 얻을 수 있으며, 비용, 마찰, 전환율 하락 없이 이를 달성합니다.
모든 세션은 7가지 명확한 상태 중 하나로 귀결되므로, 개발자 코드는 항상 다음 작업을 알 수 있습니다:
Approved, 모든 검사를 통과했습니다. 사용자를 다음 단계로 진행하세요.Declined, 하나 이상의 검사가 실패했습니다. 전체 플로우를 다시 실행하지 않고, 사용자가 특정 실패 단계(예: 셀카 재촬영)를 재제출하도록 허용할 수 있습니다.In Review, 규정 준수 검토를 위해 플래그가 지정되었습니다. 콘솔에서 케이스를 열어 모든 신호를 확인하고 승인 또는 거부를 결정하세요.In Progress, 사용자가 플로우 중간에 있습니다.Not Started, 링크가 전송되었지만, 사용자가 아직 열지 않았습니다. 너무 오래 방치되면 알림을 보내세요.Abandoned, 사용자가 링크를 열었지만, 제시간에 완료하지 못했습니다. 다시 참여를 유도하거나 만료시키세요.Expired, 세션 링크가 만료되었습니다. 새 세션을 생성하세요.모든 상태 변경 시 서명된 웹훅이 발생하므로, 데이터베이스는 항상 동기화 상태를 유지합니다. 중단되거나 거부된 세션은 무료입니다.
프로덕션 데이터는 기본적으로 유럽 연합 내 Amazon Web Services에 저장 및 처리됩니다. 규제 당국의 요구 사항이 있는 관할권의 경우, 기업 계약을 통해 다른 지역을 요청할 수 있습니다.
모든 곳에 암호화가 적용됩니다. 모든 데이터베이스, 객체 스토어, 백업에 AES-256 암호화가 적용되어 저장됩니다. 모든 API 호출, 웹훅, 비즈니스 콘솔 세션에서 전송 중인 데이터는 Transport Layer Security 1.3으로 암호화됩니다. 생체 인식 데이터는 별도의 고객 마스터 키로 암호화됩니다.
데이터 보존 기간은 고객이 직접 제어할 수 있습니다. 기본 보존 기간은 무기한(무제한)이며, 애플리케이션별로 30일에서 10년 사이로 더 짧게 설정할 수 있습니다. 또한, 대시보드나 API를 통해 언제든지 개별 세션을 삭제할 수 있습니다.
인증: SOC 2 Type 1 & Type 2, ISO/IEC 27001:2022, iBeta Level 1 PAD를 획득했으며, 스페인 Tesoro / SEPBLAC / CNMV로부터 Didit의 원격 신원 확인이 대면 확인보다 안전하다는 공식 인증을 받았습니다. 전체 보고서는 /security-compliance에서 확인하실 수 있습니다.
Didit은 신원 인프라에 중요한 규제 기관의 요건을 기본적으로 준수합니다:
자세한 메모, 모든 인증서, 모든 규제 기관 서신: /security-compliance.
세 가지 통합 경로 중 스택에 가장 적합한 것을 선택하세요:
세 가지 모두 동일한 대시보드, 동일한 청구, 동일한 성공당 지불 가격이 적용됩니다. 단계별 가이드는 docs.didit.me/integration/integration-prompt에서 확인하세요.
재사용 가능한 신원은 기존 KYC 스택에 대한 개인 정보 보호 업그레이드입니다:
수신 측에서의 처리 법적 근거는 GDPR에 따른 정당한 이익입니다. 사용자가 서비스 접근을 위해 명시적으로 자격 증명을 제시했기 때문입니다. Didit의 표준 데이터 처리 계약(DPA)은 공동 컨트롤러 관계를 다룹니다.
세 가지 트리거 유형:
수신 플랫폼은 사용자에게 업데이트를 요청할 필요가 없으며, 자격 증명은 자체적인 신선도 신호를 전달하고, 오래된 자격 증명은 제시 시 거부됩니다.
검증자(수신 플랫폼)는 원본 검증자가 받은 것과 동일한 제시별 증거 팩과 발급 체인을 받습니다:
Didit은 스페인 재무부, 스페인 중앙은행, SEPBLAC이 공동으로 이 서비스를 대면 인증보다 안전하다고 증명한 유일한 KYC 플랫폼입니다. 이 증명은 모든 재사용된 제시에도 적용됩니다.