Gareth Wilson Gareth Wilson

Guide to Signal Sciences Webhooks: Features and Best Practices

Published


Signal Sciences, now Fastly Next-Gen WAF, can notify your systems when something happens in a protected workspace: an IP address is flagged for attacking your site, an agent raises an alert, or someone changes the agent mode, a rule or a list. Its generic webhook integration is how you get those notifications into your own tooling, such as an incident channel, a SIEM or an audit log, rather than relying on someone watching the dashboard.

This guide covers how to set up the generic webhook in both of Fastly's control panels, how to verify the X-SigSci-Signature header, the activity types you can subscribe to, and what Fastly leaves undocumented.

What are Signal Sciences webhooks?

Signal Sciences is now Fastly Next-Gen WAF, but the webhook still carries the old name: the signature header is X-SigSci-Signature. Hookdeck's source type is named Signal Sciences for the same reason.

The generic webhook is an alert integration configured per workspace, which the older control panel calls a site. When a selected activity happens, Next-Gen WAF sends a JSON notification to your URL, signed with a key generated when you created the integration.

What you can subscribe to depends on which control panel you use. In the Fastly control panel, the alert form offers Flagged IPs. In the legacy Next-Gen WAF control panel, you can choose all activity or pick from 32 activity types.

Signal Sciences webhook features

FeatureDetails
ConfigurationPer workspace, in the Fastly control panel or the legacy Next-Gen WAF control panel
SubscriptionFlagged IPs (Fastly control panel), or all activity or chosen activity types (legacy panel)
Signature headerX-SigSci-Signature
SchemeHMAC-SHA256, hex digest, no prefix
Signed contentThe JSON payload
SecretA signing key per integration, generated on creation
Key rotationRotate replaces the key straight away
Activity identifiertype in the body
Retries and timeoutNot documented
Source IPsNot published

Common events

The type field names the activity. These are the activity types the legacy control panel lets you choose:

AreaActivity typesFires when
Attacksflag, expireFlagAn IP address was flagged, or a flag was manually expired
AgentsagentAlert, loggingModeChanged, agentAnonModeChangedAn agent alert was triggered, or the agent mode (Blocking, Not Blocking, Off) or IP anonymisation mode changed
WorkspacesiteDisplayNameChanged, siteNameChangedThe workspace's display name or short name changed
RulesruleCreated, ruleUpdated, ruleDeleted, detectionCreated, detectionUpdated, detectionDeletedA request rule or templated rule was created, updated or removed
Lists and signalslistCreated, listUpdated, listDeleted, customTagCreated, customTagUpdated, customTagDeletedA list or custom signal was created, updated or removed
Alerts and redactionscustomAlertCreated, customAlertUpdated, customAlertDeleted, createCustomRedaction, updateCustomRedaction, removeCustomRedactionA custom alert or custom redaction changed
DashboardscustomDashboard* and customDashboardWidget*A custom dashboard or one of its cards was created, updated, reset or removed

Fastly documents one notification example:

{
  "created": "2024-02-09T21:08:21.31644377Z",
  "type": "flag",
  "payload": {
    "ID": "abcde123456789",
    "CorpID": "123456789abcde",
    "SiteID": "987654321fedcba",
    "Scope": "site",
    "Slug": "site.example",
    "Name": "example",
    "Description": "Webhook integration for Example site",
    "CreatedBy": "admin@example.com",
    "CreatedByID": "edcba987654321",
    "Created": "2024-02-09T21:08:21.31644377Z",
    "Updated": "2025-01-07T21:06:31.50725289Z",
    "ResourceID": "ASDFghjkl123456789"
  },
  "link": "dashboard link to event",
  "workspaceId": "www.mysite.com"
}

payload holds the object the activity concerns, and its shape varies by type. Fastly's activity table points to the matching Next-Gen WAF API response for some of them: "Get event by ID" for flag, "Get site by name" for loggingModeChanged, and the create, update or remove response for lists, custom alerts and redactions. Many types, including agentAlert and the rule types, have no payload reference at all. The example above looks more like an integration record than a flagged-IP event, so don't build a parser from it alone. Capture real notifications first.

Setting up Signal Sciences webhooks

In the Fastly control panel:

  1. Go to Security > Next-Gen WAF > Workspaces.
  2. Click the gear next to the workspace, then Alerts and Add alert.
  3. Set Integration type to Webhook, enter your Webhook URL, and leave Flagged IPs checked under Select activities.
  4. Click Add workspace alert.

In the legacy Next-Gen WAF control panel:

  1. Select the site, then go to Manage > Site Integrations and click Add site integration.
  2. Choose Generic Webhook and enter your Webhook URL.
  3. Choose All activity, or Specific activity and the activity types you want.
  4. Click Create site integration.

To get the signing key, edit the alert or integration and click Reveal. Store it server-side, for example as SIGSCI_SIGNING_KEY. Fastly documents no validation or test request, so the first notification you see is a real one.

Securing Signal Sciences webhooks

Every request includes X-SigSci-Signature, the hex HMAC-SHA256 of the payload, keyed with the integration's signing key used as a plain string. Compute the same digest over the raw request body and compare it with the header in constant time.

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

function verifySigSciSignature(rawBody, signatureHeader, signingKey) {
  if (!signatureHeader || !signingKey) return false;

  const expected = crypto.createHmac('sha256', signingKey).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/sigsci', express.raw({ type: '*/*' }), (req, res) => {
  const key = process.env.SIGSCI_SIGNING_KEY;
  if (!key) return res.sendStatus(500); // fail closed

  if (!verifySigSciSignature(req.body, req.get('x-sigsci-signature'), key)) {
    return res.sendStatus(401);
  }

  const notification = JSON.parse(req.body.toString('utf8'));
  // Route on notification.type, e.g. 'flag' or 'agentAlert'.
  res.sendStatus(200);
});

The same check in Python:

import hashlib
import hmac


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


# In FastAPI: raw_body = await request.body()
# verify_sigsci_signature(raw_body, request.headers.get("x-sigsci-signature"),
#                         os.environ["SIGSCI_SIGNING_KEY"])

Fastly publishes samples in Go, Python, Ruby and Bash. The Go, Python and Ruby ones match the code above, though the Python sample compares with == rather than hmac.compare_digest. The Bash sample compares the raw output of openssl dgst, which includes a label such as (stdin)= before the digest, so it never matches as written.

Make Signal Sciences webhooks production-ready. Hookdeck Event Gateway verifies X-SigSci-Signature on every Next-Gen WAF notification and durably queues flagged-IP and agent alerts before they reach your incident tooling.

Signal Sciences webhook limitations and pain points

Delivery behaviour is undocumented

The Problem: You cannot tell whether a notification that hit a failing endpoint will ever be sent again.

Why It Happens: Fastly's generic webhook page describes setup, format and signing, and nothing about retries, timeouts, the status code it expects, or ordering.

Workarounds:

  • Return 200 quickly and assume a failed notification is not retried.
  • For anything you must not miss, such as flagged IPs, also poll the Next-Gen WAF API on a schedule.

How Hookdeck Can Help: Hookdeck accepts each notification as it arrives and stores it, then delivers to your service with retries you control and replay for anything that fails, so an outage on your side does not depend on Fastly's unknown retry behaviour.

Payloads vary and are thinly documented

The Problem: Code written against the documented example breaks on real notifications.

Why It Happens: payload changes shape with type, Fastly references an API response for only some types, and its single example has a placeholder link.

Workarounds:

  • Route on type and treat payload as untyped until you have captured real examples of each type you use.
  • Use link and workspaceId for display and routing rather than deep fields inside payload.

How Hookdeck Can Help: Every notification is logged in Hookdeck with its headers and body, so you can capture real payloads for each activity type and replay them against your parser as you build it.

Key rotation has no overlap

The Problem: Verification fails for every notification as soon as someone clicks Rotate.

Why It Happens: Rotation takes effect immediately, and Fastly documents no grace period or second signature.

Workarounds:

  • Rotate during a quiet period, and update your stored key straight away.
  • Let your receiver accept either of two configured keys while you switch.

How Hookdeck Can Help: With Hookdeck in front, the signing key lives on the Hookdeck source. Rotate in Fastly, paste the new key into the source, and your services, which only trust Hookdeck, need no change.

No timestamp or event ID

The Problem: A captured notification can be replayed, and duplicates cannot be detected from the headers.

Why It Happens: Only the payload is signed, and Fastly sends no timestamp or delivery ID.

Workarounds:

  • Build a key from type, created and an identifier inside payload, and skip repeats.
  • Keep the endpoint on HTTPS.

How Hookdeck Can Help: Hookdeck's deduplication can drop repeated notifications based on the fields you choose.

Best practices

Verify against the raw body

Sign and compare the exact bytes received. In Express, use express.raw() on the route; in FastAPI, await request.body(). Re-serialised JSON will not match.

Separate security alerts from configuration noise

flag and agentAlert usually need a fast response, while dashboard and rule changes belong in an audit log. Subscribe only to the activity types you act on, or route them to different destinations.

Treat the endpoint as part of your security tooling

A forged flag notification could trigger automated blocking or page someone at night. Fail closed when the key is missing, and keep the signing key out of logs.

Acknowledge fast and process asynchronously

Fastly doesn't document its timeout, so don't do slow work in the request. Verify, store, return 200, then notify or enrich in the background. See why to process webhooks asynchronously.

Conclusion

Signal Sciences generic webhooks, now part of Fastly Next-Gen WAF, sign each notification with a hex HMAC-SHA256 in X-SigSci-Signature. Verify against the raw body, route on type, treat payload as variable, and plan for undocumented delivery behaviour by acknowledging quickly and reconciling important activity through the API.

Hookdeck Event Gateway verifies every X-SigSci-Signature, keeps a searchable log of notifications, and holds each one until your service has processed it.

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