Agent skill
Exa Webhooks Skill
Receive and verify Exa Websets webhooks. Use when setting up Exa webhook handlers, debugging Exa-Signature verification failures, or handling Websets events like webset.created, webset.idle, webset.search.completed, webset.item.created, or webset.item.enriched.
Install this skill
npx skills add hookdeck/webhook-skills --skill exa-webhooks
When to Use This Skill
- How do I receive Exa Websets webhooks?
- How do I verify the
Exa-Signatureheader? - How do I handle
webset.item.created,webset.item.enriched, orwebset.idleevents? - Why is my Exa webhook signature verification failing?
- Where do I find my Exa webhook secret?
Essential Code (USE THIS)
Exa signs every delivery with HMAC-SHA256. The Exa-Signature header looks like t=<unix seconds>,v1=<hex> and may carry several v1 values; the request is valid if any one matches. The signed content is "<t>.<raw body>", and the key is the webhook secret as a UTF-8 string (returned only in the response that creates the webhook, so store it then).
Verification (Node.js)
const crypto = require('crypto');
function safeEqual(a, b) {
const x = Buffer.from(a), y = Buffer.from(b);
return x.length === y.length && crypto.timingSafeEqual(x, y);
}
// rawBody: Buffer of the exact request bytes (express.raw({ type: '*/*' }), not express.json)
function verifyExa(rawBody, header, secret) {
const pairs = header.split(',').map((kv) => kv.trim().split(/=(.*)/s).slice(0, 2));
const t = pairs.find(([k]) => k === 't')?.[1];
const sigs = pairs.filter(([k]) => k === 'v1').map(([, v]) => v);
if (!t || sigs.length === 0) return false;
// Optional replay window, off by default: retries and replays keep the original timestamp
const expected = crypto.createHmac('sha256', secret)
.update(`${t}.`).update(rawBody).digest('hex');
return sigs.some((s) => safeEqual(s, expected));
}
Verification (Python)
import hashlib, hmac
def verify_exa(raw_body: bytes, header: str, secret: str) -> bool:
t, sigs = None, []
for kv in header.split(","):
k, _, v = kv.strip().partition("=")
if k == "t":
t = v
elif k == "v1":
sigs.append(v)
if not t or not sigs:
return False
expected = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(s, expected) for s in sigs)
Exa documents no SDK helper for this, so verify manually. Always use the raw body; re-serialised JSON will not match.
The timestamp check is opt-in: Hookdeck retries and replays keep the original timestamp, so a freshness window would reject legitimate redeliveries. The examples skip it unless EXA_ is set to a positive number of seconds (default 0 = off).
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Common Event Types
The event arrives as JSON: { "id", "object": "event", "type", "data", "createdAt" }, where data is the full resource.
| Event | Description |
|---|---|
webset.created | A Webset was created |
webset.idle | A Webset has no running operations |
webset.search.completed | A search finished finding all items |
webset.item.created | An item was added to the Webset |
webset.item.enriched | An item's enrichment completed |
Others: webset.deleted, webset.paused, webset.search.created, webset.search.updated, webset.search.canceled, import.created, import.completed, webset.export.created, webset.export.completed, monitor.created, monitor.updated, monitor.deleted, monitor.run.created, monitor.run.completed. See references/overview.md.
For the full event reference, see Exa event types.
Important Headers
| Header | Description |
|---|---|
Exa-Signature | t=<unix seconds>,v1=<hex>[,v1=<hex>...] |
Environment Variables
EXA_WEBHOOK_SECRET=your_webhook_secret # "secret" from the create-webhook response
EXA_TIMESTAMP_TOLERANCE_SECONDS=0 # optional; a positive value opts in to the replay window
Local Development
# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 exa --path /webhooks/exa
Reference Materials
- references/overview.md - Exa webhook concepts and events
- references/setup.md - Creating a webhook through the API
- references/verification.md - Signature verification details