Gareth Wilson Gareth Wilson

Guide to Statsig Webhooks: Features and Best Practices

Published


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

FeatureDetails
ConfigurationProject Settings > Integrations > Generic Webhook
Signature headersX-Statsig-Request-Timestamp, X-Statsig-Signature
Signature schemeHMAC-SHA256 over v0:{timestamp}:{raw body}, hex, prefixed v0=
Signing secretOn the Webhook integration card in Project Settings
PayloadJSON batches (arrays); config changes wrapped as {"data": [...]}
Event filteringChoose Exposures vs Config Changes
SDKNo official webhook-verification SDK

Common events

Statsig events use a statsig:: prefix:

EventFires when
statsig::config_changeA config, gate, or experiment definition changes
statsig::gate_exposureA feature gate is evaluated (exposure)
statsig::config_exposureA dynamic config is evaluated
statsig::experiment_exposureAn 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 the v0=-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.


Gareth Wilson

Gareth Wilson

Product Marketing

Multi-time founding marketer, Gareth is PMM at Hookdeck and author of the newsletter, Community Inc.