Skip to content

Webhooks & Cache Synchronization

Webhooks allow FlagPal to notify your application in real time whenever feature flags, experiments, or experiences are created, updated, or deleted.

By pairing local caching in your application with FlagPal webhooks, you achieve the highest possible throughput and sub-millisecond flag resolution while ensuring changes take effect immediately across your entire infrastructure.


Why Use Webhooks?

The Performance Dilemma: API Latency vs. Cache Freshness

In high-traffic production environments, every millisecond counts:

  1. Direct API calls on every request: Querying FlagPal's REST API on every incoming HTTP request or user interaction adds network roundtrip latency (typically 20–100ms) and creates an external dependency that can bottleneck your application.
  2. Time-To-Live (TTL) caching without webhooks: Caching feature flags locally in Redis or in-memory storage eliminates API latency. However, with simple TTL caching, any critical change made in FlagPal (such as toggling an emergency kill-switch, disabling a faulty experience, or adjusting experiment variant weights) will not take effect until the cache TTL expires. Short TTLs still hit the API frequently, while long TTLs cause unacceptable synchronization lag.

The Solution: Cache Locally, Invalidate via Webhooks

Webhooks provide an optimal synchronization model:

  • Local sub-millisecond evaluation: Your application caches feature flags, experiences, and experiments in fast local storage (e.g., Redis, APCu, or memory).
  • Zero-lag updates: The moment you create, toggle, or edit a flag or experiment in the FlagPal dashboard, FlagPal immediately pushes a signed webhook event to your application.
  • Instant cache invalidation: Your application receives the webhook, verifies the signature, and instantly invalidates or re-hydrates its local cache.
┌─────────────────┐                     ┌────────────────────────┐
│     FlagPal     │─── Webhook Event ──▶│   Customer App / API   │
│    Dashboard    │  (Instant on change)│                        │
└─────────────────┘                     └───────────┬────────────┘
                                                    │
                                             Bust / Refresh
                                                    │
                                                    ▼
                                        ┌────────────────────────┐
                                        │  Local Cache (Redis)   │
                                        │ (Sub-millisecond read) │
                                        └────────────────────────┘

Supported Webhook Events

FlagPal dispatches webhooks for the following lifecycle events:

Event Name Trigger Typical Payload Resource
feature.created A new feature flag is created. features
feature.updated A feature flag is updated (e.g., name, rules, default value changed). features
feature.deleted A feature flag is deleted. features
funnel.created An Experience or Experiment is created. funnels (with included feature-sets)
funnel.updated An Experience or Experiment is updated, active status toggled, or variants modified. funnels (with included feature-sets)
funnel.deleted An Experience or Experiment is deleted. funnels (with included feature-sets)

Note on Funnels: FlagPal uses funnel.* events for both Experiences and Experiments. The specific type is identified by the attributes.kind property ("experiment" or "experience"). This allows downstream applications to maintain and invalidate a unified cache across all targeting rules.


Webhook Payload Structure

FlagPal formats all webhook payloads according to the JSON:API v1 specification, guaranteeing complete consistency with our public REST API endpoints.

Standard Webhook Envelope

Every webhook request sent by FlagPal contains a top-level envelope with metadata, followed by the JSON:API resource document:

{
  "event": "funnel.updated",
  "project": {
    "id": 1,
    "ulid": "01JM7G0D2Z...",
    "name": "Production App"
  },
  "timestamp": "2026-09-11T11:48:00.000000Z",
  "data": {
    "type": "funnels",
    "id": "01JM7G1A5B...",
    "attributes": {
      "name": "Checkout Flow Test",
      "kind": "experiment",
      "active": true,
      "percent": 100,
      "weight": 10,
      "rules": []
    },
    "relationships": {
      "featureSets": {
        "data": [
          { "type": "feature-sets", "id": "01JM7G2C3D..." },
          { "type": "feature-sets", "id": "01JM7G3E4F..." }
        ]
      }
    }
  },
  "included": [
    {
      "type": "feature-sets",
      "id": "01JM7G2C3D...",
      "attributes": {
        "name": "Control",
        "weight": 50,
        "control": true,
        "features": []
      }
    },
    {
      "type": "feature-sets",
      "id": "01JM7G3E4F...",
      "attributes": {
        "name": "One-Click Checkout",
        "weight": 50,
        "control": false,
        "features": [
          {
            "key": "enable_one_click",
            "type": "boolean",
            "value": true
          }
        ]
      }
    }
  ]
}

Configuring Webhooks in FlagPal

A project can have any number of webhooks. Each webhook requires both a Webhook URL and a Webhook Secret, and every event is delivered to every configured webhook, each signed with its own secret.

Step 1: Open Project Settings

  1. In the FlagPal dashboard, click on your project name or navigate to Project Settings.
  2. Locate the Webhooks section and click Add Webhook.

Step 2: Configure Endpoint and Secret

  1. Webhook URL: Enter the publicly accessible HTTPS endpoint on your server that will receive the webhook POST requests (e.g., https://api.example.com/api/webhooks/flagpal).
  2. Webhook Secret: Enter a strong, random secret string (e.g., a 32-character random string). FlagPal encrypts this secret at rest in the database and uses it to generate HMAC-SHA256 signatures for every request dispatched to this webhook.

Repeat for every additional endpoint you want to notify. Webhooks can be edited or removed from the same section at any time.

Step 3: Save Settings

Click Save. Webhooks are now active and will automatically fire whenever feature flags, experiences, or experiments are modified.


Verifying Webhook Signatures

To ensure that incoming webhook requests originate from FlagPal and have not been tampered with or forged, FlagPal calculates an HMAC-SHA256 signature for each payload and transmits it in the Signature HTTP header.

How Verification Works

  1. FlagPal computes the signature using your shared secret: $\(\text{Signature} = \text{hash\_hmac}('sha256', \text{rawRequestBody}, \text{webhookSecret})\)$
  2. FlagPal sends this value in the Signature header.
  3. Your application computes the same hash over the raw, unparsed request body and compares it against the header using a constant-time comparison (to prevent timing attacks).

Implementation Examples

PHP / Laravel

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Cache;

class FlagpalWebhookController extends Controller
{
    public function handle(Request $request): Response
    {
        $signature = $request->header('Signature');
        $secret = config('services.flagpal.webhook_secret');
        $rawPayload = $request->getContent();

        $computedSignature = hash_hmac('sha256', $rawPayload, $secret);

        if (! hash_equals($computedSignature, (string) $signature)) {
            return response('Invalid signature', 401);
        }

        $event = $request->input('event');
        $data = $request->input('data');

        // Invalidate or update local cache
        if (str_starts_with($event, 'feature.')) {
            Cache::forget('flagpal:features');
        } elseif (str_starts_with($event, 'funnel.')) {
            Cache::forget('flagpal:funnels');
        }

        return response('Webhook processed', 200);
    }
}

Node.js / Express (TypeScript)

import express, { Request, Response } from 'express';
import crypto from 'crypto';

const app = express();

// Use express.raw or express.json with verify to capture raw body buffer
app.post(
  '/api/webhooks/flagpal',
  express.raw({ type: 'application/json' }),
  (req: Request, res: Response) => {
    const signature = req.headers['signature'] as string;
    const secret = process.env.FLAGPAL_WEBHOOK_SECRET || '';
    const rawBody = req.body.toString('utf8');

    const expectedSignature = crypto
      .createHmac('sha256', secret)
      .update(rawBody)
      .digest('hex');

    const isValid =
      signature &&
      crypto.timingSafeEqual(
        Buffer.from(signature),
        Buffer.from(expectedSignature)
      );

    if (!isValid) {
      return res.status(401).send('Invalid signature');
    }

    const payload = JSON.parse(rawBody);

    // Bust local cache based on event type
    if (payload.event.startsWith('feature.')) {
      invalidateLocalCache('features');
    } else if (payload.event.startsWith('funnel.')) {
      invalidateLocalCache('funnels');
    }

    return res.status(200).send('OK');
  }
);

Python / FastAPI

import hmac
import hashlib
from fastapi import FastAPI, Request, HTTPException, Header

app = FastAPI()

FLAGPAL_WEBHOOK_SECRET = "your-secret-here".encode("utf-8")

@app.post("/api/webhooks/flagpal")
async def handle_flagpal_webhook(
    request: Request,
    signature: str = Header(None, alias="Signature")
):
    body = await request.body()

    if not signature:
        raise HTTPException(status_code=401, detail="Missing signature header")

    computed_signature = hmac.new(
        FLAGPAL_WEBHOOK_SECRET,
        body,
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(computed_signature, signature):
        raise HTTPException(status_code=401, detail="Invalid signature")

    payload = await request.json()
    event = payload.get("event", "")

    # Invalidate local cache
    if event.startswith("feature."):
        invalidate_cache("features")
    elif event.startswith("funnel."):
        invalidate_cache("funnels")

    return {"status": "ok"}

Best Practices

1. Always Verify the Signature

Never process a webhook without verifying the HMAC-SHA256 signature. This guarantees authenticity and prevents unauthorized parties from triggering artificial cache invalidations or injecting false configurations.

2. Acknowledge Quickly (Fast Response)

Return an HTTP 200 or 202 status code immediately upon receiving and verifying the webhook. If your application needs to perform extensive re-fetching or warm-up routines, dispatch an internal background job or queue worker rather than holding the webhook HTTP connection open.

3. Implement Automatic Retries Handling

FlagPal's webhook dispatcher uses exponential backoff retries if your endpoint fails or times out. Ensure your webhook handler is idempotent so that processing the same event multiple times produces identical state without side effects.

4. Use HTTPS Endpoints

Always use valid HTTPS URLs for your webhook endpoint in staging and production to protect payload data in transit.