Skip to main content
Webhooks push the same governance events that drive alerting on the dashboard into your own systems — PagerDuty, a SIEM, a Slack channel, an internal bot — without polling the API. Every delivery is signed with HMAC-SHA256 so a receiver can prove it actually came from Wardin, and delivery is at-least-once with retries: a slow or briefly-down receiver doesn’t silently lose events.

Event types

Each occurrence alerts once — a sustained spike or a budget that stays over threshold doesn’t refire on every evaluation pass, only on the first crossing (per budget period, per violation, or per clock-hour, depending on the event).

Creating an endpoint

type is required — generic or slack. The response includes a per-endpoint signing secret (whsec_...) — store it; it’s needed to verify deliveries. Full endpoint references: GET /v1/webhook-endpoints, POST /v1/webhook-endpoints, PATCH /v1/webhook-endpoints/{id}, DELETE /v1/webhook-endpoints/{id}.
Endpoint URLs are validated at create/update time and again at delivery time (SSRF guard): private/loopback/link-local addresses and non-HTTPS URLs are rejected in production, and 3xx redirects are never followed. This also blocks DNS rebinding — a hostname that resolves to a public address at config time but a private one at delivery time is caught on the re-check.

Delivery format

A generic endpoint receives the full event envelope:
A slack endpoint instead receives the native Slack incoming-webhook shape — { "text": "*Wardin · budget.threshold_reached*\n<message>" } — so it renders directly in the channel with no adapter needed. Every POST also carries X-Wardin-Delivery-Id (the delivery’s id — use it to de-duplicate on your side) and X-Wardin-Event (the event type). generic endpoints additionally carry the signature header described below; Slack’s incoming-webhook receiver ignores custom headers, so signed endpoints are generic only.

Verifying the signature

generic deliveries carry an X-Wardin-Signature header in the form t=<unix_seconds>,v1=<hex_hmac> — a timestamp and an HMAC-SHA256 digest of <timestamp>.<raw_body>, keyed with your endpoint’s whsec_... secret. Binding the timestamp into the digest (rather than signing the body alone) lets you reject replayed deliveries outside a tolerance window.
Compute the HMAC over the raw, unparsed request body — re-serializing a parsed JSON object can reorder keys or change whitespace and produce a digest mismatch even for a genuine delivery.

Delivery guarantees

Delivery is at-least-once: an event is persisted as a delivery row before any network call is attempted, so a crash between “event happened” and “HTTP POST sent” can never lose it. A failed attempt (non-2xx, timeout, or transport error) is retried with exponential backoff (capped at one hour) until it either succeeds or exhausts the endpoint’s retry budget, at which point it’s dead-lettered — the endpoint’s status still reflects the last outcome, but that specific delivery stops retrying. Because delivery is at-least-once, not exactly-once, treat X-Wardin-Delivery-Id as an idempotency key if your receiver can’t tolerate a duplicate.

Test send

Send a synthetic webhook.test event to one endpoint and get an immediate pass/fail result — useful for confirming a new endpoint and secret are wired correctly before relying on a real event:
Full endpoint reference: POST /v1/webhook-endpoints/{id}/test.

Where to see it

Console → GATEWAY → Webhooks lists every endpoint (with a live delivered/failed status dot), lets you add/toggle/delete endpoints and reveal a secret, and includes the same test-send action as the API.