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-Signature header?
  • How do I handle project.datafile_updated to 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.

EventDescription
project.datafile_updatedAn environment's datafile changed (data is an object with revision, cdn_url, origin_url, environment)
project.ruleset_updatedA 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>_<action> pattern, but the docs list only the two values above by name. Log unknown events and return 200.

Important Headers

HeaderDescription
X-Hub-Signaturesha1= 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 12, 2026

View on GitHub →