Gareth Wilson Gareth Wilson

Guide to Flagsmith Webhooks: Features and Best Practices

Published


Flagsmith webhooks tell your systems when feature flags change. When someone turns a flag on in production, changes a remote config value, or adds an override for a segment, Flagsmith can post the change to your endpoint, so you can refresh caches, keep other systems in sync, record changes for compliance, or start a pipeline the moment a release flag flips.

This guide covers Flagsmith's three kinds of webhook, their payloads, how they are signed and verified, what happens when your endpoint fails, and the best practices for running a Flagsmith receiver in production.

What are Flagsmith webhooks?

Flagsmith has three webhook products, configured in three places:

  • Environment webhooks send flag change events for one environment. You can add any number per environment, and "each event will generate an HTTP POST to all configured webhook URLs".
  • Audit log webhooks stream every entry in your organisation's audit log, as it happens.
  • Identity webhooks are an integration that sends the full set of flags, traits and segments for a user every time an SDK evaluates that identity through the Get Identity Flags endpoint.

All three send a JSON POST, and all three use the same optional signing. If you set a secret on the webhook, Flagsmith adds an X-Flagsmith-Signature header containing a hex HMAC-SHA256 of the raw body.

Flagsmith webhook features

FeatureDetails
ConfigurationEnvironment settings (environment webhooks), organisation settings (audit log webhooks), integrations (identity webhooks)
Subscription modelNo event filter. Each webhook receives every event of its kind
Method and formatPOST, application/json
Signature headerX-Flagsmith-Signature, only when a secret is set
SchemeHMAC-SHA256, hex digest
Signed contentThe raw request body, nothing else
SecretOptional, chosen by you, used as a UTF-8 string
Replay windowNone. No timestamp is sent or signed
Timeout and retriesNot documented. Flagsmith's code uses a 10 second timeout and retries connection failures only

Flagsmith publishes no source IP list for webhooks. If you self-host Flagsmith, the requests come from your own servers.

Common events

Flagsmith's docs list four triggers for environment webhooks and say they are "all sent as event_type: FLAG_UPDATED":

Triggerevent_type
Creating a new featureFLAG_UPDATED
Updating a feature value or state in an environmentFLAG_UPDATED
Overriding a feature for an identityFLAG_UPDATED
Overriding a feature for a segmentFLAG_UPDATED

Here is Flagsmith's environment webhook example, with previous_state trimmed:

{
  "data": {
    "changed_by": "user@domain.com",
    "new_state": {
      "enabled": true,
      "environment": { "id": 23, "name": "Development" },
      "feature": {
        "created_date": "2021-02-10T20:03:43.348556Z",
        "default_enabled": false,
        "description": "Show html in a butter bar for certain users",
        "id": 7168,
        "initial_value": null,
        "name": "butter_bar",
        "project": { "id": 12, "name": "Flagsmith Website" },
        "type": "CONFIG"
      },
      "feature_segment": null,
      "feature_state_value": "<strong>\nYou are using the develop environment.\n</strong>",
      "identity": null,
      "identity_identifier": null,
      "multivariate_feature_state_values": [
        {
          "id": 1,
          "multivariate_feature_option": { "id": 10, "value": "variant_a" },
          "percentage_allocation": 50
        }
      ]
    },
    "previous_state": { "enabled": false },
    "timestamp": "2021-06-18T07:50:26.595298Z"
  },
  "event_type": "FLAG_UPDATED"
}

Compare new_state with previous_state to see what changed. A segment override sets feature_segment, and an identity override sets identity and identity_identifier, so the same event_type covers four different changes.

Flagsmith's open-source API code goes further than its docs. It also defines FLAG_DELETED, sent with new_state set to null and the deleted state in previous_state, and NEW_VERSION_PUBLISHED for environments using feature versioning v2. Handle unknown event_type values with a 200 rather than an error.

Audit log webhooks carry an audit log entry: created_date, a human-readable log such as "New Flag / Remote Config created: my_feature", the author, the environment and project, and related_object_type (FEATURE, FEATURE_STATE, SEGMENT, ENVIRONMENT and others) with related_object_id or related_object_uuid. Flagsmith's docs show that entry as the whole body. Its code wraps it as {"event_type": "AUDIT_LOG_CREATED", "data": {...}}, and also sends flag change events to organisation webhooks, so check for event_type before assuming the shape.

Identity webhooks have no event_type. The body is { "flags": [...], "identity": "user_test", "segments": [...], "traits": [...] }, describing what one user saw at evaluation time.

Setting up Flagsmith webhooks

  1. Generate a long random secret, for example with openssl rand -hex 32, and store it server-side as FLAGSMITH_WEBHOOK_SECRET. Flagsmith does not generate one for you.
  2. Add the webhook where it belongs:
    • For flag changes, open the environment's settings and add a webhook.
    • For the audit log, add the webhook in your organisation settings.
    • For identity evaluations, add the Webhook integration.
  3. Enter your endpoint URL and the secret.
  4. Change a flag in a development environment to send a FLAG_UPDATED event. Flagsmith's web app also has a test webhook action that sends a sample body.

Give each webhook product its own URL. Identity and audit log bodies are shaped differently, and the path is the most reliable way to tell them apart.

For local development, the Hookdeck CLI (hookdeck listen 3000 flagsmith --path /webhooks/flagsmith) gives you a public HTTPS URL that forwards to your local server, with no account required.

Securing Flagsmith webhooks

Read X-Flagsmith-Signature, compute an HMAC-SHA256 of the raw request body with your secret as a UTF-8 key, encode it as hex and compare in constant time. The scheme is the same for all three webhook products.

const crypto = require('crypto');
const express = require('express');

function verifyFlagsmithSignature(rawBody, signatureHeader, secret) {
  if (!signatureHeader || !secret) return false;
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  const received = Buffer.from(signatureHeader.trim().toLowerCase(), 'utf8');
  const computed = Buffer.from(expected, 'utf8');
  return received.length === computed.length && crypto.timingSafeEqual(received, computed);
}

const app = express();

app.post('/webhooks/flagsmith', express.raw({ type: '*/*' }), (req, res) => {
  if (!verifyFlagsmithSignature(req.body, req.get('x-flagsmith-signature'), process.env.FLAGSMITH_WEBHOOK_SECRET)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  // Route on event.event_type; acknowledge unknown types with a 200.
  res.sendStatus(200);
});

The same check in Python:

import hashlib
import hmac
import os


def verify_flagsmith_signature(raw_body: bytes, signature_header, secret: str) -> bool:
    if not signature_header or not secret:
        return False
    expected = hmac.new(secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
    received = signature_header.strip().lower().encode("utf-8")
    return hmac.compare_digest(received, expected.encode("utf-8"))


# In FastAPI: raw_body = await request.body()
# verify_flagsmith_signature(raw_body, request.headers.get("x-flagsmith-signature"),
#                            os.environ["FLAGSMITH_WEBHOOK_SECRET"])

To check your verifier, use the secret flagsmith-test-secret and this exact body:

{"data": {"changed_by": "user@domain.com", "timestamp": "2021-06-18T07:50:26.595298Z"}, "event_type": "FLAG_UPDATED"}

The signature should be:

ba8bb51f9101d22982647a9e379f6f5cf7be107b0880b76c6f7fcc8c27880ab9

Flagsmith serialises the body with Python's json.dumps and sorted keys, so it has a space after every : and ,. That is one more reason to hash the bytes you received rather than re-serialised JSON.

Make Flagsmith webhooks production-ready. Hookdeck Event Gateway verifies X-Flagsmith-Signature, accepts every flag change within Flagsmith's 10 second timeout, and durably queues it before your service sees it.

Flagsmith webhook limitations and pain points

A failed response is not retried

The Problem: Your endpoint returned a 500 during a deploy, and the flag change it carried never arrives again.

Why It Happens: Flagsmith's docs do not describe retries. In its open-source code, environment and audit log webhooks are retried only when the request itself fails, such as a connection error or a timeout. A response with a non-2xx status is logged and not retried. Retries run only when Flagsmith uses its task processor, with three attempts and an exponentially growing delay by default, and the organisation is emailed after the last one fails.

Workarounds:

  • Return 2XX as soon as the request is verified and stored, and do the real work afterwards.
  • After an outage, re-read flag states from the Flagsmith API rather than trusting that you saw every change.
  • If you self-host, the retry count and backoff are configurable with WEBHOOK_BACKOFF_RETRIES and WEBHOOK_BACKOFF_BASE.

How Hookdeck Can Help: Hookdeck accepts each request as soon as it arrives, so Flagsmith sees a success, and then retries delivery to your service on your own schedule. Anything that failed can be replayed once you have fixed the problem.

One event type for four kinds of change

The Problem: Your handler cannot tell a new feature from a segment override.

Why It Happens: Every documented environment trigger is sent as FLAG_UPDATED. The difference is only in the data: whether previous_state exists, and whether feature_segment or identity is set.

Workarounds:

  • Classify changes by inspecting new_state and previous_state rather than event_type.
  • Treat a missing previous_state as a newly created state.

How Hookdeck Can Help: Hookdeck filters can route on any body field, for example sending events where data.new_state.feature_segment is not null to a segment-aware consumer.

No secret, no signature

The Problem: Requests arrive without X-Flagsmith-Signature.

Why It Happens: The secret is optional on every webhook, and without it Flagsmith sends no signature.

Workarounds:

  • Always set a secret and reject unsigned requests.
  • Set it on all three webhook products, since each has its own.

How Hookdeck Can Help: Hookdeck verifies the signature when you give the source your secret. If you didn't set a secret in Flagsmith, leave verification off on the source.

Identity webhooks are high volume

The Problem: Your endpoint is flooded once identity webhooks are turned on.

Why It Happens: An identity webhook fires every time an SDK calls Get Identity Flags, which can be every page load or app start. It is evaluation telemetry, not change notification.

Workarounds:

  • Point identity webhooks at a separate endpoint that only writes to a queue or warehouse.
  • Batch inserts downstream instead of processing each request inline.

How Hookdeck Can Help: Hookdeck absorbs the burst and can cap the delivery rate to your destination, so your warehouse loader sees a steady stream instead of your traffic peaks.

No timestamp means no replay protection

The Problem: A captured request verifies if it is sent again later.

Why It Happens: The signature covers only the body, and no timestamp header is sent.

Workarounds:

  • Make handlers idempotent.
  • Use the body's data.timestamp to ignore a change older than the state you already hold.

How Hookdeck Can Help: Hookdeck's deduplication can drop repeated requests based on fields you choose, such as data.timestamp and the feature ID.

Best practices

Verify against the raw body

Mount express.raw({ type: '*/*' }) in Express, use await request.text() in a Next.js route handler, or read await request.body() in FastAPI. Parse only after verification.

Use one URL per webhook product

Environment, audit log and identity bodies have different shapes, and identity bodies have no event_type at all. A path per product, such as /webhooks/flagsmith/environment, keeps the routing obvious.

Treat flag changes as cache invalidations

The most common use of environment webhooks is refreshing a flag cache. Use the webhook to know that something changed, and fetch the current environment state from Flagsmith if you need it to be complete, especially after any period of downtime.

Build your own idempotency key

There is no event ID. For environment webhooks, combine the environment ID, feature ID, any segment or identity, and data.timestamp. See our guide to webhook idempotency.

Acknowledge fast and process asynchronously

Flagsmith's code waits 10 seconds and does not follow redirects. Verify, store and respond, then process from a queue. See why to process webhooks asynchronously.

Conclusion

Flagsmith sends environment, audit log and identity webhooks, all signed the same way when you set a secret. Verify X-Flagsmith-Signature against the raw bytes, give each product its own URL, compare new_state with previous_state to see what changed, and respond with a 2XX quickly, because a failed response is not sent again.

Hookdeck Event Gateway checks every signature, accepts each flag change straight away and holds it until your service has processed it, so a deploy on your side does not leave a stale flag in your cache.

Get started with Hookdeck for free and handle Flagsmith 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.