# 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:

```javascript
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:

```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:
> 
> * [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/optimizely-webhooks/examples/express/)
> * [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/optimizely-webhooks/examples/nextjs/)
> * [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/optimizely-webhooks/examples/fastapi/)

## 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>_<action>` 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

```bash
OPTIMIZELY_WEBHOOK_SECRET=your_webhook_secret_token   # Shown once, right after creating the webhook

```

## Local Development

```bash
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 optimizely --path /webhooks/optimizely

```

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/optimizely-webhooks/references/overview.md) - Optimizely webhook concepts and events
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/optimizely-webhooks/references/setup.md) - Creating the webhook and getting the secret
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/optimizely-webhooks/references/verification.md) - Signature details, test vector, gotchas