Skip to main content

Error Handling

Praeto Dispatcher uses JSON error responses. Standard shape:
{
  "ok": false,
  "error": "error message",
  "details": {}
}
details may be a string, object, array, or omitted depending on the failure.

Common HTTP statuses

StatusMeaningTypical Cause
400Bad requestInvalid JSON, invalid URL, missing fields, payload too large
401UnauthorizedMissing/invalid bearer token
404Not foundEvent, endpoint, or delivery does not exist for this tenant
409ConflictIdempotency key reused with different payload
429Rate limited or quota exceededToo many requests or monthly/hard limit reached
500Internal server errorUnexpected backend/database/runtime failure

Authentication errors

Missing bearer token:
{
  "ok": false,
  "error": "missing bearer token"
}
Invalid API key:
{
  "ok": false,
  "error": "invalid API key"
}
Invalid admin key:
{
  "ok": false,
  "error": "invalid admin key"
}

Idempotency conflict

Returned when the same Idempotency-Key is reused with a different payload.
{
  "ok": false,
  "error": "idempotency key already used with a different payload",
  "details": {
    "event_id": "811fad9a-d2cb-4dd2-a2e1-9bb5d90190db"
  }
}
Correct response from the publisher:
  • Do not retry with a different payload under the same key.
  • Fetch or inspect the original event.
  • Use a new idempotency key only if this is genuinely a different event.

Rate limit errors

Example:
{
  "ok": false,
  "error": "rate limit exceeded",
  "details": {
    "tenant_id": "default",
    "action": "events:write",
    "limit": 60,
    "current": 61,
    "window_seconds": 60,
    "window_start": "2026-04-28T09:14:00.000Z",
    "retry_after_seconds": 16
  }
}
Recommended client behavior:
  1. Read details.retry_after_seconds.
  2. Wait at least that long.
  3. Retry with the same Idempotency-Key if retrying POST /v1/events.

Quota limit errors

Possible quota failures:
  • monthly event limit exceeded
  • monthly attempt limit exceeded
  • max endpoint count exceeded
  • max fan-out endpoints exceeded
  • payload too large
Recommended response:
  • inspect /v1/usage/current
  • inspect /v1/usage/limits
  • upgrade plan or reduce usage

URL validation errors

Praeto Dispatcher blocks unsafe webhook URLs. Examples:
{
  "ok": false,
  "error": "invalid url",
  "details": "url hostname is blocked for SSRF protection"
}
{
  "ok": false,
  "error": "invalid url",
  "details": "url must not include username or password credentials"
}
{
  "ok": false,
  "error": "invalid url",
  "details": "url must start with http:// or https://"
}
Blocked examples:
http://localhost:8080/webhook
http://127.0.0.1:8080/webhook
http://10.0.0.5/webhook
http://172.16.0.1/webhook
http://192.168.1.20/webhook
http://169.254.169.254/latest/meta-data
http://metadata.google.internal/computeMetadata/v1
ftp://example.com/webhook
https://user:pass@example.com/webhook

Delivery failure vs API failure

Important distinction:
  • POST /v1/events returning success means Praeto accepted and queued the event.
  • It does not guarantee every downstream webhook endpoint returned success.
Use:
GET /v1/events/{event_id}/deliveries
GET /v1/deliveries/{delivery_id}/attempts
GET /v1/endpoints/{endpoint_id}/health
for delivery status.

Retry-safe client behavior

When publishing events:
  1. Always send an Idempotency-Key.
  2. If the request times out or returns 5xx, retry with the same key and same body.
  3. If the response is 409, stop and inspect the original event.
  4. If the response is 429, wait for retry_after_seconds and retry with the same key.