HookWatch HTTP API reference
Everything the dashboard does, over HTTP. Authenticate with a workspace API key, then manage endpoints, read deliveries, and replay or retry failed webhooks — with worked curl examples and the real response shapes.
The HookWatch HTTP API is what the dashboard and the hookwatch CLI both call. Base
URL https://api.hookwatch.dev; every /api/* route speaks JSON. The examples
below use a workspace API key — the same credential a script or CI job would use. For the full, normative
route list, see the in-repo API contract; this page is the worked-example tour.
Authentication
Mint a workspace API key in the dashboard under Settings → Tokens → New token. The hwk_… secret is shown once — copy
it then. It authenticates a workspace (not a user), so it grants member-level reads and non-role-gated
actions; owner/admin-only routes and the admin panel refuse it.
curl -s "https://api.hookwatch.dev/api/deliveries?limit=1" \
-H "Authorization: Bearer $HOOKWATCH_KEY"
# → {"data":[{"id":"evt_…","status":"delivered", …}]} Responses and errors
Success is always wrapped in a data envelope — a single object, or a bare array for
lists (there is no cursor object; lists take ?limit= only). Errors are uniform: {"error":{"code","message","fields?"}}, with fields present only on a validation failure.
| HTTP | code | When |
|---|---|---|
| 400 | bad_request | Malformed JSON or an unknown field. |
| 400 | validation | Per-field failure; fields populated. |
| 401 | unauthenticated | Missing or invalid key. |
| 403 | forbidden | Authenticated, but the key lacks the role. |
| 404 | not_found | Missing, or outside the key's workspace. |
| 409 | conflict | Uniqueness or an illegal state transition. |
| 422 | unprocessable | Well-formed but unactionable (e.g. no stored body to replay). |
Endpoints
An endpoint is a capture URL plus its forwarding config. provider is one of stripe, github, telegram, shopify, meta, custom; mode is proxy, tunnel, or capture. The signing secret and public URL are minted for
you, and the secret is returned exactly once.
curl -s -X POST "https://api.hookwatch.dev/api/projects/$PROJECT_ID/endpoints" \
-H "Authorization: Bearer $HOOKWATCH_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"orders","provider":"stripe","mode":"proxy","destination":"https://app.example.com/webhooks/stripe"}'
# → 201 {"data":{"id":"ep_…","slug":"…","public_url":"https://hooks.hookwatch.dev/in/…",
# "signing_secret":"whsec_…","signing_enabled":true,"active":true, …}} Read one, or delete it (delete returns 200 with a body,
not 204):
curl -s "https://api.hookwatch.dev/api/endpoints/$ENDPOINT_ID" \
-H "Authorization: Bearer $HOOKWATCH_KEY" # {"data":{ …EndpointView }} curl -s -X DELETE "https://api.hookwatch.dev/api/endpoints/$ENDPOINT_ID" \
-H "Authorization: Bearer $HOOKWATCH_KEY"
# → 200 {"data":{"id":"ep_…","deleted":true}} Deliveries and recovery
A delivery is one inbound webhook and its forwarding outcome. The workspace feed is a lean list (no payload or headers); the detail route carries the full request, response, and error.
curl -s "https://api.hookwatch.dev/api/deliveries?limit=50" \
-H "Authorization: Bearer $HOOKWATCH_KEY"
# → {"data":[{"id":"evt_…","endpoint_name":"orders","status":"failed",
# "error_type":"connection_refused", …}]} Replay re-sends the exact captured payload (optionally to a different target), records a new attempt, and never mutates the original — safe once your handler is fixed:
curl -s -X POST "https://api.hookwatch.dev/api/deliveries/$DELIVERY_ID/replay" \
-H "Authorization: Bearer $HOOKWATCH_KEY" \
-H "Content-Type: application/json" \
-d '{"reason":"handler fixed"}'
# → {"data":{"replay_id":"rep_…","delivery_id":"evt_…","status":"completed",
# "attempt_id":"att_…","target_status_code":200,"latency_ms":42, …}} Retry re-sends the same delivery to its own endpoint and advances its status; analyze runs the deterministic failure classifier (no external LLM):
curl -s -X POST "https://api.hookwatch.dev/api/deliveries/$DELIVERY_ID/retry" \
-H "Authorization: Bearer $HOOKWATCH_KEY"
# → {"data":{"delivery_id":"evt_…","status":"retrying","next_attempt_number":2, …}} curl -s -X POST "https://api.hookwatch.dev/api/deliveries/$DELIVERY_ID/analyze" \
-H "Authorization: Bearer $HOOKWATCH_KEY" \
-H "Content-Type: application/json" -d '{}'
# → {"data":{"analysis_id":"ana_…","category":"…","severity":"high","retryable":true, …}} Webhook ingest
Providers send to an endpoint's capture URL, https://hooks.hookwatch.dev/in/<slug>. When signing is on, sign the raw body with HMAC-SHA256 keyed by the endpoint's signing secret and send it in X-Hookwatch-Signature. HookWatch verifies it, persists the delivery, and forwards
asynchronously — re-signing the forward with the same scheme.
SECRET='whsec_…' # the endpoint's signing secret (shown once on create)
SLUG='…' # from the endpoint's public URL
BODY='{"type":"ping","id":1}'
SIG="sha256=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $NF}')"
curl -s -X POST "https://hooks.hookwatch.dev/in/$SLUG" \
-H "Content-Type: application/json" \
-H "X-Hookwatch-Signature: $SIG" \
--data-binary "$BODY"
# → {"id":"evt_…","received":true} (bare JSON — forwarding is async) Ingest errors are plain text: 401 bad or missing signature, 404 unknown slug, 403 paused/archived or a blocked source IP, 413 oversize.
Keep reading
Start debugging your webhooks.
Point one endpoint at HookWatch, capture a failure, and replay it once it’s fixed. Free during beta.