Ask whether it is up
The runtime’s own prefix, the durable-execution broker and the sandbox controller are private in the routing table, so a probe sent from outside gets a bare
404 Not found rather than a health answer — see Deploying for why that 404 is the gateway working correctly. The broker is the gap in the table above: the routing table declares /health for it, but no Receipt code path ever calls that path, so check it against your own broker build before you probe it.
Gate on readiness, not liveness
Both runtime endpoints run the same check — open a connection to Postgres with a five-second timeout and issueSELECT 1 — and then report it differently.
GET /healthz returns 200 whatever that check said, and reports the answer only inside its body, alongside a cached queue snapshot and the process role. The comment in the handler states the intent: Health is a liveness surface, not a replay boundary. It is deliberately bounded so that slow queue repair or receipt replay cannot make a healthy runtime look down.
GET /readyz returns 200 or 503 from the same check, with a smaller body. Both bodies carry a postgres object holding ok and a target naming the host, database, user and sslmode the runtime tried, plus an error with the connection message when it failed — enough to tell a wrong host from a wrong password without printing either. The runtime API reference lists every field of both payloads.
Only the
api role binds a port and serves these endpoints. The driver, worker-chat, worker-control and worker-codex roles run the same image with no HTTP server at all — they log runtime.worker_connected instead of runtime.http_listening and have nothing to probe. Supervise them as processes and watch their Resonate registration in the logs. Roles are covered in Processes, roles, and routing.GET /connect/nango/health, called with a connect:read token, asks the runtime to reach the integrations provider on your behalf and answers {"ok":…,"status":…,"reachable":true}. When the provider is not configured it answers 503 with Self-hosted Nango is not configured. Set RECEIPT_INTEGRATIONS_URL, RECEIPT_INTEGRATIONS_SECRET_KEY, and RECEIPT_INTEGRATIONS_WEBHOOK_SECRET.
Read the runtime’s logs
The Receipt runtime writes one JSON object per line, on every role. Each line carries five reserved keys —ts, level, service, event, message — and any context the call site added is merged in alongside:
service is always receipt-runtime, and processRole is attached to every line, so one aggregated stream from five roles stays separable.
Four behaviours matter when you build a pipeline on top of it:
- Secrets are redacted before serialization. Any context key that contains
authorization,cookie,password,secret,tokenorsession— case-insensitively, and likewiseapi_keyoraccess_keywith either separator — is replaced by the string[redacted]. - Long strings are clipped at 2,000 characters and suffixed with
...[truncated]. - Errors become objects —
name,message,stack— rather than a stringified throw. Circular values become[circular]. - Severity picks the stream.
warnanderrorgo to stderr;debugandinfogo to stdout.
Events worth alerting on
event is a stable identifier, which makes it the field to alert on rather than the prose message. The names are grouped by prefix:
A single
projection.*_sync_failed line is not an incident: a transient failure is retried, five attempts by default from a 100 ms base, tunable with RECEIPT_PROJECTION_SYNC_MAX_ATTEMPTS and RECEIPT_PROJECTION_SYNC_RETRY_BASE_MS. A rising rate is the signal.
projection.sync_latency is the one you can build a latency alert on without a metrics pipeline. It logs at info with projector, queueDelayMs, durationMs, totalLatencyMs and thresholdMs, but only when a sync crosses 250 ms, was re-run, or failed. Lower the bar with RECEIPT_PROJECTION_SYNC_LATENCY_LOG_THRESHOLD_MS, or set RECEIPT_PROJECTION_SYNC_LATENCY_LOG_ALWAYS=true to log every sync while you are investigating.
Not every line is JSON. A few hot paths still write plain
console output with bracketed prefixes such as [receipt-runtime] and [durable <label>]. A log pipeline pointed at the runtime must tolerate both shapes rather than failing to parse.Read the web application’s logs
The web application does not use that logger. It runs an Effect logger whose format and verbosity come from the environment:One request, one line
A chat request mints a request id and carries it through the whole turn as a single wide event — actor, thread, model, policy, stream, usage, breadcrumbs and outcome accumulated in one object. At the end it drains as exactly one log line namedchat.request, at Info, Warn or Error depending on the outcome, annotated with request_id, route, method, status, latency_ms, error_code, error_tag, token counts and cost. One request, one line — you do not stitch a turn together from fragments. The Zero mutate route honours an inbound x-request-id header and generates one when the header is absent.
The runtime is different: no runtime HTTP route reads or emits a request id, and there is no request-id middleware to enable. Correlate runtime work by event, by the job and objective identifiers in the log context, and by the receipt streams themselves — see Receipts and streams.
Send it to a collector
SetEFFECT_OTLP_BASE_URL to an OTLP/JSON endpoint and the web application exports logs, traces and metrics to it.
The exporter also stamps a
deployment.environment resource attribute from NODE_ENV.
PostHog is an error drain, not analytics
Receipt’s PostHog integration exists to catch exceptions, and it is configured to do only that. The browser client initializes with autocapture off, page views and page leaves not captured, session recording disabled and surveys disabled — it then starts exception autocapture. The only other browser sends are explicit exception captures for client-side failures that never reached the server. On the server, only finalized chat request failures are mirrored; a successful request is never sent.
Release and environment metadata are read from
RAILWAY_GIT_COMMIT_SHA or GITHUB_SHA, and RAILWAY_ENVIRONMENT_NAME or NODE_ENV.
There is no metrics endpoint
Neither the web application nor the runtime exposes/metrics, and nothing in Receipt serves Prometheus. Application metrics leave a deployment only through the OTLP exporter above, from the web application.
The durable-execution broker serves its own metrics port — RESONATE_METRICS_PORT, default 9090 — but that is the broker reporting on itself, not Receipt reporting on your organization’s work.
For what happened inside a run, the answer is not a metrics scrape: it is the receipt stream, which is complete by construction and replayable. See Receipts and streams.
Locally, the supervisor writes one file per service under .deploy-artifacts/start-all/<runId>/, and its error messages name the file to open — Local development has the layout.
Next step: find the right place to ask for help.