Skip to main content

Webhooks

Status: Current (Scale plan). Warmr can POST events to a URL you configure. Webhook management is an opt-in, secret-bearing surface: reads need webhooks:read, everything else needs webhooks:write — neither is on a default key.

Event types

Endpoints

Create a webhook — POST /v1/webhooks

Body: { "url", "events"? }. url must be a public https:// URL — a private, loopback, or link-local host is rejected 422 by an SSRF guard. Returns 201:
The secret is shown exactly once — here. Store it immediately; no later call (GET, PATCH) can ever return it. It is the HMAC key for X-Warmr-Signature. Deleting the webhook destroys the secret with the row.
  • Omitting events subscribes to ALL events. An explicit "events": [] is rejected 422 (the empty set means “all”, so an empty list is ambiguous — omit the field, or list the events you want).
  • The create response carries no created_at / updated_at; GET /v1/webhooks for timestamps.

Update — PATCH /v1/webhooks/{id}

Partial update of any of url, events, active (at least one, else 422). A new url is re-checked by the SSRF guard. "events": [] is rejected 422 — it would silently widen the subscription to all events; to quiet a webhook, set "active": false instead. Returns { "webhook": Webhook } (never the secret), or 404 if the webhook is not yours.

Delete — DELETE /v1/webhooks/{id}

A hard delete — the delivery history cascades and the secret dies with the row (no undo). Returns { "webhook_id", "deleted": true }.

Test — POST /v1/webhooks/{id}/test

Enqueues a signed webhook.ping delivery and returns 202 with { "delivery_id" }. 202 means enqueued, not delivered — the pump delivers asynchronously; follow the attempt at GET /v1/webhooks/{id}/deliveries.

Redrive — POST /v1/webhooks/deliveries/{id}/redrive

Re-queues a dead delivery for another attempt.

Delivery headers and payload

Every delivery carries three headers:
  • X-Warmr-Event — the event type.
  • X-Warmr-Event-Id — a stable id; use it to dedupe (delivery is at-least-once).
  • X-Warmr-Signaturesha256=<hmac-sha256(rawBody, secret)>.
The body:
data carries the event-specific payload.

Delivery guarantees

  • At-least-once. The same event may arrive more than once — dedupe on X-Warmr-Event-Id.
  • Retries. A failed delivery retries with exponential backoff + jitter, up to 5 attempts over 72 hours.
  • Delivery status. A delivery is pending while it still has attempts left within the window, then becomes delivered on success or dead once it exhausts the attempts or the 72-hour window.
  • HTTPS + public only. The target URL must be a public https:// host; the SSRF guard blocks private, loopback, and link-local targets.

Verify the signature

Recompute the HMAC over the raw request body — not the re-serialized JSON, since whitespace and key order must match — and compare in constant time. The SDK’s sdk/src/webhooks.ts exposes two helpers:
verifyWebhookSignature never throws on a bad or missing header — it returns false. constructWebhookEvent verifies and then parses, throwing on an invalid signature. Without the SDK (Node):

Next

  • Scopes — the webhooks:read / webhooks:write opt-in.
  • Runs — the runs whose completion these events report.