免费
适用于构建、测试和您的首批用户。
- 每月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,了解您的客户)、企业验证(KYB,了解您的业务)、加密钱包筛选(KYT,了解您的交易)以及实时交易监控。该技术栈旨在实现:
底层支持:14,000 多种文档类型,支持 48 种以上语言,1,000 多个数据源,以及每次会话的 200 多个欺诈信号。Didit 基础设施通过每次会话动态学习,并日益完善。
eIDAS 2.0, 全称电子身份识别、认证和信任服务, 是欧盟2024年对其身份规则手册的更新。主要变化是欧洲数字身份钱包(通常简称EUDI钱包):到2026年底,每个欧盟公民和居民都将有权拥有一个智能手机应用程序。
该钱包存储可验证凭证, 数字驾驶执照、已验证身份证明、学术文凭, 由受信任方签发,并以加密证明形式提交给依赖方,可选择选择性披露(证明您已满18岁,而无需透露您的完整出生日期)。
对于企业而言,eIDAS 2.0既涉及接受钱包出示的凭证,也涉及签发用户携带的凭证。
《欧盟法规 (EU) 2024/1183 (eIDAS 2)》规定:
其他企业可自愿接受该钱包。Didit 即将支持 EUDI 钱包:请阅读企业接受 EUDI 钱包所需了解的信息。
整个流程通常在 30 秒内完成, 拿起身份证,拍摄文件,拍摄自拍,完成。这是市场上最快的。传统的 KYC 提供商通常需要超过 90 秒才能完成相同的流程。
在后端,Didit 在 p99 下两秒内返回结果,从用户完成自拍到您的 webhook 触发的那一刻开始计算。移动捕获针对慢速手机和慢速网络进行了优化:渐进式图像压缩、延迟软件开发工具包加载,以及如果用户从网页开始,通过二维码从桌面到手机的一键式切换。
联邦登录证明身份提供商处存在账户,接收方企业可获得电子邮件地址和可能有的姓名。但这并非监管级别的身份验证。
可复用身份包含:
验证方可获得与自行运行 KYC 相同的监管保障,且无需承担成本、减少摩擦并避免转化率下降。
每个会话都有七种明确状态之一,因此您的代码始终知道如何处理:
Approved, 所有检查通过。让用户继续。Declined, 一项或多项检查失败。您可以允许用户重新提交特定的失败步骤(例如,重新拍摄自拍照),而无需重新运行整个流程。In Review, 标记为合规审查。在控制台中打开案例,查看所有信号,决定批准或拒绝。In Progress, 用户正在流程中。Not Started, 链接已发送,用户尚未打开。如果长时间未打开,发送提醒。Abandoned, 用户打开了链接但未及时完成。重新激活或使其过期。Expired, 会话链接已过期。创建新会话。每次状态更改都会触发签名 webhook,因此您的数据库始终保持同步。放弃和拒绝的会话是免费的。
生产数据默认在欧盟的 Amazon Web Services 上处理和存储。企业合同可根据监管机构要求,申请在其他区域部署。
全面加密。 所有数据库、对象存储和备份均采用 AES-256 静态加密。所有 API 调用、webhook 和业务控制台会话均采用传输层安全协议 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 是唯一获得欧盟成员国政府正式证明的 KYC 平台, 西班牙财政部、西班牙银行和 SEPBLAC 联合证明该服务比亲自验证更安全。该证明适用于每次复用出示。