Pushrail Docs
Open app
Guides · Sending events

Error handling

HTTP status codes the ingestion API returns and how to react to each.

Error handling

The ingestion API returns one of a small set of HTTP status codes. Each maps to a clear category (validation, auth, rate limit, server) and each has a recommended response from your code. The SDKs raise typed exceptions per category so a catch block can branch on type rather than parse status codes.

Validation errors (400)

400 Bad Request means the event failed schema validation. The response body is a structured error with the offending field path and reason. Validation errors are permanent, retrying with the same payload always fails. Log, alert on a spike, and fix the upstream caller.

{
  "error": {
    "code": "validation_failed",
    "message": "payload.amount: expected number, got string",
    "details": [
      { "path": "payload.amount", "expected": "number", "received": "string" }
    ]
  }
}

The Node SDK raises PushrailValidationError; Python raises PushrailValidationError. Both expose the structured detail array on the exception.

A 422 Unprocessable Entity is also classed as a validation error and surfaced as the same exception type, it means the envelope was syntactically valid but a constraint was violated (an eventType not in your published schema catalog, for example).

Auth errors (401)

401 Unauthorized means the API key is missing, malformed, expired, or revoked. 403 Forbidden means the key is valid but lacks the scope for the endpoint you called, an ingest-only key trying to write to /destinations, for instance.

Auth errors are permanent until you fix the key. Do not retry, escalate to your secrets-management process or, if you just rotated, give the rotation a moment to propagate (rotation has a 24-hour grace window, so brand-new and just-rotated keys both work; an immediate 401 is almost always a typo or a wrong-environment key).

Rate limiting (429)

429 Too Many Requests means you exceeded the per-tenant ingestion rate limit. The response carries a Retry-After header (seconds, or an HTTP-date) telling you how long to wait. The SDKs honor this automatically inside their retry logic.

If you are hitting 429s consistently, the answer is rarely "retry harder." Either smooth the burst (a small client-side queue with a fixed concurrency cap), switch to batch ingest, or talk to support about your tier's burst budget.

Server errors (5xx)

500, 502, 503, and 504 are transient, Pushrail is having a moment and the request should be retried. The SDKs retry automatically with exponential backoff and jitter; if you are calling the HTTP API directly, retry with an upper bound of attempts (3-5 is typical) and the same idempotencyKey so the eventual success collapses any partial double-acceptance.

The retry policy in both SDKs is 2 ** attempt * 200ms with random jitter, capped at 30 seconds. See Node SDK retries for the customization API.

Retry guidance

The short version: retry 429 and 5xx; do not retry 400, 401, 403, or 422. The SDKs encode this for you, your code only needs custom handling if you are bypassing the SDK or implementing a redrive worker.

Always re-send retries with the same idempotencyKey as the original. That way if the first request actually succeeded but the response was lost on the wire, the retry collapses to a duplicate instead of producing a second event. See Idempotency.