PolicifyAI on Webhooks
Signed, retrying event delivery to any HTTPS endpoint.
PolicifyAI posts a signed JSON event to your endpoint whenever something happens - policy published, updated or deleted, consent recorded, DSAR submitted, cookie scan completed, integration connected/disconnected. Every delivery is HMAC-SHA256 signed, retried with exponential backoff (30s → 2m → 10m → 1h, up to 5 attempts), and logged with response code and payload preview you can replay. Available on all paid plans.
Set up in 10 minutes
10 minutes- 1Dashboard → Integrations → Webhooks → Add endpoint (HTTPS only).
- 2Copy the generated signing secret and store it server-side - it is shown once.
- 3Subscribe to the events you care about via checkboxes.
- 4Verify X-PolicifyAI-Signature on your receiver using the code below, then return 2xx within 5 seconds. Use "Send test" to confirm the wiring.
{
"event_id": "evt_1a2b3c4d5e",
"type": "policy.published",
"created_at": "2026-06-22T09:00:00Z",
"workspace_id": "ws_acme",
"api_version": "v1",
"data": {
"policy_id": "pol_abc123",
"policy_type": "privacy_policy",
"version": 4,
"jurisdictions": ["GB", "EU", "US-CA"],
"hosted_url": "https://policifyai.com/p/abc123"
}
}const crypto = require('crypto')
function verify(rawBody, header, secret) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
const a = Buffer.from(header || '', 'utf8')
const b = Buffer.from(expected, 'utf8')
return a.length === b.length && crypto.timingSafeEqual(a, b)
}
// Express: use express.raw({ type: 'application/json' }) so rawBody is intact.
app.post('/webhooks/policify', express.raw({ type: '*/*' }), (req, res) => {
if (!verify(req.body, req.header('X-PolicifyAI-Signature'), process.env.POLICIFY_SECRET)) {
return res.status(401).end()
}
const event = JSON.parse(req.body.toString())
// handle event.type ...
res.status(200).end()
})import hmac, hashlib
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = b"your_signing_secret"
@app.post("/webhooks/policify")
def policify():
raw = request.get_data() # raw bytes - do not use request.json first
sig = request.headers.get("X-PolicifyAI-Signature", "")
expected = hmac.new(SECRET, raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
event = request.get_json()
# handle event["type"] ...
return "", 200<?php
$secret = getenv('POLICIFY_SECRET');
$raw = file_get_contents('php://input');
$sig = $_SERVER['HTTP_X_POLICIFYAI_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $raw, $secret);
if (! hash_equals($expected, $sig)) {
http_response_code(401);
exit;
}
$event = json_decode($raw, true);
// handle $event['type'] ...
http_response_code(200);package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"io"
"net/http"
"os"
)
func handler(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
mac := hmac.New(sha256.New, []byte(os.Getenv("POLICIFY_SECRET")))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))
if !hmac.Equal([]byte(expected), []byte(r.Header.Get("X-PolicifyAI-Signature"))) {
w.WriteHeader(http.StatusUnauthorized)
return
}
w.WriteHeader(http.StatusOK)
}require 'openssl'
post '/webhooks/policify' do
raw = request.body.read
sig = request.env['HTTP_X_POLICIFYAI_SIGNATURE'].to_s
expected = OpenSSL::HMAC.hexdigest('SHA256', ENV['POLICIFY_SECRET'], raw)
halt 401 unless Rack::Utils.secure_compare(expected, sig)
event = JSON.parse(raw)
# handle event['type'] ...
status 200
endWhy PolicifyAI
- Per-endpoint signing secret + timing-safe verification examples in five languages.
- Exponential-backoff retries with replay - not fire-and-forget like many CMP webhooks.
- Delivery log with response code and payload preview, plus a health score per endpoint.
Frequently asked
What events are available?+
policy.published, policy.updated, policy.deleted, consent.recorded, consent_banner_shown, dsar.submitted, cookie_scan.completed, integration.connected, integration.disconnected - with more added over time.
How do you prevent replay attacks?+
Each event carries a unique event_id and created_at timestamp. Reject events older than your tolerance window and de-dupe on event_id (idempotency).
Which plans include webhooks?+
All paid plans. Free accounts can browse the docs but need to upgrade to add endpoints.