# 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:

```javascript
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:

```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:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/veriff-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/veriff-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/veriff-webhooks/examples/fastapi/)

## 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`.

| Webhook | Field | Values |
| --- | --- | --- |
| Decision | `verification.status` | `approved`, `declined`, `resubmission_requested`, `review`, `expired`, `abandoned` |
| Event | `action` | `started`, `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](https://github.com/hookdeck/webhook-skills/blob/main/skills/veriff-webhooks/references/overview.md).

> **For the full payload reference**, see [Veriff Webhooks Guide](https://devdocs.veriff.com/docs/webhooks-guide) and the [Decision webhook](https://devdocs.veriff.com/docs/decision-webhook) / [Event webhook](https://devdocs.veriff.com/docs/event-webhook) pages.

## Important Headers

| Header | Description |
| --- | --- |
| `X-HMAC-SIGNATURE` | Hex HMAC-SHA256 of the raw body (the only header that authenticates the request) |
| `X-AUTH-CLIENT` | Your integration API key (identifies the sender, not proof of authenticity) |
| `vrf-integration-id` | Integration ID (shown in Veriff's example headers; don't rely on it) |

## Environment Variables

```bash
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

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

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/veriff-webhooks/references/overview.md) - Webhook types, statuses, event actions, payload shapes
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/veriff-webhooks/references/setup.md) - Configure webhook URLs in the Customer Portal
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/veriff-webhooks/references/verification.md) - Signature details, test vector, gotchas