# Outbound alert webhooks > Configure signed incident-delivery webhooks and validate Firewatch request signatures. This is the machine-readable version of this Firewatch documentation page. Organization administrators can configure up to ten outbound endpoints under **Organization → Integrations**. Firewatch sends an `incident.notification.requested` event for each escalation notification. The payload contains structured incident, service, recipient, and escalation data, plus the organization's rendered webhook message template. ## Request headers Every request includes: | Header | Meaning | | ----------------------- | ---------------------------------------------------------- | | `X-Firewatch-Event` | `incident.notification.requested` | | `X-Firewatch-Delivery` | Stable UUID for this notification delivery and its retries | | `X-Firewatch-Timestamp` | Unix timestamp in seconds | | `X-Firewatch-Signature` | `v1=` followed by a lowercase HMAC-SHA256 hex digest | The signature base is the exact UTF-8 string: ```text .. ``` Compute HMAC-SHA256 with the endpoint's `fwhsec_...` signing secret and compare the complete `v1=` value in constant time. Read and verify the raw body before JSON parsing; re-serializing JSON changes the signed bytes. Receivers should also: 1. reject timestamps more than five minutes away from their current time; 2. persist processed `X-Firewatch-Delivery` values and ignore duplicates; 3. return a `2xx` response only after durably accepting the event; 4. keep the signing secret in a secret manager and rotate it from the UI if it may have been exposed. The signing secret is displayed only when an endpoint is created or rotated. Firewatch stores only its encrypted form. Rotation invalidates the previous secret immediately. ## Node.js verification example ```js export function verifyFirewatchWebhook({ secret, rawBody, deliveryId, timestamp, signature, now = Date.now(), }) { const timestampNumber = Number(timestamp); if (!Number.isSafeInteger(timestampNumber)) return false; if (Math.abs(now / 1000 - timestampNumber) > 300) return false; const base = `${timestamp}.${deliveryId}.${rawBody}`; const expected = `v1=${createHmac("sha256", secret) .update(base, "utf8") .digest("hex")}`; const expectedBytes = Buffer.from(expected, "ascii"); const actualBytes = Buffer.from(signature, "ascii"); return ( expectedBytes.length === actualBytes.length && timingSafeEqual(expectedBytes, actualBytes) ); } ``` ## Destination safety Redirects are not followed. HTTPS is required, and localhost, private, link-local, and other unsafe network destinations are blocked by default to reduce SSRF risk. A self-hoster can deliberately opt into an internal receiver with `FIREWATCH_WEBHOOK_ALLOW_PRIVATE_NETWORKS=true`; plain HTTP additionally requires `FIREWATCH_WEBHOOK_ALLOW_HTTP=true`. Keep both disabled for an internet-facing deployment unless there is a reviewed operational need.