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
Ageneric endpoint receives the full event envelope:
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.
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, treatX-Wardin-Delivery-Id as an idempotency key if your receiver can’t tolerate a duplicate.
Test send
Send a syntheticwebhook.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:
POST /v1/webhook-endpoints/{id}/test.