Skip to content

Error codes

import { Aside } from ‘@astrojs/starlight/components’

Proxy errors

These are returned to the webhook sender (Stripe, Paddle, etc.) by the proxy endpoint.

project_not_found — 404

{ "error": "project_not_found", "project": "acme/payments" }

Cause: The org slug or project slug in the URL doesn’t match any registered project.

Fix: Check the endpoint URL configured in your webhook provider. The format is https://hookman.dev/w/{org}/{project}.


no_route_resolved — 422

{
"error": "no_route_resolved",
"hint": "Rules were evaluated but none matched — set a default catch-all or add a matching rule",
"trace": [ /* per-rule evaluation results */ ]
}

Cause: No routing rule matched the request and there is no active (default) deployment to fall back to.

Fix:

  • Set an active deployment so unmatched webhooks have somewhere to go, or
  • Add/adjust a routing rule so it matches the incoming request. The trace array shows how each rule was evaluated, so you can see why none matched.

quota_exceeded — 429

{
"error": "quota_exceeded",
"used": 1000,
"limit": 1000,
"plan": "free"
}

Cause: Your org has exceeded its monthly webhook quota.

Fix: Upgrade your plan, or wait until the quota resets at the start of the next billing period (the 1st of each month). Note: Stripe and similar services will retry on 429 — check that this isn’t causing a retry storm.


Management API errors

These are returned by api.hookman.dev endpoints. Unlike proxy errors above, there’s no stable machine-parseable error string enum here — error is a free-form human-readable message describing exactly what went wrong, and its wording may change over time. If you need to branch programmatically, match on the HTTP status code (and, for plan limits, the code field below). Most endpoints use 400/401/403/404; the replay endpoint also returns 422 for a handful of not-retryable-as-is cases, covered separately below.

Auth / permission / not-found failures — 400, 401, 403, or 404

{ "error": "API key is not authorized for this organisation" }

Cause: Covers authentication failures, insufficient role, and missing or inaccessible resources (org, project, deployment, etc.). The status varies by case: 401 for a missing/invalid session or API key, 403 for insufficient permissions or a key scoped to a different org/project, 404 for a resource that doesn’t exist (or isn’t visible to you), 400 for a handful of request-shape failures (e.g. a missing required field).

Fix: Read the error message for specifics. Re-authenticate for a 401; use an account or key with the required role/scope for a 403; check the identifiers in the URL for a 404.


plan_limit_reached — 403

{
"error": "You've reached the Free plan's limit of 1 project",
"code": "plan_limit_reached",
"resource": "projects",
"limit": 1,
"planTier": "free"
}

Cause: You’ve hit a plan limit — for example, trying to create a second project on the Free tier. This is the one Management API error with a stable machine-parseable field: check code, not error.

Fix: Upgrade your plan, or remove an existing resource to free up a slot.


Replay errors — 422

{ "error": "No payload stored for this log" }

Cause: POST /:logId/replay (redeliver a logged webhook) can’t proceed with the log as stored:

  • "No payload stored for this log" — this log predates payload storage, or payload storage isn’t enabled on your plan.
  • "Payload has expired or was not stored" — the payload’s retention TTL has passed.
  • "Payload was truncated when stored and cannot be replayed byte-accurately" — a truncated body would fail signature verification (e.g. Stripe) on redelivery, so it’s refused rather than replayed partially.
  • "No target URL available for replay" — neither a deployment_id/target_url in the request nor the log’s original routing resolved to a target.

Fix: These aren’t retryable as-is. For the first three, replay isn’t possible for this particular log — there’s no valid payload to resend. For the last, pass an explicit deployment_id or target_url (matching a deployment already registered on the project) in the request body.


Upstream behaviour

Hookman mirrors your deployment’s response verbatim — same status, body and headers. If your target returns a 5xx, the sender sees that 5xx; if it returns 200, the sender sees 200. Hookman does not rewrite upstream statuses.

The only status Hookman synthesises is 502, returned when your target is unreachable or refused (so there’s no response to mirror):

{ "error": "upstream_timeout" }
Log errorReturned statusMeaning
upstream_timeout502The target URL did not respond within 10 seconds.
upstream_connection_refused502Could not connect to the target URL (bad hostname, refused connection, etc.).
target_host_blocked502The target URL resolves to a private/internal/loopback address and was refused before the request was sent (SSRF guard).

For upstream_timeout and upstream_connection_refused, the webhook payload is still stored for replay if your plan supports it, so you can re-deliver once the target is back up. target_host_blocked isn’t a transient failure — the target itself is disallowed, so replaying without changing the target URL or its DNS will hit the same guard again.