Agent skill

Veriff Webhooks Skill

Receive and verify Veriff webhooks. Use when setting up Veriff identity verification webhook handlers, debugging X-HMAC-SIGNATURE verification, or handling decision webhooks (approved, declined, resubmission_requested, expired, abandoned) and event webhooks (started, submitted).

Install this skill

npx skills add hookdeck/webhook-skills --skill veriff-webhooks


When to Use This Skill

  • How do I receive Veriff webhooks?
  • How do I verify the Veriff X-HMAC-SIGNATURE header?
  • Why is my Veriff webhook signature verification failing?
  • How do I handle Veriff decision webhooks (approved, declined, resubmission_requested)?
  • How do I track the started and submitted Veriff event webhooks?

Verification (core)

Veriff signs the raw request body with HMAC-SHA256, keyed with your integration's shared secret key (used as a plain string, no decoding), and sends the lowercase hex digest in X-HMAC-SIGNATURE. There is no timestamp and no signature prefix. Veriff has no webhook-verification SDK, so verify manually.

Veriff also sends X-AUTH-CLIENT (your integration API key) and possibly vrf-integration-id. They are identifiers, not proof of authenticity: only X-HMAC-SIGNATURE authenticates the sender.

Node:

const crypto = require('crypto');

function verifyVeriff(rawBody, signatureHeader, sharedSecret) {
  if (!signatureHeader) return false;
  const expected = crypto.createHmac('sha256', sharedSecret).update(rawBody).digest('hex');
  try {
    return crypto.timingSafeEqual(
      Buffer.from(signatureHeader.trim().toLowerCase()),
      Buffer.from(expected)
    );
  } catch {
    return false; // Different lengths = invalid
  }
}

Python:

import hmac, hashlib

def verify_veriff(raw_body: bytes, signature_header: str, shared_secret: str) -> bool:
    if not signature_header:
        return False
    expected = hmac.new(shared_secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature_header.strip().lower(), expected)

For complete handlers with route wiring, event dispatch, and tests, see:

Webhook Types and Events

Veriff has several webhook types, each with its own URL in the Customer Portal. This skill handles the two most common. A decision webhook carries a verification object; an event webhook carries a top-level action.

WebhookFieldValues
Decisionverification.statusapproved, declined, resubmission_requested, review, expired, abandoned
Eventactionstarted, submitted, waiting_complete, waiting_continued, flow_finished, flow_cancelled, document_type_other_selected

review is only sent if agreed with Veriff. Event actions other than started and submitted need extra configuration and apply to the web flow only. Other types (watchlist screening, INE, CURP, user-defined statuses, full auto) are not covered here. See references/overview.md.

For the full payload reference, see Veriff Webhooks Guide and the Decision webhook / Event webhook pages.

Important Headers

HeaderDescription
X-HMAC-SIGNATUREHex HMAC-SHA256 of the raw body (the only header that authenticates the request)
X-AUTH-CLIENTYour integration API key (identifies the sender, not proof of authenticity)
vrf-integration-idIntegration ID (shown in Veriff's example headers; don't rely on it)

Environment Variables

VERIFF_SHARED_SECRET=your_shared_secret_key   # Customer Portal > Integrations > your integration (not the API key)

Delivery Behavior

  • Respond with 200 within 5 seconds. Any other response triggers retries for up to a week.
  • Delivery is at-least-once and unordered: make handlers idempotent (key on verification.id + verification.attemptId + status).
  • Only HTTPS URLs with no custom port are accepted.

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 veriff --path /webhooks/veriff

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 2, 2026

View on GitHub →