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-Signature header?
  • 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.updated events 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_TIMESTAMP_TOLERANCE_SECONDS, 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.updatedA KYC or KYB session's status changed (Approved, Declined, In Review, Abandoned, Resubmitted, ...)
data.updatedVerification 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

HeaderDescription
X-SignatureHex HMAC-SHA256 of the exact raw body
X-TimestampUnix seconds when Didit dispatched the webhook (refreshed on each retry)
X-Signature-V2Hex HMAC-SHA256 of sorted-key, Unicode-preserved compact JSON (alternative)
X-Signature-SimpleDeprecated; 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 2, 2026

View on GitHub →