Webhooks
Receive real-time notifications for fingerprint events.
Webhooks are available on Builder plans and above. See 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
The event type. One of visit.new, visit.returning, visit.flagged, or threat.detected.
Unix timestamp (milliseconds) when the event occurred on the FingerprintIQ edge.
Event data specific to the fingerprint event.
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.
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');
});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
2xxas 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.