didit-sdk
Unofficial, community-maintained Python client for the Didit Identity Verification API.
Highlights
- Ergonomic Sync & Async: Dual-client architecture built on top of high-performance
httpx. - Strictly Typed & Validated: 100% type annotations (PEP 561 compliant with
py.typed) and robust Pydantic v2 domain models. - Didit V3 API Alignment: Full fidelity to upstream V3 schemas (
id_verifications[],liveness_checks[],face_matches[],aml_screenings[],reviews[],warnings[]) with forward compatibility (extra="allow"), verification warnings helper (decision.has_warning(...)), and backward-compatible property accessors. - Complete Session Lifecycle: Covers all 10 documented Didit session statuses (
Not Started,In Progress,In Review,Approved,Declined,Expired,Abandoned,Kyc Expired,Resubmitted,Awaiting User) with granular state inspection (is_decided,is_closed,requires_review,requires_user_action). - Cryptographic Security & DoS Defense: Constant-time HMAC-SHA256 signature verification (
X-Signature-V2&X-Signature), full UTF-8 Unicode canonical JSON support (ensure_ascii=False,allow_nan=False), authoritative signed body anti-replay verification with strict header matching, and bounded body limits (HTTP 413) to prevent memory exhaustion. - Webhook Deduplication: Thread-safe in-memory and atomic Redis deduplication stores (
InMemoryWebhookDedupStore,RedisWebhookDedupStore,AsyncRedisWebhookDedupStore) with configurable duplicate actions (respond_ok,pass,raise). - Multi-Framework Integrations: Native adapters for FastAPI (
DiditWebhookGuard), Django (@didit_webhook_view), and Flask (@didit_webhook). - Resilient HTTP Engine: Deterministic jittered exponential backoff retries with fail-fast budget timers for transient errors (HTTP 429, 502, 503, 504).
- High-Fidelity Sandbox Parity: 16 official Didit sandbox outcome simulation slugs (
approve,decline_document_expired,decline_face_match_low_similarity,decline_aml_hit,review_face_match_borderline,review_aml_possible_match, etc.) and calibrated 0–100 biometric confidence scoring.
Installation
# Core SDK (httpx + pydantic)
pip install didit-sdk
# With framework integrations
pip install "didit-sdk[fastapi]"
pip install "didit-sdk[django]"
pip install "didit-sdk[flask]"
# With Redis webhook deduplication store
pip install "didit-sdk[redis]"
# All optional dependencies
pip install "didit-sdk[all]"
Quickstart
1. Synchronous Client
from didit import Didit, SessionStatus
# Initialize client (falls back to DIDIT_API_KEY environment variable if omitted)
client = Didit(api_key="your_api_key", webhook_secret="your_webhook_secret")
# Create a verification session with sandbox outcome scenario
session = client.sessions.create(
vendor_data="user_12345",
workflow_id="wf_kyc_standard",
callback="https://yourapp.com/kyc/complete",
language="es",
sandbox_scenario="approve",
)
print(f"Verification URL: {session.url}")
print(f"Session ID: {session.session_id}")
# Fetch verification decision immediately:
decision = client.sessions.get_decision(session.session_id)
# Or poll until a terminal outcome (Approved or Declined) is reached:
decision = client.sessions.poll_decision(session.session_id, timeout=60.0, interval=2.0)
if decision.status == SessionStatus.APPROVED:
print(f"User approved! Document: {decision.document.document_number}")
if decision.has_warning("DOCUMENT_POOR_QUALITY"):
print("Note: Document quality was flagged.")
2. Asynchronous Client (asyncio)
import asyncio
from didit import AsyncDidit, SessionStatus
async def main():
async with AsyncDidit(api_key="your_api_key") as client:
session = await client.sessions.create(
vendor_data="user_987",
workflow_id="wf_kyc_standard",
)
print(f"Session URL: {session.url}")
decision = await client.sessions.poll_decision(session.session_id, timeout=30.0)
if decision.status.is_terminal:
print(f"Final outcome: {decision.status}")
asyncio.run(main())
Webhook Integrations & Deduplication
Didit dispatches signed HTTP POST events upon verification completion. didit-sdk provides native, production-grade adapters with streaming body limits (HTTP 413) and distributed deduplication:
FastAPI
from fastapi import FastAPI, Depends
from didit import WebhookPayload, SessionStatus, RedisWebhookDedupStore
from didit.integrations.fastapi import DiditWebhookGuard
app = FastAPI()
guard = DiditWebhookGuard(
secret="whsec_...",
dedup_store=RedisWebhookDedupStore.from_url("redis://localhost:6379/0"),
# duplicate_action defaults to "pass" for at-least-once safe delivery
)
@app.post("/webhooks/didit")
async def handle_webhook(payload: WebhookPayload = Depends(guard)):
# Note: payload.is_duplicate is a hint that this event key was seen before.
# It does NOT prove that a previous attempt completed successfully.
# Enforce idempotency at the database/transaction boundary:
async with db.transaction():
if not await is_event_unprocessed(payload.event_id):
return {"status": "already_processed"}
if payload.status == SessionStatus.APPROVED:
# Idempotently process approved KYC verification
await mark_user_verified(payload.session_id)
return {"status": "ok"}
Django
from django.http import HttpRequest, HttpResponse
from didit import WebhookPayload, SessionStatus
from didit.integrations.django import didit_webhook_view
@didit_webhook_view(secret="whsec_...")
def my_webhook_view(request: HttpRequest, payload: WebhookPayload) -> HttpResponse:
if payload.status == SessionStatus.APPROVED:
...
return HttpResponse(status=200)
Flask
from flask import Flask
from didit import WebhookPayload, SessionStatus
from didit.integrations.flask import didit_webhook
app = Flask(__name__)
@app.route("/webhooks/didit", methods=["POST"])
@didit_webhook(secret="whsec_...")
def handle_didit(payload: WebhookPayload):
if payload.status == SessionStatus.APPROVED:
...
return {"status": "ok"}, 200
Testing & Offline Simulation
Developing without live credentials or mock servers? Use the built-in simulator:
from didit import SimulatedDidit, SessionStatus
# Create in-memory client
sim = SimulatedDidit(webhook_secret="test_secret")
# 1. Create a simulated session
session = sim.sessions.create(vendor_data="dev_user", workflow_id="wf_dev")
assert session.session_id.startswith("sim_")
# 2. Simulate user passing identity check
sim.approve_session(session.session_id)
decision = sim.sessions.get_decision(session.session_id)
assert decision.status == SessionStatus.APPROVED
# 3. Generate signed webhook payloads to test your local receiver
raw_body, headers = sim.generate_webhook_event(session.session_id)
# headers contain valid X-Signature-V2 and fresh X-Timestamp!
Exception Handling
All client errors inherit from DiditError:
from didit import Didit
from didit.errors import (
DiditAuthenticationError,
DiditNotFoundError,
DiditRateLimitError,
DiditServerError,
)
client = Didit()
try:
decision = client.sessions.get_decision("non_existent_session")
except DiditNotFoundError:
print("Session not found")
except DiditRateLimitError as exc:
print(f"Rate limited. Retry after {exc.retry_after} seconds.")
except DiditAuthenticationError:
print("Invalid API key")
except DiditServerError as exc:
print(f"Didit server error ({exc.status_code}): {exc.response_body}")
Environment Variables
| Variable | Default | Description |
|---|---|---|
DIDIT_API_KEY |
None (Required) | Your Didit secret API key |
DIDIT_BASE_URL |
https://verification.didit.me/v3 |
Didit API base URL |
DIDIT_WEBHOOK_SECRET |
None | Shared webhook HMAC secret |
DIDIT_TIMEOUT |
30.0 |
HTTP request timeout in seconds |
DIDIT_MAX_RETRIES |
2 |
Maximum retry attempts for transient errors |
Development & Testing
# Clone the repository
git clone https://github.com/agustin18/didit-sdk.git
cd didit-sdk
# Install dependencies with uv
uv sync --extra dev
# Run unit tests with 100% coverage gate
uv run pytest --cov=didit --cov-report=term-missing
# Lint & Format check
uv run ruff check .
uv run ruff format --check .
# Strict type check
uv run mypy src/didit
License
This project is licensed under the terms of the MIT License.
Metadata
Release files for didit-sdk 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| didit_sdk-0.2.0.tar.gz | 64.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| didit_sdk-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 110.0 kB
Release files / didit_sdk-0.2.0.tar.gz
| Download URL | didit_sdk-0.2.0.tar.gz |
|---|---|
| Size | 64.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
73909cb59bd32a4f2598043ada4235623b175da6d6328af58816ff35fbef53ed
|
|
BLAKE2b-256 checksum How to use checksums |
a1823247ad2f000f885a0f4a6b0f509438fcd63759f9d60e415a59a5569d80cb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency logRelease files / didit_sdk-0.2.0-py3-none-any.whl
| Download URL | didit_sdk-0.2.0-py3-none-any.whl |
|---|---|
| Size | 45.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6281984b79e36e1ce30936800476a6f0044e3dec90b2dc3e1a132a74f8201ac6
|
|
BLAKE2b-256 checksum How to use checksums |
724fd9785ee710e9525b786101b8f0a558b8be35541a8b09fc1cec0f4fe22e04
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency log