# ComplyCube Webhooks

## When to Use This Skill

* How do I receive ComplyCube webhooks?
* How do I verify the `ComplyCube-Signature` header on a ComplyCube webhook?
* How do I handle `check.completed` or `check.completed.clear` events?
* Why is my ComplyCube webhook signature verification failing?
* Why was my ComplyCube webhook disabled?

## Verification (core)

ComplyCube sends `ComplyCube-Signature: <hex>`: the bare lowercase hex **HMAC-SHA256 of the raw
request body**, keyed with the webhook's secret (UTF-8 bytes, used as-is). There is no prefix, no
timestamp and it is not the Standard Webhooks spec; only the body is signed. Verify over the **raw
bytes before parsing**, validate the hex shape (64 chars) before decoding, then compare in constant time.

Node (`Buffer.from(x, 'hex')` silently drops invalid characters, so check the shape first):

```javascript
const crypto = require('crypto');

function verifyComplyCubeSignature(rawBody, signatureHeader, secret) {
  if (typeof signatureHeader !== 'string' || !/^[0-9a-f]{64}$/i.test(signatureHeader)) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
  return crypto.timingSafeEqual(Buffer.from(signatureHeader, 'hex'), expected);
}

```

Python (compare bytes so a non-ASCII header yields 401, not a 500):

```python
import hashlib, hmac, re

def verify_complycube_signature(raw_body: bytes, signature_header: str | None, secret: str) -> bool:
    if not signature_header or not re.fullmatch(r"[0-9a-fA-F]{64}", signature_header):
        return False
    expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest().encode("ascii")
    return hmac.compare_digest(signature_header.lower().encode("ascii"), expected)

```

> **Raw body matters.** ComplyCube's own Node sample runs `bodyParser.json()` and verifies
> `JSON.stringify(request.body)`, which only matches by accident when the re-serialised JSON is
> byte-identical. Verify the raw bytes instead. Express handlers use `express.raw({ type: '*/*' })`.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/complycube-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/complycube-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/complycube-webhooks/examples/fastapi/).

Reply 401 on a missing, malformed or mismatched signature.

## Event Types

The event name is the body's `type` field (no header names the event). `resourceType` names the object in `payload`.

| Event | Triggered When |
| --- | --- |
| `check.completed` | A check completed with any outcome |
| `check.completed.clear` | A check completed with a `clear` outcome |
| `check.completed.attention` | A check completed with an `attention` outcome |
| `check.completed.rejected` | A check completed with a `rejected` outcome |
| `check.completed.match_confirmed` | A check completed with a `match_confirmed` outcome |
| `check.failed` | A check failed |
| `client.created` | A client was created |
| `workflow.session.completed` | A workflow session completed |

A completed check fires both `check.completed` and the outcome-specific type if you subscribed to
both. The full list of 25 types is in [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/complycube-webhooks/references/overview.md).

## Delivery Behaviour

Return a `20x` within **15 seconds**. Otherwise ComplyCube retries up to 10 times with exponential
backoff (60 s, 120 s, 480 s, ...) and then **disables the webhook**. Events can arrive more than
once and out of order: use the event `id` for idempotency, and fetch the object from the API when
you need its current state. Webhook URLs must be HTTPS.

## Environment Variables

```bash
COMPLYCUBE_WEBHOOK_SECRET=your_webhook_secret   # Returned once, when the webhook is created

```

## Setup with Hookdeck

Create the webhook in ComplyCube with your Hookdeck source URL, then paste the webhook secret into
the Hookdeck source's webhook verification settings (HMAC, SHA-256, header `ComplyCube-Signature`,
hex). Hookdeck accepts the delivery quickly, which keeps you inside the 15-second window and avoids
the webhook being disabled.

## Local Development

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

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/complycube-webhooks/references/overview.md) - Webhook concepts, event types and payload shape
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/complycube-webhooks/references/setup.md) - Create a webhook endpoint and get its secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/complycube-webhooks/references/verification.md) - Signature verification details and gotchas