Railway
Railway runs your app as a container behind its own proxy. WebDecoy runs inside the app, as your framework’s middleware, so it sees every request that reaches your code, including crawlers that never run JavaScript.
Start from a template
Section titled “Start from a template”Each template deploys a small app with WebDecoy already configured (the same repositories also deploy to Render), the client IP read correctly for Railway, and a /health route for Railway’s healthcheck. Use one as a starting point, or read its source and copy the few lines into your own app.
| Framework | Template | Source |
|---|---|---|
| Express | Deploy | WebDecoy/railway-express-starter |
| Next.js 16 | Deploy | WebDecoy/railway-nextjs-starter |
| Fastify 5 | Deploy | WebDecoy/railway-fastify-starter |
| Hono (Node) | Deploy | WebDecoy/railway-hono-starter |
| Angular SSR | Deploy | WebDecoy/railway-angular-ssr-starter |
- In WebDecoy, create an API key under Settings > API Keys.
- Deploy a template. Railway asks for one variable,
WEBDECOY_API_KEY: paste the key there. It is a secret, so keep it in Railway’s variables and never in a file you commit. - Open the service’s public domain and send a test request (see Prove it reports).
The middleware starts in monitor mode: detections are recorded and every request is still served.
A few framework details the templates already handle:
- Next.js runs detection in
proxy.ts, Next 16’s name for middleware. Next streams its pages, so the honeytoken link is rendered inapp/layout.tsxand the tripwire for it is armed inproxy.ts. - Fastify serves its HTML page as a string: the plugin can only inject the honeytoken into HTML it sees whole, not a file streamed from disk.
- Angular SSR refuses to render for a host it does not recognise.
*.up.railway.appis allowed; for a custom domain, set the optionalNG_ALLOWED_HOSTSvariable (comma-separated) or Angular answers 400.
Add it to an app already on Railway
Section titled “Add it to an app already on Railway”Follow the Express, Fastify or Next.js install, set WEBDECOY_API_KEY in the service’s Variables, and pass it to the middleware as apiKey. The SDK does not read the variable by itself.
The client IP on Railway
Section titled “The client IP on Railway”Railway’s proxy replaces any X-Forwarded-For the client sent with exactly two addresses: the client, then Railway’s edge. The connection your app accepts comes from a third, internal address. So the visitor is two hops from your app, not one:
// Any adapter, @webdecoy/* 0.18.0 or laterwebdecoy({ apiKey: process.env.WEBDECOY_API_KEY, trustProxy: 'railway' });
// Earlier versions: the same answer as a countwebdecoy({ apiKey: process.env.WEBDECOY_API_KEY, trustProxy: 2 });
// Or for Express as a whole (and Angular SSR's Express server)app.set('trust proxy', 2);Next’s middleware defaults to one hop, which on Railway is the edge. On Fastify, pass it to the WebDecoy plugin: Fastify’s own numeric trustProxy trusts nothing since 5.12.1 (see Fastify).
With the common trust proxy: 1, req.ip is Railway’s edge, and every visitor looks like the same few addresses. That hides who is scraping you and makes any per-IP rule useless. Railway also sets X-Real-IP to the client address, if you prefer to read that through the middleware’s getIP option.
If you put Cloudflare or another proxy in front of Railway, that is one more hop: count it too, or use trustProxy: 'cloudflare'.
Prove it reports
Section titled “Prove it reports”Request any page with the reserved test user agent:
curl -A "WebDecoy-Test/1.0" https://YOUR-APP.up.railway.app/A detection labeled Test appears on the Detections page within a few seconds. Test detections are excluded from stats and billing. If nothing arrives, check the deploy logs: the template logs WEBDECOY_API_KEY is not set at startup when the variable is missing.
Enforce
Section titled “Enforce”When you have seen what it would block, set mode: 'enforce'. Blocked requests then get a 403; pass onBlocked to answer them your own way. See Express middleware.