SigSentrySigSentry

Outgoing webhooks

Payload shapes and signature verification for webhooks SigSentry sends to your notification channels

This page describes the webhooks SigSentry sends to you — the payload format your webhook endpoint receives when an analysis fires. This is the contract you build against when implementing a webhook notification channel.

For configuring webhook channels themselves, see Webhook channels.

Delivery

When an analysis result meets a webhook channel's severity threshold, SigSentry sends a POST to your URL with:

POST <your-url>
Content-Type: application/json
X-Sigsentry-Event: analysis.complete
X-Sigsentry-Signature: sha256=...
X-Sigsentry-Delivery: <unique-id>
HeaderNotes
X-Sigsentry-EventEvent type — see the table below
X-Sigsentry-SignatureHMAC-SHA256 of the raw body, keyed with the channel's signing secret
X-Sigsentry-DeliveryUnique delivery id; safe to use for dedup

Event types

EventWhen sent
analysis.completeAn analysis finished and its severity met the channel's threshold
analysis.partialAn analysis returned partial results (one or more log sources unreachable)
watchdog.firedA Watchdog rule with notify_only action fired (no analysis follows)

Your endpoint should respond 2xx quickly — long-running work should be enqueued in your handler and processed asynchronously. Non-2xx responses are retried with exponential backoff for a while; if all retries fail the delivery is marked Failed in the channel's activity.

analysis.complete payload

{
  "event": "analysis.complete",
  "deliveryId": "del_a1b2c3...",
  "analysisId": "ana_x1y2z3...",
  "projectId": "proj_p1q2r3...",
  "projectSlug": "checkout-api",
  "createdAt": "2026-04-25T14:30:12Z",
  "summary": "Connection pool exhaustion in checkout-api...",
  "severity": "high",
  "confidence": 0.87,
  "rootCause": {
    "description": "...",
    "service": "checkout-api",
    "errorType": "ConnectionPoolExhausted",
    "category": "database"
  },
  "affectedServices": [
    { "serviceName": "checkout-api", "role": "origin", "errorCount": 234, "firstSeen": "...", "lastSeen": "..." }
  ],
  "suggestedActions": [
    { "priority": 1, "action": "Increase the database connection pool ceiling", "rationale": "...", "type": "fix" }
  ],
  "codeCorrelation": {
    "available": true,
    "suspectedCode": { "repo": "checkout-api", "filePath": "src/db.ts", "functionName": "getConnection", "lineRange": [42, 58] },
    "causalPR": { "id": "...", "title": "...", "url": "...", "confidence": 0.91 }
  },
  "dashboardUrl": "https://dashboard.sigsentry.com/..."
}

The shape mirrors the API's AnalysisResult — see Reading the result for field-level detail.

analysis.partial payload

Same shape as analysis.complete plus a partialReasons field:

{
  "event": "analysis.partial",
  "...": "...",
  "partialReasons": [
    { "source": "datadog-prod", "reason": "rate-limited" }
  ]
}

watchdog.fired payload

Sent for notify_only rules. No analysis runs.

{
  "event": "watchdog.fired",
  "deliveryId": "del_...",
  "ruleId": "rule_...",
  "ruleName": "checkout-api error rate",
  "projectId": "proj_...",
  "projectSlug": "checkout-api",
  "firedAt": "2026-04-25T14:30:00Z",
  "trigger": {
    "type": "error_rate",
    "value": 0.082,
    "threshold": 0.05,
    "errorCount": 234,
    "totalCount": 2854,
    "lookbackMinutes": 30
  },
  "sampleLogs": [
    { "timestamp": "...", "service": "...", "level": "error", "message": "..." }
  ],
  "dashboardUrl": "https://dashboard.sigsentry.com/..."
}

Signature verification

Every delivery is signed with HMAC-SHA256 using the channel's signing secret. The secret is supplied by you when you create the webhook channel — generate a strong random value (e.g. openssl rand -hex 32) and store it on both ends. It's not auto-generated server-side, so you control where it lives.

The signature is computed over the raw request body, before any parsing or modification. Verify with this Node.js example:

import { createHmac, timingSafeEqual } from 'crypto';

function verifySignature(rawBody: Buffer, headerValue: string, secret: string): boolean {
  // headerValue looks like "sha256=<hex>"
  const [scheme, sig] = headerValue.split('=');
  if (scheme !== 'sha256' || !sig) return false;

  const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
  const received = Buffer.from(sig, 'hex');
  const expectedBuf = Buffer.from(expected, 'hex');

  return received.length === expectedBuf.length
    && timingSafeEqual(received, expectedBuf);
}

Verify against the raw bytes, not a re-serialized JSON body. Most web frameworks parse JSON before exposing the body — you'll need to configure your framework to retain the raw body for verification.

FrameworkPattern
Expressapp.use(express.json({ verify: (req, res, buf) => req.rawBody = buf })) then verify against req.rawBody
FastifyUse the addContentTypeParser to capture the raw body before parsing
Next.js (Pages Router)export const config = { api: { bodyParser: false } }, then read the raw stream
Next.js (App Router)await request.text() gives the raw body as a string; convert to Buffer for verification

Dedup with deliveryId

Each delivery has a unique deliveryId. If your endpoint sometimes receives duplicates (network retries, your handler fails after processing), dedup on this field rather than analysis id — multiple deliveries can be triggered for the same analysis if your endpoint returns non-2xx and SigSentry retries.

Manual replay

The dashboard has a Replay button on each delivery in Project → Channels → [your channel] → Activity. It re-sends the same payload with the same signature so you can debug your handler without triggering a new analysis.