> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fingerprintiq.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

Receive real-time notifications for fingerprint events.

Webhooks are available on Builder plans and above. See [Pricing](/introduction#pricing) to upgrade.

## Overview

Configure webhooks to receive HTTP POST notifications when fingerprint events occur in real time — without polling the API.

### Event Types

| Event | Description |
|-------|-------------|
| `visit.new` | A device is identified for the first time |
| `visit.returning` | A previously seen device visits again |
| `visit.flagged` | A visit triggered one or more risk factors |
| `threat.detected` | Bot probability exceeded 0.7 on a visit |

## Webhook Payload

All events share the same payload structure. The `event` field identifies the type.

```json
{
  "event": "visit.returning",
  "timestamp": 1712000003000,
  "data": {
    "visitorId": "iq_01hns3k6tez83695a6t714s6n1",
    "visitCount": 5,
    "botProbability": 0.05,
    "country": "US",
    "riskFactors": []
  }
}
```

### Payload Fields

**event**

The event type. One of `visit.new`, `visit.returning`, `visit.flagged`, or `threat.detected`.

**timestamp**

Unix timestamp (milliseconds) when the event occurred on the FingerprintIQ edge.

**data**

Event data specific to the fingerprint event.

  **data fields**

**data.visitorId**

The stable device identifier that triggered this event.

    **data.visitCount**

Total visit count for this device at the time of the event.

    **data.botProbability**

Bot probability score (0.0–1.0) for this visit.

    **data.country**

ISO country code of the visitor's IP at the time of the event.

    **data.riskFactors**

Risk indicators active during the visit that triggered this event. Will be non-empty for `visit.flagged` and `threat.detected` events.

## Signature Verification

All webhook deliveries include an `X-FIQ-Signature` header containing an HMAC-SHA256 signature computed over the raw request body. Always verify this signature before processing the event.

```javascript Node.js
import crypto from 'crypto';

function verifyWebhook(rawBody, signature, webhookSecret) {
  const expected = crypto
    .createHmac('sha256', webhookSecret)
    .update(rawBody)
    .digest('hex');

  // Use timingSafeEqual to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature, 'hex'),
    Buffer.from(expected, 'hex')
  );
}

// Express example
app.post('/webhooks/fingerprintiq', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-fiq-signature'];
  if (!verifyWebhook(req.body, sig, process.env.FIQ_WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body);
  // handle event
  res.status(200).send('OK');
});
```

```typescript Hono (Cloudflare Workers)
import { Hono } from 'hono';

const app = new Hono();

app.post('/webhooks/fingerprintiq', async (c) => {
  const rawBody = await c.req.text();
  const sig = c.req.header('x-fiq-signature') ?? '';

  const key = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(c.env.FIQ_WEBHOOK_SECRET),
    { name: 'HMAC', hash: 'SHA-256' },
    false,
    ['sign']
  );

  const expected = await crypto.subtle.sign(
    'HMAC',
    key,
    new TextEncoder().encode(rawBody)
  );

  const expectedHex = Array.from(new Uint8Array(expected))
    .map(b => b.toString(16).padStart(2, '0'))
    .join('');

  if (sig !== expectedHex) {
    return c.text('Invalid signature', 401);
  }

  const event = JSON.parse(rawBody);
  // handle event
  return c.text('OK');
});
```

Always use `crypto.timingSafeEqual` (Node.js) or `crypto.subtle` (Web Crypto) for signature comparison. A simple string comparison (`===`) is vulnerable to timing attacks that can leak your webhook secret.

## Delivery and Retries

FingerprintIQ delivers webhooks with the following guarantees:

- Deliveries time out after **5 seconds** — your endpoint must respond within this window
- Events are retried up to **3 times** with exponential backoff (1s, 5s, 25s) on non-200 responses or timeouts
- Respond with `2xx` as quickly as possible and process the event asynchronously (e.g., push to a queue)

Return a `200 OK` immediately and process webhook events in a background job or queue. This prevents timeouts if your processing logic involves database writes or external API calls.