Agent skill
Optimizely Webhooks Skill
Receive and verify Optimizely Feature Experimentation webhooks. Use when setting up Optimizely webhook handlers, debugging X-Hub-Signature verification, or handling project.datafile_updated and project.ruleset_updated events.
Install this skill
npx skills add hookdeck/webhook-skills --skill optimizely-webhooks
When to Use This Skill
- How do I receive Optimizely Feature Experimentation webhooks?
- How do I verify the Optimizely
X-Hub-Signatureheader? - How do I handle
project.datafile_updatedto refresh my SDK datafile? - Why is my Optimizely webhook signature verification failing?
Scope: Feature Experimentation project webhooks only (not Web Experimentation or the CMS products).
Verification (core)
Optimizely sends X-Hub-Signature: sha1=<hex>: the hex HMAC-SHA1 of the raw request body, keyed with the webhook's secret token (used as UTF-8 bytes, not decoded). Nothing else is signed, so there is no timestamp or replay window to check. Validate the format (40 hex characters), then compare in constant time.
Node:
const crypto = require('crypto');
function verify(rawBody /* Buffer */, header, secret) {
if (!header || !secret) return false;
const sig = header.startsWith('sha1=') ? header.slice(5) : header;
if (!/^[0-9a-fA-F]{40}$/.test(sig)) return false;
const expected = crypto.createHmac('sha1', secret).update(rawBody).digest();
return crypto.timingSafeEqual(Buffer.from(sig, 'hex'), expected);
}
Python:
import hashlib, hmac, re
def verify(raw_body: bytes, header: str | None, secret: str) -> bool:
if not header or not secret:
return False
sig = header.removeprefix("sha1=")
if not re.fullmatch(r"[0-9a-fA-F]{40}", sig):
return False
expected = hmac.new(secret.encode(), raw_body, hashlib.sha1).digest()
return hmac.compare_digest(bytes.fromhex(sig), expected)
Reject a missing, malformed or mismatched signature with 401. In Express use express.raw({ type: '*/*' }) so the exact bytes are verified; in Next.js use await request.arrayBuffer(); in FastAPI use await request.body(). Parse JSON only after the check.
For complete handlers with route wiring, event dispatch, and tests, see:
Common Event Types
Dispatch on the top-level event field of the body.
| Event | Description |
|---|---|
project.datafile_updated | An environment's datafile changed (data is an object with revision, cdn_url, origin_url, environment) |
project.ruleset_updated | A change-history notification (data is an array of change records with change_type and entity.type) |
Other change-history events (entities such as Attribute, Audience, Flag, Rule × Create, Update, Archive, Delete) follow the project.<entity>_ pattern, but the docs list only the two values above by name. Log unknown events and return 200.
Important Headers
| Header | Description |
|---|---|
X-Hub-Signature | sha1= followed by the hex HMAC-SHA1 of the raw body |
Environment Variables
OPTIMIZELY_WEBHOOK_SECRET=your_webhook_secret_token # Shown once, right after creating the webhook
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 optimizely --path /webhooks/optimizely
Reference Materials
- references/overview.md - Optimizely webhook concepts and events
- references/setup.md - Creating the webhook and getting the secret
- references/verification.md - Signature details, test vector, gotchas