Guide to Statsig Webhooks: Features and Best Practices
Statsig Event Webhooks notify your systems about experimentation and configuration activity: a config or gate changes, an exposure is logged. If you're auditing config changes, syncing experiment data, or triggering workflows off Statsig activity, webhooks are how you react without polling.
This guide covers how Statsig's Event Webhook (the "Generic Webhook" integration) works, the events you'll handle, how to verify the X-Statsig-Signature, the batched payload shape, and the best practices for production.
What are Statsig webhooks?
Statsig's Event Webhook delivers HTTP POSTs to a destination URL you configure, each carrying one or more events as a JSON batch. Statsig signs deliveries with a Slack/Stripe-style HMAC scheme: an X-Statsig-Request-Timestamp header and an X-Statsig-Signature header computed over a versioned basestring.
Statsig webhook features
| Feature | Details |
|---|---|
| Configuration | Project Settings > Integrations > Generic Webhook |
| Signature headers | X-Statsig-Request-Timestamp, X-Statsig-Signature |
| Signature scheme | HMAC-SHA256 over v0:{timestamp}:{raw body}, hex, prefixed v0= |
| Signing secret | On the Webhook integration card in Project Settings |
| Payload | JSON batches (arrays); config changes wrapped as {"data": [...]} |
| Event filtering | Choose Exposures vs Config Changes |
| SDK | No official webhook-verification SDK |
Common events
Statsig events use a statsig:: prefix:
| Event | Fires when |
|---|---|
statsig::config_change | A config, gate, or experiment definition changes |
statsig::gate_exposure | A feature gate is evaluated (exposure) |
statsig::config_exposure | A dynamic config is evaluated |
statsig::experiment_exposure | An experiment is evaluated |
Config-change payloads carry type/name/description/action metadata (with action values like created, updated). Use Event Filtering to pick exposures vs config changes so you only receive what you process.
Setting up Statsig webhooks
Configure the webhook under Project Settings > Integrations > Generic Webhook: enter your destination URL, copy the signing secret from the integration card, and use Event Filtering to select Exposures and/or Config Changes.
Securing Statsig webhooks
Each delivery carries X-Statsig-Request-Timestamp and X-Statsig-Signature. To verify, build the basestring v0:{timestamp}:{raw body}, compute an HMAC-SHA256 with your signing secret, hex-encode it, prefix v0=, and compare against X-Statsig-Signature. Use the raw, unparsed body.
const crypto = require("crypto");
const SIGNING_SECRET = process.env.STATSIG_SIGNING_SECRET;
function verify(rawBody, timestamp, signature) {
const basestring = `v0:${timestamp}:${rawBody}`;
const expected = "v0=" +
crypto.createHmac("sha256", SIGNING_SECRET).update(basestring).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signature || "");
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const timestamp = req.headers["x-statsig-request-timestamp"];
const signature = req.headers["x-statsig-signature"];
if (!verify(req.body.toString("utf8"), timestamp, signature)) {
return res.sendStatus(401);
}
res.sendStatus(200); // acknowledge fast
const payload = JSON.parse(req.body);
const events = Array.isArray(payload) ? payload : payload.data || [];
for (const event of events) processQueue.add(event); // process asynchronously
});
The same check in Python:
import hashlib
import hmac
import os
SIGNING_SECRET = os.environ["STATSIG_SIGNING_SECRET"].encode()
def verify(raw_body: str, timestamp: str, signature: str) -> bool:
basestring = f"v0:{timestamp}:{raw_body}".encode()
expected = "v0=" + hmac.new(SIGNING_SECRET, basestring, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature or "")
Statsig webhook limitations and pain points
The basestring and prefix are specific
The Problem: The signature is over v0:{timestamp}:{body} (colon-separated, with a v0 version), and the header value is prefixed v0=. Signing only the body, or omitting the prefix, fails verification.
Why It Happens: Statsig uses a versioned Slack-style scheme rather than signing the body alone.
Workarounds:
- Build the exact
v0:{timestamp}:{raw body}basestring and compare against thev0=-prefixed value. - Use the raw body.
How Hookdeck Can Help: Hookdeck verifies the X-Statsig-Signature at the edge, so your app receives pre-verified events without reconstructing the basestring.
Batched array payloads
The Problem: Payloads are JSON batches, and config changes are wrapped as {"data": [...]} while exposures may arrive as bare arrays. A handler that expects one event per request mishandles the batch.
Why It Happens: Statsig batches events for delivery efficiency and wraps some payload types.
Workarounds:
- Normalize to an array (handle both the bare array and the
{"data": [...]}wrapper) and iterate.
How Hookdeck Can Help: Hookdeck can split batched payloads into individual events, so your app processes one clean event at a time.
Undocumented retry behavior
The Problem: Statsig doesn't document its retry behavior, so you can't assume a failed delivery will be retried on a known schedule.
Why It Happens: The retry semantics simply aren't published.
Workarounds:
- Acknowledge reliably and persist events on receipt; reconcile against Statsig's console/API for critical audit data.
How Hookdeck Can Help: Hookdeck durably queues every delivery and retries to your downstream on a schedule you control, giving you retry guarantees Statsig doesn't document.
No verification SDK
The Problem: There's no official SDK helper; the docs give HMAC pseudo-code, so you implement the check yourself.
Why It Happens: Statsig's SDKs target feature-flag evaluation, not webhook ingestion.
Workarounds:
- Use the constant-time HMAC verification above, centralized in one middleware.
How Hookdeck Can Help: Hookdeck verifies the signature at the edge, so your app receives pre-verified events without hand-rolled crypto.
Best practices
Verify the v0:{timestamp}:{body} basestring
Build the basestring, HMAC-SHA256 with your signing secret, hex-encode, prefix v0=, and compare in constant time against X-Statsig-Signature. Use the raw body.
Handle batches and both payload shapes
Normalize bare arrays and {"data": [...]} wrappers to a list and iterate every event.
Acknowledge fast, process asynchronously
Return 200 immediately and defer work to a queue. See why to process webhooks asynchronously.
Filter narrowly and persist
Use Event Filtering to receive only exposures or config changes you need, and persist events on receipt since retries aren't documented.
Make Statsig webhooks production-ready
Hookdeck verifies X-Statsig-Signature, splits batches, and durably queues every event
Conclusion
Statsig Event Webhooks deliver config-change and exposure activity as signed JSON batches, verified with an X-Statsig-Signature over the v0:{timestamp}:{body} basestring. The details that matter are the exact basestring and v0= prefix, handling the batched (and sometimes {"data": [...]}-wrapped) payloads, and persisting events since retry behavior isn't documented.
Hookdeck verifies the signature, splits batches, and durably queues every event at the edge, so your app only ever processes verified events with delivery guarantees Statsig doesn't provide.
Get started with Hookdeck for free and handle Statsig webhooks reliably in minutes.