Skip to content
Back to site

Configuration

Railhook is configured through environment variables in the .env file next to docker-compose.yml. A variable you leave out keeps the default built into the Compose file. After editing .env, run ./railhook up -d. Compose recreates only the services whose settings changed.

Most defaults are fine. These are development defaults that become wrong once the instance is reachable.

Variable Why it matters
APP_ENV production makes the API refuse to start on unsafe configuration instead of running with it
WEBHOOK_ENCRYPTION_KEY, WEBHOOK_ENCRYPTION_SALT Encrypt every endpoint secret in the database. Back them up. Never change the salt once data exists
JWT_SECRET Signs sign-in sessions
POSTGRES_PASSWORD, DB_PASSWORD The first creates the database user and the second is what the API and worker connect with. They must be equal
REDIS_PASSWORD Protects the cache
APP_BASE_URL The URL people type. Verification, invite and reset links are built from it
CORS_ALLOWED_ORIGINS Must include the origin the dashboard is served from, or sign-in fails with “Invalid CORS request”
TUNNEL_INGRESS_BASE_URL The public base of source ingress URLs and CLI tunnel URLs. Unset, it follows APP_BASE_URL
EMAIL_ENABLED, EMAIL_FROM, SMTP_* With email off, accounts are created already verified and invites cannot be sent
RAILHOOK_PORT, RAILHOOK_BIND Where the one published port listens
WEBHOOK_TRUSTED_PROXIES The proxies whose X-Forwarded-For is believed. Empty trusts nothing
WEBHOOK_ALLOW_PRIVATE_IPS, WEBHOOK_ALLOWED_HOSTS Keep private addresses blocked. List a named internal host to allow just that one
DB_HOST and COMPOSE_PROFILES To use a managed PostgreSQL, set the DB_* variables and leave embedded-db out of the profiles
PLATFORM_ADMIN_TOKEN Unlocks the operator endpoints. Empty keeps them unreachable
API_REPLICAS 2 keeps the API answering while ./railhook upgrade restarts it, at the cost of roughly another 600 MB. With 1, expect about 30 s of 502 per upgrade

ONBOARDING_EMAILS_ENABLED sends two mails to new accounts: a welcome once the address is verified, and one nudge two days later if the account’s organization has still sent and received no events. Each goes at most once per account, replies go to EMAIL_SUPPORT_ADDRESS, and nothing is sent unless EMAIL_ENABLED is on as well. Accounts that existed before the upgrade that added the setting never receive either.

Leave it off on your own instance. Both mails are written in the first person by Railhook’s author for Railhook Cloud, and your users did not sign up with him.

BLOG_ENABLED serves /blog, its posts and its feed. The image carries them only because railhook.io runs the same image as your instance: they are Railhook’s own articles, not your site’s.

It is off by default, and only railhook.io turns it on. Off, /blog and everything under it answer 404, the header and footer carry no Blog link, and /sitemap.xml lists no posts. The UI container reads it at startup, so changing it takes docker compose up -d ui, not a rebuild. On Kubernetes the setting is ui.publicBlog.

262

Database location

VariableDefaultDescription
DB_MODEembedded

embedded | external. Read by `make backup-db`, `restore-db` and `shell-db` only; Compose starts the PostgreSQL container when COMPOSE_PROFILES includes embedded-db.

COMPOSE_PROJECT_NAMErailhook

Compose project name. Pinned because the monitoring stack joins this project's network by name, and Compose would otherwise derive it from the clone's directory name.

DOCKER_REGISTRYunsetexample: ghcr.io/yourorg/railhook

Registry prefix for the api, worker and ui images; defaults to ghcr.io/vadymkykalo/railhook.

Application environment

VariableDefaultDescription
APP_ENVdevelopment

development | staging | production. Production refuses to start with placeholder secrets.

APP_BASE_URLhttp://localhost:8080

Public origin of the dashboard, used in emails, shared links, canonical tags and the sitemap. Include the port when it is not 80; this one matches RAILHOOK_PORT below.

Database - embedded mode (COMPOSE_PROFILES=embedded-db)

VariableDefaultDescription
POSTGRES_IMAGEpostgres:16-alpine

Image and credentials of the PostgreSQL container.

POSTGRES_DBwebhook_platform

Image and credentials of the PostgreSQL container.

POSTGRES_USERwebhook_user

Image and credentials of the PostgreSQL container.

POSTGRES_PASSWORDwebhook_dev_pass_12345

Image and credentials of the PostgreSQL container.

Database - external mode (no embedded-db profile)

VariableDefaultDescription
DB_HOSTpostgres

Database connection; DB_HOST stays `postgres` (the Compose service) in embedded mode.

DB_PORT5432

Database connection; DB_HOST stays `postgres` (the Compose service) in embedded mode.

DB_NAMEwebhook_platform

Database connection; DB_HOST stays `postgres` (the Compose service) in embedded mode.

DB_USERwebhook_user

Database connection; DB_HOST stays `postgres` (the Compose service) in embedded mode.

DB_PASSWORDwebhook_dev_pass_12345

Database connection; DB_HOST stays `postgres` (the Compose service) in embedded mode.

DB_POOL_MAX_SIZE20

API connection pool (connections; timeout in ms). Named apart from the worker's on purpose: a shared name once gave the worker the API's smaller pool.

DB_POOL_MIN_IDLE10

API connection pool (connections; timeout in ms). Named apart from the worker's on purpose: a shared name once gave the worker the API's smaller pool.

DB_POOL_CONNECTION_TIMEOUT30000

API connection pool (connections; timeout in ms). Named apart from the worker's on purpose: a shared name once gave the worker the API's smaller pool.

WORKER_DB_POOL_MAX_SIZE40

Worker connection pool. Smaller than the worker's thread count because no transaction spans the outbound HTTP call; check hikari_connections_pending under load before changing it.

WORKER_DB_POOL_MIN_IDLE20

Worker connection pool. Smaller than the worker's thread count because no transaction spans the outbound HTTP call; check hikari_connections_pending under load before changing it.

Redis configuration (for distributed rate limiting & concurrency)

VariableDefaultDescription
REDIS_IMAGEredis:7-alpine

Redis image, address, password and memory cap.

REDIS_HOSTredis

Redis image, address, password and memory cap.

REDIS_PORT6379

Redis image, address, password and memory cap.

REDIS_PASSWORDwebhook_redis_pass

Redis image, address, password and memory cap.

REDIS_MAXMEMORY256mb

Redis image, address, password and memory cap.

Security (required - must change for production)

VariableDefaultDescription
WEBHOOK_ENCRYPTION_KEYdev_encryption_key_32_chars_min!

Key that encrypts stored secrets, 32+ characters.

WEBHOOK_ENCRYPTION_SALTdev_encryption_salt_16_chars

PBKDF2 salt for the AES-256 key, 16+ characters, unique per deployment.

WEBHOOK_ENCRYPTION_KEYSempty

Keys for zero-downtime rotation as "version:key,version:key"; empty uses WEBHOOK_ENCRYPTION_KEY as version 1.

WEBHOOK_ENCRYPTION_KEY_ACTIVE_VERSION0

Key version new encryptions use; 0 picks the highest.

JWT_SECRETdev_jwt_secret_key_32_chars_minimum

JWT signing secret, 32+ characters.

PLATFORM_ADMIN_TOKENempty

Token for /api/v1/admin/** (X-Platform-Admin-Token header), including key rotation across all organizations; an organization OWNER does not get this. Empty keeps those endpoints closed.

PLATFORM_ADMIN_EMAILSempty

Comma-separated emails allowed into the platform admin panel (sign-in under 12 hours old). Empty means nobody.

API service

VariableDefaultDescription
API_IMAGE_TAGlatest

API image tag.

Worker service

VariableDefaultDescription
WORKER_IMAGE_TAGlatest

Worker image tag.

Scheduling (api + worker)

VariableDefaultDescription
SCHEDULING_POOL_SIZE8

Threads shared by all scheduled jobs. Keep at 8 or more: with Spring's default of 1, one slow job stalls every other one.

UI service

VariableDefaultDescription
RAILHOOK_PORT8080

Host port and bind address of the dashboard's nginx, the only published port; it also proxies the API. Use RAILHOOK_BIND=127.0.0.1 behind a reverse proxy.

RAILHOOK_BIND0.0.0.0

Host port and bind address of the dashboard's nginx, the only published port; it also proxies the API. Use RAILHOOK_BIND=127.0.0.1 behind a reverse proxy.

RAILHOOK_DOMAINempty

Domain and ACME contact for the built-in Caddy TLS terminator (COMPOSE_PROFILES=tls).

ACME_EMAILempty

Domain and ACME contact for the built-in Caddy TLS terminator (COMPOSE_PROFILES=tls).

UI_IMAGE_TAGlatest

UI image tag.

VITE_API_URLempty

Build-time only: API origin baked into the UI bundle. Leave empty; the UI's nginx proxies the API on the same origin. If set, add it to CORS_ALLOWED_ORIGINS too.

VITE_CSP_EXTRA_CONNECTempty

Build-time only: extra CSP connect-src origins, space-separated.

RAILHOOK_CONTACT_DOMAINempty

Domain for the sales@ and support@ addresses in the contact form and footer, e.g. example.com. Empty offers no addresses. Read by the ui container at start.

Data retention policies

VariableDefaultDescription
DATA_RETENTION_ATTEMPTS_DAYS90

Days to keep failed and successful (2xx) Attempts, and received incoming events.

DATA_RETENTION_SUCCESSFUL_ATTEMPTS_DAYS14

Days to keep failed and successful (2xx) Attempts, and received incoming events.

DATA_RETENTION_INCOMING_EVENTS_DAYS30

Days to keep failed and successful (2xx) Attempts, and received incoming events.

DATA_RETENTION_EVENTS_DAYS90

Days to keep Events and, by cascade, their Deliveries; -1 keeps them forever. The only bound on those tables when billing is off. Matches DATA_RETENTION_ATTEMPTS_DAYS on purpose.

DATA_RETENTION_DLQ_DAYS14

Days a Delivery or Forward stays in the DLQ (Failed Messages) before it is deleted; -1 keeps it until its Event expires.

DATA_RETENTION_MAX_ATTEMPTS_PER_DELIVERY10

Most recent Attempts kept per Delivery.

DATA_RETENTION_BATCH_SIZE1000

Rows per cleanup batch.

DATA_RETENTION_CRON0 0 2 * * *

Cleanup schedule (cron, daily at 2 AM).

DATA_RETENTION_LIMIT_CRON0 */30 * * * *

Limit enforcement schedule (cron, every 30 minutes).

DATA_RETENTION_BURST_CLEANUP_CRON0 0 */4 * * *

Extra cleanup for daytime error spikes (cron, every 4 hours).

DATA_RETENTION_TABLE_METRICS_INTERVAL_MS900000

Table size gauge refresh interval (ms).

Partition maintenance

VariableDefaultDescription
PARTITION_MAINTENANCE_ENABLEDtrue

Creates delivery_attempts and tunnel_request_log partitions ahead of need and drops those past retention; lookahead in months and weeks, schedule as cron.

PARTITION_MAINTENANCE_DELIVERY_ATTEMPTS_LOOKAHEAD_MONTHS3

Creates delivery_attempts and tunnel_request_log partitions ahead of need and drops those past retention; lookahead in months and weeks, schedule as cron.

PARTITION_MAINTENANCE_TUNNEL_LOG_LOOKAHEAD_WEEKS3

Creates delivery_attempts and tunnel_request_log partitions ahead of need and drops those past retention; lookahead in months and weeks, schedule as cron.

PARTITION_MAINTENANCE_CRON0 45 1 * * *

Creates delivery_attempts and tunnel_request_log partitions ahead of need and drops those past retention; lookahead in months and weeks, schedule as cron.

Rate limiting

VariableDefaultDescription
GLOBAL_RATE_LIMIT_PER_SECOND5000

Platform-wide request limit per instance (requests/s).

ORG_RATE_LIMIT_ENABLEDfalse

Per-organization request limit (requests/s), for shared installs where one tenant must not spend everyone's budget. Costs a Redis round trip per request, so leave it off when self-hosting.

ORG_RATE_LIMIT_PER_SECOND200

Per-organization request limit (requests/s), for shared installs where one tenant must not spend everyone's budget. Costs a Redis round trip per request, so leave it off when self-hosting.

GLOBAL_RATE_LIMIT_ENABLEDtrue

Per-organization request limit (requests/s), for shared installs where one tenant must not spend everyone's budget. Costs a Redis round trip per request, so leave it off when self-hosting.

EVENT_INGESTION_RATE_LIMIT_PER_SECOND100

Events per second per project (token bucket).

AUTH_RATE_LIMIT_LOGIN_PER_MINUTE10

Sign-in and registration attempts per minute, per IP and per email.

AUTH_RATE_LIMIT_REGISTER_PER_MINUTE5

Sign-in and registration attempts per minute, per IP and per email.

AUTH_RATE_LIMIT_REFRESH_PER_TOKEN_PER_MINUTE30

Session refreshes per minute, per refresh token and per IP. Separate from sign-in, whose budget page loads would otherwise exhaust.

AUTH_RATE_LIMIT_REFRESH_PER_IP_PER_MINUTE600

Session refreshes per minute, per refresh token and per IP. Separate from sign-in, whose budget page loads would otherwise exhaust.

Password authentication

VariableDefaultDescription
AUTH_BCRYPT_STRENGTH12

BCrypt cost, 4-31; each step doubles the work. Changing it never invalidates stored hashes.

AUTH_LOCKOUT_ENABLEDtrue

Lock an account after consecutive failed sign-ins. Held in Postgres, not Redis, so it does not weaken when Redis is down. Locks expire on their own and a password reset clears them.

AUTH_LOCKOUT_THRESHOLD5

Consecutive failures before the account locks.

AUTH_LOCKOUT_INITIAL_SECONDS60

First lockout, in seconds. Doubles with each further failure.

AUTH_LOCKOUT_MAX_SECONDS900

Cap on the doubling, in seconds; longer only helps someone locking a stranger out.

AUTH_LOCKOUT_FAILURE_WINDOW_MINUTES60

Minutes after which a failure no longer counts toward the lockout.

AUTH_SESSION_CLEANUP_INTERVAL_MS3600000

Interval for purging expired sessions (ms).

Webhook delivery

VariableDefaultDescription
WEBHOOK_MAX_PAYLOAD_SIZE_BYTES262144

Largest event payload accepted (bytes).

WEBHOOK_MAX_FANOUT_PER_EVENT100

Most Deliveries one Event may fan out to.

WEBHOOK_PAYLOAD_COMPRESSION_THRESHOLD_BYTES1024

Payloads larger than this are stored gzip-compressed (bytes).

ORGANIZATION_SUSPENSION_CACHE_TTL_SECONDS60

Seconds an organization's suspension state is cached on each node; other nodes see a change within this window.

WEBHOOK_PROJECT_RATE_LIMIT_PER_SECOND50

Deliveries per second one project may take from the shared worker pool. Not the plan's ingest limit: size it from WEBHOOK_OUTGOING_POOL_SIZE.

WEBHOOK_ENDPOINT_VERIFICATION_REQUIREDfalse

Require new endpoints to prove ownership before they receive deliveries (true | false).

WEBHOOK_INGRESS_REPLAY_WINDOW_MINUTES5

Minutes a received signature is remembered to reject replays. For a source signed with a plain body HMAC this is the only replay bound; raise it for such sources.

WEBHOOK_ALLOW_PRIVATE_IPSfalse

Allow delivery to private IPs (SSRF risk, e.g. cloud metadata). Development only; refused in production.

WEBHOOK_MAX_CONCURRENT_PER_ENDPOINT5

Concurrent deliveries per endpoint.

WEBHOOK_MAX_CONCURRENT_PER_TENANT20

Concurrent in-flight attempts per organization across all its endpoints, so one tenant's slow receivers cannot occupy the whole WEBHOOK_OUTGOING_POOL_SIZE.

WEBHOOK_CONCURRENCY_PERMIT_LEASE_SECONDS90

Seconds a concurrency permit lives before Redis reclaims it; must exceed the 60s maximum delivery timeout plus setup time.

WEBHOOK_OUTGOING_POOL_SIZE50

Threads for outgoing deliveries.

WEBHOOK_INCOMING_POOL_SIZE20

Threads for incoming forwards.

WEBHOOK_CLAIM_ENABLEDtrue

Worker claims due Deliveries and Forwards from Postgres (true | false).

WEBHOOK_CLAIM_IDLE_POLL_MS200

How long a poll that found nothing sleeps (ms); a full batch polls again at once.

WEBHOOK_CLAIM_TIMEOUT_SECONDS300

Seconds after which a claimed row whose worker has not finished is claimed again; keep above 60s plus transformation time.

WEBHOOK_CLAIM_MAX_TARGETS500

Endpoints or destinations one poll visits.

WEBHOOK_ASYNC_SHUTDOWN_TIMEOUT_SECONDS60

Seconds to wait for in-flight deliveries on shutdown.

CLIENT_ERROR_REPORTING_ENABLEDtrue

Dashboard error reporting.

Dashboard errors are posted back and written to this installation's own logs, never to a third party; reports per user per minute.

CLIENT_ERROR_REPORTS_PER_USER_PER_MINUTE20

Dashboard error reporting.

Dashboard errors are posted back and written to this installation's own logs, never to a third party; reports per user per minute.

WEBHOOK_MAX_RESPONSE_BODY_BYTES1048576

Receiver response body buffered per attempt (bytes); past it the status code alone decides. Only the first 10 KiB is stored.

TRANSFORM_SCRIPT_TIMEOUT_MS2000

JavaScript transformations.

Wall-clock limit per script run (ms); a runaway script holds one delivery slot this long. Exceeding any of these limits fails the Delivery, which is retried.

TRANSFORM_SCRIPT_MAX_MEMORY_MB128

Allocation limit per run (MB), counted as total allocation rather than live heap.

TRANSFORM_SCRIPT_MAX_OUTPUT_BYTES1048576

Largest body a script may return (bytes).

TRANSFORM_SCRIPT_MAX_CONSOLE_LINES100

console.* lines kept per run for the Transform Studio.

TRANSFORM_SCRIPT_MAX_CHARS65536

Largest script the engine compiles (characters).

WEBHOOK_POOL_MAX_CONNECTIONS200

Outgoing HTTP connection pool shared by deliveries and forwards (connections, seconds).

WEBHOOK_POOL_PENDING_ACQUIRE_TIMEOUT_SECONDS10

Outgoing HTTP connection pool shared by deliveries and forwards (connections, seconds).

WEBHOOK_POOL_MAX_IDLE_TIME_SECONDS60

Outgoing HTTP connection pool shared by deliveries and forwards (connections, seconds).

WEBHOOK_ALLOWED_HOSTSempty

Comma-separated host names exempt from the private-IP check, matched as written in the endpoint URL. Empty exempts nothing; other rules still apply.

WEBHOOK_TRUSTED_PROXIESempty

Comma-separated proxy IPs/CIDRs whose X-Forwarded-For sets the client IP (rate limits, audit, source IP checks); behind a CDN include its ranges. Empty trusts nothing.

Incoming webhooks

VariableDefaultDescription
WEBHOOK_INGRESS_BASE_URLhttp://localhost:8080

Base URL for ingress URLs shown in the dashboard.

WEBHOOK_INCOMING_MAX_PAYLOAD_SIZE_BYTES524288

Largest incoming payload accepted (bytes).

WEBHOOK_INCOMING_RATE_LIMIT_PER_SECOND100

Default ingress limit for a source that sets none (requests/s); 0 disables the default.

DLQ_MONITORING_INTERVAL_MS60000

DLQ monitoring interval (ms).

Ordering (FIFO delivery per endpoint)

VariableDefaultDescription
ORDERING_GAP_TIMEOUT_SECONDS60

Seconds an ordered Delivery waits on a gap before going out of order, and only once nothing in the gap is being or about to be attempted.

ORDERING_BUFFER_RESCHEDULE_DELAY_SECONDS5

Fallback poll for a Delivery parked behind a missing sequence (seconds).

ORDERING_DELIVERED_SEQ_TTL_HOURS24

TTL of delivered-sequence tracking in Redis (hours).

ORDERING_BUFFER_TTL_MINUTES10

TTL of the ordering buffer in Redis (minutes).

ORDERING_BUFFER_GAUGE_RESYNC_MS30000

How often the ordering buffer size gauge resyncs from Redis (ms).

ORDERING_STRANDED_SEQUENCE_AFTER_SECONDS120

Backfills ordered Deliveries left without a sequence number by a process that died after commit; only rows older than this many seconds, per batch. Below a few seconds it races ingest.

ORDERING_STRANDED_SEQUENCE_BATCH_SIZE500

Backfills ordered Deliveries left without a sequence number by a process that died after commit; only rows older than this many seconds, per batch. Below a few seconds it races ingest.

ORDERING_SEQUENCE_RECONCILIATION_LOOKBACK_HOURS48

How far back sequence reconciliation looks for active ordered endpoints (hours).

ORDERING_SEQUENCE_RECONCILIATION_INTERVAL_MS900000

Sequence reconciliation interval (ms).

Event time machine (replay)

VariableDefaultDescription
REPLAY_BATCH_SIZE200

Events read per replay batch.

REPLAY_BATCH_DELAY_MS50

Pause between replay batches (ms).

REPLAY_MAX_EVENTS_PER_SESSION500000

Most events one replay session may cover.

WEBHOOK_RETRY_AFTER_MAX_SECONDS21600

Longest a receiver's Retry-After (on 429 and 503) may delay the next attempt (seconds); it only ever delays. 0 ignores the header. Kept within DELIVERY_ESCALATION_HARD_CAP_HOURS.

Auto-disable a persistently failing target

VariableDefaultDescription
ENDPOINT_AUTO_DISABLE_ENABLEDtrue

Turn off an endpoint or destination that has only failed for a whole window, and notify its owners. Set it the same on api and worker.

ENDPOINT_AUTO_DISABLE_AFTER_HOURS72

Hours of failures without a success before disabling, at least 1. Shorter than DELIVERY_ESCALATION_HARD_CAP_HOURS on purpose.

ENDPOINT_AUTO_DISABLE_MIN_FAILURES10

Failed attempts that window must also contain, so an idle endpoint is not disabled.

ENDPOINT_AUTO_DISABLE_INTERVAL_MS300000

Sweep interval (ms) and targets checked per sweep.

ENDPOINT_AUTO_DISABLE_BATCH_SIZE200

Sweep interval (ms) and targets checked per sweep.

Circuit breaker (Redis-backed, shared across worker pods)

VariableDefaultDescription
CIRCUIT_BREAKER_FAILURE_RATE_THRESHOLD50

Failure rate that opens the circuit (percent, 0-100).

CIRCUIT_BREAKER_MINIMUM_CALLS5

Calls needed before the failure rate is evaluated.

CIRCUIT_BREAKER_WAIT_DURATION_SECONDS30

Seconds the circuit stays open before probing.

CIRCUIT_BREAKER_WINDOW_TTL_SECONDS120

Sliding window of the failure counters (seconds).

CIRCUIT_BREAKER_SLOW_CALL_THRESHOLD_MS10000

A call slower than this counts as slow, even with a 2xx (ms).

CIRCUIT_BREAKER_SLOW_CALL_RATE_THRESHOLD80

Slow call rate that opens the circuit (percent, 0-100).

Stale delivery escalation (hard-cap auto-DLQ)

VariableDefaultDescription
DELIVERY_ESCALATION_HARD_CAP_HOURS96

Hours after which a PENDING Delivery goes to the DLQ. Must cover the outgoing retry ladder's worst case (~83h), or startup fails.

DELIVERY_ESCALATION_BATCH_SIZE100

Deliveries per escalation batch.

Stale forward escalation (hard-cap auto-DLQ, Incoming)

VariableDefaultDescription
FORWARD_ESCALATION_HARD_CAP_HOURS24

Hours from receipt after which an outstanding Forward goes to the DLQ; batch size and interval (ms). Must cover the incoming retry ladder's worst case (~11h), or startup fails.

FORWARD_ESCALATION_BATCH_SIZE100

Hours from receipt after which an outstanding Forward goes to the DLQ; batch size and interval (ms). Must cover the incoming retry ladder's worst case (~11h), or startup fails.

FORWARD_ESCALATION_INTERVAL_MS300000

Hours from receipt after which an outstanding Forward goes to the DLQ; batch size and interval (ms). Must cover the incoming retry ladder's worst case (~11h), or startup fails.

DELIVERY_ESCALATION_INTERVAL_MS300000

Stale delivery check interval (ms).

Queue depth metrics

VariableDefaultDescription
QUEUE_DEPTH_METRICS_INTERVAL_MS900000

Queue depth gauge refresh interval (ms).

QUEUE_DEPTH_METRICS_RETENTION_DAYS30

Only rows created within this many days are counted.

Test endpoints (Request Bin)

VariableDefaultDescription
TEST_ENDPOINT_BASE_URLunset

Public origin of test endpoint URLs (…/hook/<slug>); defaults to APP_BASE_URL. Never an internal name.

TEST_ENDPOINT_MAX_PER_PROJECT10

Test endpoints per project.

TEST_ENDPOINT_MAX_TTL_HOURS72

Longest test endpoint lifetime (hours).

TEST_ENDPOINT_CLEANUP_INTERVAL_MS3600000

Expired test endpoint cleanup interval (ms).

Rules engine

VariableDefaultDescription
RULES_CACHE_REFRESH_MS30000

Compiled rules cache refresh (ms).

RULES_EXECUTION_LOG_RETENTION_DAYS7

Rule execution log retention (days).

Transformations

VariableDefaultDescription
TRANSFORMATION_HISTORY_LIMIT50

Published versions kept per transformation (up to ~64 KB each); the one in use is never dropped.

Workflow engine

VariableDefaultDescription
WORKFLOW_POOL_CORE_SIZE4

Workflow executor threads and queue capacity.

WORKFLOW_POOL_MAX_SIZE8

Workflow executor threads and queue capacity.

WORKFLOW_POOL_QUEUE_CAPACITY50

Workflow executor threads and queue capacity.

WORKFLOW_MAX_DURATION_SECONDS600

Longest a workflow run may take (seconds).

WORKFLOW_MAX_RECURSION_DEPTH3

Deepest workflow → CreateEvent → workflow chain.

WORKFLOW_STUCK_THRESHOLD_MINUTES15

Stuck execution recovery: threshold (minutes) and check interval (ms).

WORKFLOW_RECOVERY_INTERVAL_MS120000

Stuck execution recovery: threshold (minutes) and check interval (ms).

WORKFLOW_RESUME_INTERVAL_MS5000

How often suspended delay nodes are checked for being due (ms); the resolution of a delay.

WORKFLOW_RESUME_BATCH_SIZE50

Due executions resumed per tick; the rest wait for the next tick.

WORKFLOW_NODE_TIMEOUT_DEFAULT_SECONDS30

Per-node timeouts (seconds) and the timeout thread pool size.

WORKFLOW_NODE_TIMEOUT_HTTP_SECONDS60

Per-node timeouts (seconds) and the timeout thread pool size.

WORKFLOW_NODE_TIMEOUT_SLACK_SECONDS60

Per-node timeouts (seconds) and the timeout thread pool size.

WORKFLOW_NODE_TIMEOUT_CREATE_EVENT_SECONDS30

Per-node timeouts (seconds) and the timeout thread pool size.

WORKFLOW_NODE_TIMEOUT_POOL_SIZE16

Per-node timeouts (seconds) and the timeout thread pool size.

WORKFLOW_SHUTDOWN_AWAIT_SECONDS30

Seconds to wait for in-flight workflows on shutdown.

WORKFLOW_TRIGGER_OUTBOX_POLL_INTERVAL_MS2000

Workflow trigger outbox: poll interval (ms), batch size, attempts per trigger.

WORKFLOW_TRIGGER_OUTBOX_BATCH_SIZE50

Workflow trigger outbox: poll interval (ms), batch size, attempts per trigger.

WORKFLOW_TRIGGER_OUTBOX_MAX_ATTEMPTS3

Workflow trigger outbox: poll interval (ms), batch size, attempts per trigger.

WORKFLOW_TRIGGER_OUTBOX_MAX_PER_PROJECT5

Triggers claimed per project per batch.

WORKFLOW_TRIGGER_OUTBOX_MAX_CONCURRENT_PER_PROJECT3

Concurrent workflow executions per project.

WORKFLOW_TRIGGER_OUTBOX_STALLED_AFTER_MINUTES15

Minutes after which a trigger abandoned in PROCESSING is requeued, and the sweep interval (ms). Keep it above your longest workflow, or a live one runs twice.

WORKFLOW_TRIGGER_OUTBOX_STALLED_SWEEP_MS300000

Minutes after which a trigger abandoned in PROCESSING is requeued, and the sweep interval (ms). Keep it above your longest workflow, or a live one runs twice.

Device authorization

VariableDefaultDescription
DEVICE_AUTH_CLEANUP_INTERVAL_MS300000

Expired device code cleanup interval (ms).

Email verification

VariableDefaultDescription
EMAIL_ENABLEDfalse

Send real email (false logs links to the console). Also enforces email verification: while off, new accounts are marked verified. Turn on wherever registration is open to strangers.

EMAIL_FROMnoreply@example.com

Sender address; use a domain you own, or mail fails SPF/DKIM and lands in spam.

EMAIL_SUPPORT_ADDRESSempty

Contact address in the notice about an unexpected email change; empty says to contact the administrator.

ONBOARDING_EMAILS_ENABLEDfalse

Welcome and follow-up mail to new accounts, written for Railhook Cloud; keep off when self-hosting.

CONTACT_DAILY_LIMIT30

Contact form messages sent per UTC day, so the form cannot spend the mail quota verification needs.

SMTP_HOSTlocalhost

SMTP relay, used when EMAIL_ENABLED=true.

SMTP_PORT1025

SMTP relay, used when EMAIL_ENABLED=true.

SMTP_USERNAMEempty

SMTP relay, used when EMAIL_ENABLED=true.

SMTP_PASSWORDempty

SMTP relay, used when EMAIL_ENABLED=true.

SMTP_AUTHfalse

SMTP relay, used when EMAIL_ENABLED=true.

SMTP_STARTTLSfalse

SMTP relay, used when EMAIL_ENABLED=true.

Alert notifications

VariableDefaultDescription
ALERTS_NOTIFICATIONS_ENABLEDfalse

Send alert notifications to Slack, webhooks and email (false logs them instead).

ALERTS_EVALUATION_CRON0 * * * * *

Alert rule evaluation schedule (cron); how late an alert can be, not its window.

Entitlements (limits when billing is off)

VariableDefaultDescription
ENTITLEMENT_DEFAULT_RATE_LIMIT100

Rate limit (events/s) and fan-out applied when BILLING_ENABLED=false, instead of no limit.

ENTITLEMENT_DEFAULT_MAX_FANOUT50

Rate limit (events/s) and fan-out applied when BILLING_ENABLED=false, instead of no limit.

Captcha (registration)

VariableDefaultDescription
CAPTCHA_SECRET_KEYunset

Registration CAPTCHA secret and verify URL (Turnstile by default; hCaptcha works too). Empty means no challenge; required with APP_ENV=production and BILLING_ENABLED=true. Fails closed.

CAPTCHA_VERIFY_URLunsetexample: https://challenges.cloudflare.com/turnstile/v0/siteverify

Registration CAPTCHA secret and verify URL (Turnstile by default; hCaptcha works too). Empty means no challenge; required with APP_ENV=production and BILLING_ENABLED=true. Fails closed.

CAPTCHA_SITE_KEYunset

Site key and script for the registration page, read by the ui container at start. Set together with CAPTCHA_SECRET_KEY, or every registration is refused.

CAPTCHA_SCRIPT_URLunsetexample: https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit

Site key and script for the registration page, read by the ui container at start. Set together with CAPTCHA_SECRET_KEY, or every registration is refused.

Public webhook tester

VariableDefaultDescription
PUBLIC_TESTER_ENABLEDunsetexample: false

The anonymous /tester page; off by default. Live URLs per address and overall when on.

PUBLIC_TESTER_PER_ADDRESSunsetexample: 3

The anonymous /tester page; off by default. Live URLs per address and overall when on.

PUBLIC_TESTER_MAX_ACTIVEunsetexample: 5000

The anonymous /tester page; off by default. Live URLs per address and overall when on.

Live demo

VariableDefaultDescription
DEMO_ENABLEDunsetexample: false

Anonymous read-only demo organization; off by default, and turning it off deletes it. Session length and data refresh in minutes. Set on both api and ui.

DEMO_SESSION_TTL_MINUTESunsetexample: 30

Anonymous read-only demo organization; off by default, and turning it off deletes it. Session length and data refresh in minutes. Set on both api and ui.

DEMO_REFRESH_INTERVAL_MINUTESunsetexample: 60

Anonymous read-only demo organization; off by default, and turning it off deletes it. Session length and data refresh in minutes. Set on both api and ui.

DEMO_SCRIPT_RUNS_PER_SESSION_PER_MINUTEunsetexample: 20

JavaScript transformation runs per minute for a demo session and per IP. Runs hold request threads, so at a 2000 ms timeout 60 per IP bounds one address to two threads.

DEMO_SCRIPT_RUNS_PER_IP_PER_MINUTEunsetexample: 60

JavaScript transformation runs per minute for a demo session and per IP. Runs hold request threads, so at a 2000 ms timeout 60 per IP bounds one address to two threads.

Blog

VariableDefaultDescription
BLOG_ENABLEDunsetexample: false

Serve Railhook's own blog at /blog; off by default. Read by the ui container.

Web analytics

VariableDefaultDescription
WEB_ANALYTICS_TOKENunset

Cloudflare Web Analytics token; empty loads no analytics. Read by the ui container at start.

STATUS_PAGE_URLunset

Status page URL linked from the footer; empty shows no link. Read by the ui container at start.

Sign in with google

VariableDefaultDescription
GOOGLE_OAUTH_CLIENT_IDunset

"Continue with Google", off unless both are set. Redirect URI: <APP_BASE_URL>/api/v1/auth/oauth/google/callback

GOOGLE_OAUTH_CLIENT_SECRETunset

"Continue with Google", off unless both are set. Redirect URI: <APP_BASE_URL>/api/v1/auth/oauth/google/callback

Billing / plans

VariableDefaultDescription
BILLING_ENABLEDfalse

Enforce plan quotas (false = self-hosted, unlimited). In production it also requires EMAIL_ENABLED=true and CAPTCHA_SECRET_KEY.

BILLING_DEFAULT_PROVIDERnoop

noop | stripe | wayforpay; noop with billing on runs the free plan only.

STRIPE_SECRET_KEYunsetexample: sk_test_...

Stripe credentials; STRIPE_PRICE_MAP is plan_interval=price_id (interval _monthly or _yearly).

STRIPE_WEBHOOK_SECRETunsetexample: whsec_...

Stripe credentials; STRIPE_PRICE_MAP is plan_interval=price_id (interval _monthly or _yearly).

STRIPE_PRICE_MAPunsetexample: starter_monthly=price_xxx,starter_yearly=price_xxy,pro_monthly=price_yyy,pro_yearly=price_yyz

Stripe credentials; STRIPE_PRICE_MAP is plan_interval=price_id (interval _monthly or _yearly).

BILLING_RECONCILIATION_CRON0 0 */6 * * *

Schedule for syncing billing state from Stripe (cron).

WAYFORPAY_MERCHANT_ACCOUNTunsetexample: your_merchant_account

WayForPay credentials; WAYFORPAY_PLAN_PRICES is plan_interval=amount_in_cents.

WAYFORPAY_MERCHANT_SECRETunsetexample: your_merchant_secret_key

WayForPay credentials; WAYFORPAY_PLAN_PRICES is plan_interval=amount_in_cents.

WAYFORPAY_MERCHANT_DOMAINunsetexample: example.com

WayForPay credentials; WAYFORPAY_PLAN_PRICES is plan_interval=amount_in_cents.

WAYFORPAY_SERVICE_URLunsetexample: https://example.com/api/v1/billing/webhook/wayforpay

WayForPay credentials; WAYFORPAY_PLAN_PRICES is plan_interval=amount_in_cents.

WAYFORPAY_PLAN_PRICESunsetexample: starter_monthly=2900,starter_yearly=29000,pro_monthly=9900,pro_yearly=99000

WayForPay credentials; WAYFORPAY_PLAN_PRICES is plan_interval=amount_in_cents.

PRERENDER_LOCALEen

Build-time only: locale the public pages are prerendered in.

Tunnel (CLI local webhook tunnels)

VariableDefaultDescription
TUNNEL_INGRESS_BASE_URLhttp://localhost:8080

Public base URL of incoming-source and tunnel URLs; defaults to APP_BASE_URL.

TUNNEL_HEARTBEAT_TIMEOUT_SECONDS120

Seconds without a heartbeat before a tunnel session is stale.

TUNNEL_CLEANUP_INTERVAL_MS60000

Stale session cleanup interval (ms).

TUNNEL_REQUEST_LOG_RETENTION_DAYS7

Tunnel request log retention (days), enforced by dropping weekly partitions.

Mcp server (ai agents)

VariableDefaultDescription
MCP_ENABLEDtrue

Serve the MCP server at /mcp, authenticated with a project API key.

MCP_OAUTH_ENABLEDtrue

OAuth sign-in for MCP connectors (claude.ai, ChatGPT); needs APP_BASE_URL to be the public origin. false accepts API keys only.

CORS configuration

VariableDefaultDescription
CORS_ALLOWED_ORIGINShttp://localhost:8080,http://localhost:5173

Comma-separated allowed origins. Must include the RAILHOOK_PORT origin: browsers send Origin on same-origin POSTs too.

Observability

VariableDefaultDescription
LOG_LEVELINFO

TRACE | DEBUG | INFO | WARN | ERROR

SWAGGER_ENABLEDfalse

Serve Swagger UI; keep false in production.

JVM tuning

VariableDefaultDescription
API_JAVA_OPTSunsetexample: -Xms256m -Xmx384m

Extra JVM flags, appended to the image's container tuning; an explicit -Xmx still wins.

WORKER_JAVA_OPTSunsetexample: -Xms384m -Xmx512m

Extra JVM flags, appended to the image's container tuning; an explicit -Xmx still wins.

METRICS_ENABLEDtrue

Expose Prometheus metrics.

Resource limits (Docker deploy.resources)

VariableDefaultDescription
POSTGRES_MEMORY_LIMITunsetexample: 1G

Container memory limits.

REDIS_MEMORY_LIMITunsetexample: 512m

Container memory limits.

API_MEMORY_LIMITunsetexample: 768m

Container memory limits.

WORKER_MEMORY_LIMITunsetexample: 768m

Container memory limits.

API_REPLICAS1

API containers. Two keep the API answering during `railhook upgrade` (about 600 MB more); one costs ~30s of 502 per upgrade while the JVM starts.

UI_MEMORY_LIMITunsetexample: 128m

UI container memory limit.

Monitoring stack (optional)

VariableDefaultDescription
GRAFANA_ADMIN_PASSWORDempty

Grafana admin password, 16+ characters; the stack refuses to start without it. Re-applied on every start.

GRAFANA_ADMIN_USERadmin

Grafana admin login.

GRAFANA_PORT3001

Loopback port Grafana is published on.

MONITORING_DOMAINempty

Host name to serve Grafana on through the built-in Caddy; empty keeps it loopback-only. Put an identity-aware proxy in front.

MONITORING_TLS_CERTempty

Certificate and key Caddy serves for MONITORING_DOMAIN instead of obtaining one, as paths inside the Caddy container. Both or neither.

MONITORING_TLS_KEYempty

Certificate and key Caddy serves for MONITORING_DOMAIN instead of obtaining one, as paths inside the Caddy container. Both or neither.

MONITORING_PROBE_URLSempty

Comma-separated URLs for the uptime probes; empty probes RAILHOOK_DOMAIN when set.

RAILHOOK_NETWORKempty

Platform Docker network to join; empty finds it by its Compose label.

MONITORING_NODENAMEempty

Server name shown on the Host dashboard; empty uses the hostname.

MONITORING_BACKUP_DIR../backups

Backup directory, relative to monitoring/, read by the BackupStale alert.

PROMETHEUS_RETENTION15d

Prometheus retention time and disk budget.

PROMETHEUS_RETENTION_SIZE4GB

Prometheus retention time and disk budget.

Alerting (optional)

VariableDefaultDescription
ALERTMANAGER_SLACK_WEBHOOK_URLempty

Alertmanager receivers. With none set, alerts show only in Grafana.

Slack incoming webhook URL and channel.

ALERTMANAGER_SLACK_CHANNEL#railhook-alerts

Alertmanager receivers. With none set, alerts show only in Grafana.

Slack incoming webhook URL and channel.

ALERTMANAGER_WEBHOOK_URLempty

Generic webhook receiver URL (PagerDuty Events v2, Opsgenie, …).

ALERTMANAGER_EMAIL_TOempty

Alert email recipient; sender and relay default to EMAIL_FROM and SMTP_*.

ALERTMANAGER_EMAIL_FROMempty

Alert email recipient; sender and relay default to EMAIL_FROM and SMTP_*.

ALERTMANAGER_SMTP_HOSTempty

Alert email recipient; sender and relay default to EMAIL_FROM and SMTP_*.

ALERTMANAGER_SMTP_PORTempty

Alert email recipient; sender and relay default to EMAIL_FROM and SMTP_*.

ALERTMANAGER_SMTP_USERNAMEempty

Alert email recipient; sender and relay default to EMAIL_FROM and SMTP_*.

ALERTMANAGER_SMTP_PASSWORDempty

Alert email recipient; sender and relay default to EMAIL_FROM and SMTP_*.

ALERTMANAGER_SMTP_REQUIRE_TLSempty

true | false; empty requires TLS except for localhost, mailpit and mailhog.

ALERTMANAGER_TELEGRAM_BOT_TOKENempty

Telegram bot token and chat id (negative for groups); both, or Telegram stays off.

ALERTMANAGER_TELEGRAM_CHAT_IDempty

Telegram bot token and chat id (negative for groups); both, or Telegram stays off.

ALERTMANAGER_HEARTBEAT_URLempty

Heartbeat URL pinged every minute by the Watchdog alert, so an outside service notices this host going down.

ALERTMANAGER_EXTERNAL_URLhttp://localhost:9093

Alertmanager's address in Slack and webhook payloads.

Log aggregation (Loki)

VariableDefaultDescription
LOKI_RETENTION_PERIOD168h

How long Loki keeps log lines (Go duration, e.g. 168h).

Backup configuration

VariableDefaultDescription
BACKUP_DIR./backups

Backup directory and retention (days) for `make backup-db` and the db-backup service.

BACKUP_RETENTION_DAYS30

Backup directory and retention (days) for `make backup-db` and the db-backup service.

DB_BACKUP_INTERVAL_SECONDS86400

Seconds between scheduled backups by the db-backup service.