Skip to content
Back to site

Errors and limits

Every error from the Railhook API has the same shape, so one handler in your client covers all of them.

400 Bad Request
{
"error": "validation_error",
"message": "Invalid request parameters",
"status": 400,
"fieldErrors": {
"type": "Event type is required",
"data": "Event data cannot be empty"
}
}
Field Always present Meaning
error Yes A stable, machine-readable code. Branch on this.
message Yes A human-readable explanation. Do not parse it.
status Yes The HTTP status, repeated
fieldErrors No On validation errors: field name → what is wrong with it
Status error What to do
202 authorization_pending railhook login only: the code has not been approved in the browser yet. Poll again.
400 validation_error Fix the fields named in fieldErrors.
400 malformed_request The body is missing or is not valid JSON.
400 missing_parameter, invalid_parameter A query or path parameter is missing or in the wrong format.
400 invalid_url A URL was refused, for example by SSRF protection.
400 invalid_request The request was understood but rejected, for example an event that would fan out past the limit or an expired confirmation link. Read message.
400 captcha_failed The challenge on a public form was not passed. Solve it again.
401 unauthorized Credentials missing, expired or wrong. Refresh the access token, or check the X-API-Key header.
401 signature_verification_failed Ingress URL only: the provider’s signature did not match the source’s secret.
402 quota_exceeded A plan limit is reached. fieldErrors names the quota, the usage, the limit and the plan.
403 forbidden Authenticated, but the role or key scope does not allow it. A READ_ONLY key cannot write.
403 demo_read_only The live demo cannot change anything. Create an account.
403 reauthentication_required Platform admin panel only: the last sign-in is too old. Sign in again.
403 suspended, tunnel_suspended Ingress or tunnel URL of a suspended organization. It accepts nothing until the suspension is lifted.
404 not_found Nothing with that id exists in your organization.
405 method_not_allowed Wrong HTTP method. The Allow header lists the right ones.
409 conflict It already exists, or it changed while you were editing. Re-read it and try again.
410 gone It existed but can no longer be used: an expired or spent device code or invite, a closed tunnel. Start over.
410 disabled Ingress URL only: the source is disabled.
413 payload_too_large The body is over the size limit.
415 unsupported_media_type Send Content-Type: application/json.
423 account_locked Too many failed sign-ins. Wait the minutes message names, or reset the password.
429 rate_limit_exceeded, organization_rate_limit, platform_rate_limit, portal_rate_limit, rate_limited Wait for Retry-After seconds where it is sent, then retry. rate_limited is the platform admin panel’s limit.
429 too_many_active_urls, contact_busy Public webhook tester and contact form: this address holds too many live tester URLs, or the form has taken all it can today.
500 internal_error Railhook failed. Retry with backoff.
502 tunnel_error Tunnel URL only: the CLI could not reach your local app. message says why.
503 service_unavailable Not ready yet, for example the demo data is still being prepared. Retry in a minute.
503 tunnel_offline Tunnel URL only: no CLI is connected for this tunnel.
503 tester_busy, contact_unavailable The public webhook tester is full, or this deployment has no support address.
504 tunnel_timeout Tunnel URL only: your local app did not answer in time, or the CLI disconnected.
4xx client_error A refusal with no specific code, for example the MCP OAuth endpoints on a deployment where they are off. Read status and message.
5xx server_error A server error with no specific code. Retry with backoff.

client_error and server_error are the fallback for the few errors without a code of their own. Branch on status first and use error to refine. The MCP OAuth endpoints under /oauth/ answer in the shape OAuth clients expect instead: error and error_description, with the OAuth codes such as invalid_grant.

These are the defaults of a self-hosted deployment. Railhook Cloud has its own limits, listed below.

Limit Applies to Default On a 429
Event ingestion POST /api/v1/events, per project 100 per second rate_limit_exceeded with budget headers
Organization All API traffic of one organization Off (ORG_RATE_LIMIT_ENABLED=false); 200 per second when on organization_rate_limit, Retry-After: 1
Platform All traffic, per API instance 5000 per second (GLOBAL_RATE_LIMIT_PER_SECOND) platform_rate_limit
Ingress URL Webhooks received at /ingress/…, per source 100 per second (WEBHOOK_INCOMING_RATE_LIMIT_PER_SECOND), unless the source sets its own Retry-After: 1
Sign-in Per IP and per email 10 per minute (AUTH_RATE_LIMIT_LOGIN_PER_MINUTE) rate_limit_exceeded
Session refresh Per refresh token, and per IP 30 per minute per token (AUTH_RATE_LIMIT_REFRESH_PER_TOKEN_PER_MINUTE), 600 per minute per IP (AUTH_RATE_LIMIT_REFRESH_PER_IP_PER_MINUTE) rate_limit_exceeded

Every response from POST /api/v1/events carries the project’s current budget:

Header Meaning
X-RateLimit-Limit Requests the current window allows
X-RateLimit-Remaining Requests left in it
X-RateLimit-Reset Unix timestamp when the window resets
Retry-After On a 429 only: seconds to wait

Railhook Cloud at railhook.io runs every organization on the Free plan. Every feature is included (workflows, rules, replay, mTLS); only volume is limited. These limits replace the self-hosted defaults above:

Limit Free plan
Event ingestion (POST /api/v1/events) 10 per second per project
Events 10,000 per month
Projects 3
Endpoints 5 per project
Members 5 per organization
Event retention 7 days
Active tunnels 1
Deliveries per event (fan-out) 100

A self-hosted deployment has no plan limits. Only the defaults in the tables on this page apply.

Limit Default Variable
Request body, API 256 KB (262144 bytes) WEBHOOK_MAX_PAYLOAD_SIZE_BYTES
Request body, ingress URL 512 KB (524288 bytes) WEBHOOK_INCOMING_MAX_PAYLOAD_SIZE_BYTES
Deliveries per event (fan-out) 50 on a self-hosted deployment —

The ingress limit is higher because what a provider sends is not yours to shrink. An event that matches more subscriptions than the fan-out limit is rejected with 400 as a whole. It is never delivered to some matching endpoints and not others.

An ingress URL answers the provider that sent the webhook, not you, so its responses are shorter: a successful receive is 202 with status and requestId, and an error carries error and message without status. When a deployment with plans runs out of quota, the ingress URL answers 429 with Retry-After: 3600 and no detail, so a provider never learns anything about your plan.