Agent skill
Didit Webhooks Skill
Receive and verify Didit identity verification webhooks. Use when setting up Didit webhook handlers, debugging X-Signature verification failures, or handling status.updated and data.updated events for KYC/KYB sessions.
Install this skill
npx skills add hookdeck/webhook-skills --skill didit-webhooks
When to Use This Skill
- How do I receive Didit webhooks?
- How do I verify the Didit
X-Signatureheader? - Why is my Didit webhook signature verification failing?
- How do I handle
status.updated(session Approved / Declined / In Review) events? - How do I handle
data.updatedevents after a manual review?
Verification (core)
Didit signs every webhook with HMAC-SHA256, keyed with the destination's secret_shared_key (used as a raw string). X-Signature is the hex digest of the exact raw request body. X-Timestamp (unix seconds) is sent separately and is not part of the signed input. Didit recommends rejecting requests where abs(now - X-Timestamp) > 300, but Hookdeck retries and replays keep the original header, so the examples make that check opt-in (DIDIT_, default 0).
Node:
const crypto = require('crypto');
// toleranceSec = 0 (default) skips the X-Timestamp check; a positive value opts in
function verify(rawBody, signature, timestamp, secret, toleranceSec = 0, now = Math.floor(Date.now() / 1000)) {
if (!signature) return false;
if (toleranceSec > 0 && !(Math.abs(now - Number(timestamp)) <= toleranceSec)) return false;
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(signature), b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Python:
import hmac, hashlib, time
def verify(raw_body: bytes, signature: str, timestamp: str, secret: str, tolerance: int = 0) -> bool:
# tolerance = 0 (default) skips the X-Timestamp check; a positive value opts in
if not signature:
return False
if tolerance > 0 and (not timestamp or not timestamp.isdigit() or abs(time.time() - int(timestamp)) > tolerance):
return False
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(signature, expected)
For complete handlers with route wiring, event dispatch, and tests, see:
Didit has no webhook verification SDK, so verification is manual. X-Signature-V2 (HMAC of re-serialized, sorted-key JSON) is an alternative for stacks that re-encode the body before your handler runs, and the deprecated X-Signature-Simple only authenticates the envelope, not decision. See references/verification.md.
Common Event Types
Event (webhook_type) | Description |
|---|---|
status.updated | A KYC or KYB session's status changed (Approved, Declined, In Review, Abandoned, Resubmitted, ...) |
data.updated | Verification data was edited after creation, e.g. by a manual reviewer |
Didit also delivers entity, transaction, Travel Rule and Investigator events (for example user.status.updated, transaction.created). There is no wildcard: list each event on the destination. See references/overview.md.
For the full event reference, see Didit's webhook documentation.
Important Headers
| Header | Description |
|---|---|
X-Signature | Hex HMAC-SHA256 of the exact raw body |
X-Timestamp | Unix seconds when Didit dispatched the webhook (refreshed on each retry) |
X-Signature-V2 | Hex HMAC-SHA256 of sorted-key, Unicode-preserved compact JSON (alternative) |
X-Signature-Simple | Deprecated; envelope-only signature |
Environment Variables
DIDIT_WEBHOOK_SECRET=your_secret_shared_key # Returned once when the destination is created
DIDIT_TIMESTAMP_TOLERANCE_SECONDS=0 # Optional; a positive value turns on the timestamp check
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 didit --path /webhooks/didit
Reference Materials
- references/overview.md - Didit webhook concepts, events, payload shape
- references/setup.md - Creating a destination and getting the secret
- references/verification.md - Signature verification details and gotchas