Skip to main content

Webhooks

Webhooks deliver real-time HTTP POST notifications to your endpoint when events happen on your domain. Create and manage webhooks in the dashboard under API & Webhooks, in the Webhooks tab (up to 5 webhooks per domain).

Event types

EventDescription
consent.createdA new consent record was created
consent.updatedReserved. Not currently emitted
consent.revokedReserved. Not currently emitted
consent.expiredConsents expired (daily check at 02:00 CET)
dsar.createdA data subject access request was submitted
scan.completedA cookie scan finished
webhook.testTest event sent from the dashboard

Endpoint requirements

  • HTTPS URL, up to 2048 characters.
  • Public address: localhost, private IP ranges and internal hostnames are rejected.
  • Respond with a 2xx status within 10 seconds to acknowledge the delivery.

Payload format

Every delivery has this structure:

{
"event": "consent.created",
"timestamp": "2026-04-07T14:30:00.000Z",
"data": {
// event-specific data
}
}

Example consent.expired payload (visitors capped at 100 per delivery):

{
"event": "consent.expired",
"timestamp": "2026-04-07T02:00:05.000Z",
"data": {
"expired_count": 42,
"visitors": [
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"b2c3d4e5-f6a7-8901-bcde-f12345678901"
],
"period": {
"from": "2026-04-06T02:00:00.000Z",
"to": "2026-04-07T02:00:05.000Z"
}
}
}

Verify the signature

Every delivery is signed with HMAC-SHA256 using your webhook secret. The secret is shown once on webhook creation and can be regenerated in the dashboard.

Headers sent with each delivery:

HeaderDescription
X-OptSens-Signaturev1=<hex digest>
X-OptSens-TimestampUnix timestamp (seconds) when the payload was signed
X-OptSens-EventThe event type
Content-Typeapplication/json
User-AgentOptSens-Webhook/1.0

The signature is computed as HMAC-SHA256(secret, "{timestamp}.{json_body}").

Verify against the raw request body exactly as received. Re-serializing parsed JSON can produce different bytes and fail verification.

const crypto = require('crypto');
const express = require('express');

const app = express();
// Keep the raw bytes: the signature covers the body exactly as sent.
app.use(express.json({
verify: (req, res, buf) => { req.rawBody = buf.toString(); },
}));

function verifyWebhook(secret, signature, timestamp, body) {
const expected = crypto
.createHmac('sha256', secret)
.update(`${timestamp}.${body}`)
.digest('hex');
return signature === `v1=${expected}`;
}

app.post('/webhooks/optsens', (req, res) => {
const sig = req.headers['x-optsens-signature'];
const ts = req.headers['x-optsens-timestamp'];

if (verifyWebhook(WEBHOOK_SECRET, sig, ts, req.rawBody)) {
// signature valid: process req.body
res.sendStatus(200);
} else {
// reject: tampered payload or wrong secret
res.sendStatus(401);
}
});

Reject deliveries whose timestamp is far in the past to protect against replay.

Retries

Failed deliveries (non-2xx or timeout) retry with exponential backoff:

AttemptDelayTotal elapsed
1Immediate0
21 minute1 minute
35 minutes6 minutes
430 minutes36 minutes
52 hours2 hours 36 minutes

After 5 failed attempts the delivery is abandoned.

Auto-disable and delivery logs

  • A webhook that accumulates 50 consecutive failures is disabled automatically. Any successful delivery resets the counter, and re-enabling from the dashboard also resets it.
  • Delivery logs (status, response, attempts) are visible in the dashboard and retained for 30 days.