SIGNAL_BENCH DOCS

Premium tier

Webhook Alerts

Get notified the moment a composite regime flips, with HMAC-signed POSTs to your endpoint.

Overview

Webhook alerts replace the daily-polling workflow. When the ingestion run detects that a composite signal's direction has changed since the previous as-of date, the dispatcher POSTs a signed JSON payload to every active subscription registered for that signal.

  • Premium-tier feature. Free and Standard subscribers can read the API but cannot register webhook subscriptions.
  • Currently triggers on regime flips only (direction change between consecutive composite values). Threshold-based events are queued for a future release.
  • Single-attempt delivery with a 10-second timeout. No retry today; failures are recorded against the subscription's last-fire status.

Create A Subscription

From the authenticated app, open Settings → Webhooks and click Add subscription. The signing secret is shown ONCE on creation; copy it to your environment immediately.

Programmatically:

curl https://www.signalbench.dev/api/v1/user/webhooks \
  -X POST \
  -H "content-type: application/json" \
  --cookie "<your-session-cookie>" \
  -d '{"url":"https://your-server.example.com/sb-webhook","signal_id":"composite.equity.regime"}'

Successful response includes the signing secret one time:

{
  "id": 12,
  "url": "https://your-server.example.com/sb-webhook",
  "signal_id": "composite.equity.regime",
  "event_type": "regime_flip",
  "secret": "whsec_abc123..."
}

Payload Format

Every delivery is a POST with this JSON body:

{
  "version": "1",
  "event": "regime_flip",
  "signal_id": "composite.equity.regime",
  "signal_name": "Equity Regime",
  "as_of_date": "2026-05-16",
  "previous_direction": "improving",
  "current_direction": "deteriorating",
  "previous_score": 0.18,
  "current_score": -0.22,
  "delivered_at": "2026-05-16T06:30:42.812Z"
}

Headers attached to every delivery:

HeaderValue
content-typeapplication/json
user-agentsignal-bench-webhook/1.0
x-signalbench-eventregime_flip
x-signalbench-signal-idthe composite id
x-signalbench-signaturesha256=<hex digest>

Verifying The Signature

Compute HMAC-SHA256 of the raw request body using your stored secret, then compare against the digest in x-signalbench-signature (strip the sha256= prefix). Reject the delivery if the digests do not match — a mismatch means either the secret is wrong or the body was tampered with in transit.

import { createHmac, timingSafeEqual } from "node:crypto";

export function verifySignalBenchWebhook(
  rawBody: string,
  signatureHeader: string | undefined,
  secret: string
): boolean {
  if (!signatureHeader?.startsWith("sha256=")) return false;
  const provided = Buffer.from(signatureHeader.slice(7), "hex");
  const expected = createHmac("sha256", secret).update(rawBody).digest();
  if (provided.length !== expected.length) return false;
  return timingSafeEqual(provided, expected);
}
import hmac, hashlib

def verify_signalbench_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
    if not signature_header.startswith("sha256="):
        return False
    provided = bytes.fromhex(signature_header[len("sha256="):])
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).digest()
    return hmac.compare_digest(provided, expected)
Use raw body

Compute the HMAC over the exact bytes you received, not a JSON-parsed-and-re-serialized version. Re-serialization changes whitespace and key order and will break the signature.

List And Delete

# List your subscriptions (does not return secrets)
curl https://www.signalbench.dev/api/v1/user/webhooks --cookie "<your-session-cookie>"

# Delete a subscription
curl -X DELETE https://www.signalbench.dev/api/v1/user/webhooks/12 \
  --cookie "<your-session-cookie>"

Limits And Reliability

  • Single delivery attempt per event. If your receiver is down or slow (>10s), the delivery is marked failed and not retried.
  • The dispatcher runs once per ingestion cycle (daily). You will not receive intra-day webhooks for the same signal even if it moves within a day.
  • Each subscription's last_fired_at, last_fire_status, and last_fire_message are visible in the settings UI for debugging.
  • Your endpoint should respond quickly with 2xx. We do not parse the response body.
Retry coming later

Exponential-backoff retry with a delivery-log table is on the roadmap. Today, treat webhooks as advisory and reconcile against the API if state matters.