Guide to Flagsmith Webhooks: Features and Best Practices
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
| Feature | Details |
|---|---|
| Configuration | Environment settings (environment webhooks), organisation settings (audit log webhooks), integrations (identity webhooks) |
| Subscription model | No event filter. Each webhook receives every event of its kind |
| Method and format | POST, application/json |
| Signature header | X-Flagsmith-Signature, only when a secret is set |
| Scheme | HMAC-SHA256, hex digest |
| Signed content | The raw request body, nothing else |
| Secret | Optional, chosen by you, used as a UTF-8 string |
| Replay window | None. No timestamp is sent or signed |
| Timeout and retries | Not 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":
| Trigger | event_type |
|---|---|
| Creating a new feature | FLAG_UPDATED |
| Updating a feature value or state in an environment | FLAG_UPDATED |
| Overriding a feature for an identity | FLAG_UPDATED |
| Overriding a feature for a segment | FLAG_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_, 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_, 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_, describing what one user saw at evaluation time.
Setting up Flagsmith webhooks
- Generate a long random secret, for example with
openssl rand -hex 32, and store it server-side asFLAGSMITH_WEBHOOK_SECRET. Flagsmith does not generate one for you. - 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.
- Enter your endpoint URL and the secret.
- Change a flag in a development environment to send a
FLAG_UPDATEDevent. 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
2XXas 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_RETRIESandWEBHOOK_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_stateandprevious_staterather thanevent_type. - Treat a missing
previous_stateas a newly created state.
How Hookdeck Can Help: Hookdeck filters can route on any body field, for example sending events where data.new_ 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.timestampto 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.