PolicifyAI
Dashboard

API reference

Webhooks

Webhooks let your server receive real-time notifications whenever a policy event occurs in your PolicifyAI workspace. No polling required.

Available events

policy.published

A policy has been published to a live URL.

policy.updated

A policy has been edited or regenerated.

policy.deleted

A policy has been permanently deleted.

consent.recorded

A visitor recorded a cookie/consent choice.

dsar.submitted

A data subject access request was submitted.

cookie_scan.completed

A compliance scanner scan finished.

integration.connected

An integration (e.g. WordPress, Shopify) was connected.

integration.disconnected

An integration was disconnected.

Payload structure

All webhook deliveries send a JSON payload with this structure:

{
  "event": "policy.published",
  "webhook_id": "wh_abc123",
  "delivery_id": "del_xyz789",
  "created_at": "2025-01-01T12:00:00Z",
  "data": {
    "policy_id": "pol_abc123",
    "policy_type": "privacy-policy",
    "business_name": "Acme Ltd",
    "domain": "https://acme.com",
    "jurisdiction": "GB",
    "language": "en",
    "client_id": null,
    "status": "ready",
    "created_at": "2025-01-01T12:00:00Z",
    "updated_at": "2025-01-01T12:01:30Z"
  }
}
💡 Generating a policy (via the dashboard, API, bulk, or MCP) also fires a policy.created event to any configured endpoint - separate from policy.published, which fires when a policy is published to a live URL. content_html is not included in webhook payloads to keep delivery sizes small; use the generate endpoint's response or list policies to fetch full content.

Signature verification

Every webhook delivery includes an X-PolicifyAI-Signature header - an HMAC-SHA256 hex digest of the raw request body, keyed with your webhook secret. Always verify this signature before processing.

The same digest is also sent as X-PolicifyAI-Signature-256 in the prefixed sha256=<hex> form. Verify whichever you prefer - they cover identical bytes. Compare with a constant-time function (crypto.timingSafeEqual, hmac.compare_digest, hash_equals), never ===, and always hash the raw body rather than a re-serialised object.

Node.js

const crypto = require('crypto')

// Express middleware
app.post('/webhooks/policify', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-policifyai-signature']
  const expected = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex')

  const ok = signature?.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
  if (!ok) {
    return res.status(401).send('Invalid signature')
  }

  const event = JSON.parse(req.body)
  // Process event...
  res.sendStatus(200)
})

Python

import hmac
import hashlib

def verify_signature(payload: bytes, secret: str, signature: str) -> bool:
    expected = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

PHP

$payload = file_get_contents('php://input');
$signature = $_SERVER['HTTP_X_POLICIFYAI_SIGNATURE'];
$expected = hash_hmac('sha256', $payload, getenv('WEBHOOK_SECRET'));

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    exit;
}

Retries

PolicifyAI retries failed deliveries (non-2xx responses or timeouts) up to 5 times with exponential backoff:

Attempt 1

Immediate

Attempt 2

30 s

Attempt 3

2 min

Attempt 4

10 min

Attempt 5

1 hour

After 5 failed attempts the webhook is automatically disabled and you receive an email. You can re-enable it from the API & Webhooks page. Any delivery can be manually replayed from the delivery history.

Response requirements

Your endpoint must return a 2xx HTTP status within 10 seconds. If you need to do heavy processing, respond immediately with 200 OK and process asynchronously. Do not return 3xx redirects - PolicifyAI does not follow them.

Testing webhooks

In the Agency Hub, open the webhook detail page and click Send test event. This sends a policy.published payload with synthetic data to your endpoint so you can verify your handler works without needing to generate a real policy.