Vallkey's auto-connect sends every request that carries a ghost key to Vallkey, which checks it against your rules and swaps in the real key. Requests without a ghost key are left alone, and your code calls the API exactly as the provider's docs show. This guide sets it up on Railway for each language, the way we tested it on real deployments.
Before you start
- A ghost key from the dashboard, or from
npx vallkey initin your project. - In your service's Variables, put the ghost key where the real key was, under the same name, such as
OPENAI_API_KEY. - Optional:
VALLKEY_ENV=productionin the same Variables names this app invallkey statusand on the key's Activity tab.
Do not put NODE_OPTIONS, RUBYOPT or JAVA_TOOL_OPTIONS in Railway's variables
Railway uses your service's variables while it builds the app too. With NODE_OPTIONS or a similar setting there, Railway's own install steps try to load auto-connect before it is installed, and the build fails: Cannot find module 'vallkey/auto' for Node.js, a failed gem install bundler for Ruby, Error opening zip file or JAR manifest missing for Java. Turn it on in your start command, a Procfile, railpack.json or a Dockerfile, as below. Python's VALLKEY_AUTO=1 is safe in the variables.
Set it up
Choose your language. Each tab is complete on its own.
Tested: Node.js 20 on Railway, fetch and node:https
Install the package
Terminalnpm install vallkeyTurn on auto-connect in your start script
npx vallkey hookoffers to change your start script inpackage.json, and shows the change first:package.json"scripts": { "start": "node --require vallkey/auto server.js"}Starting with something other than
node, such as tsx? Put the setting in front of the command instead. We tested this form withnode; it works the same in front of any command:package.json"scripts": { "start": "NODE_OPTIONS='--require vallkey/auto' tsx server.ts"}Deploy
Push, or run
railway up.
Tested: Python 3.13 on Railway, requests, httpx and urllib
Add vallkey to your dependencies
requirements.txtvallkeyrequestsTurn on auto-connect with one variable
In the service's Variables, add
VALLKEY_AUTOwith the value1. Python turns on auto-connect by itself when it starts. No code changes, and no start command to edit.Terminalrailway variables --set VALLKEY_AUTO=1Deploy
Setting a variable redeploys the service. Otherwise push, or run
railway up.
Tested: Ruby on Railway, Net::HTTP
Add the gem
Terminalbundle add vallkeyTurn on auto-connect in your Procfile
npx vallkey hookreads your Procfile and shows the line to use. For a plain Ruby app:Procfileweb: bundle exec ruby -rvallkey/auto app.rbFor any other command, such as Puma or Rails, put the setting in front of it. We tested this form with a plain Ruby app; it works the same in front of any command:
Procfileweb: RUBYOPT=-rvallkey/auto bundle exec puma -C config/puma.rbFaraday, HTTParty and rest-client are built on Net::HTTP, so they are covered too.
Deploy
Push, or run
railway up.
Tested: Java 21 on Railway, HttpClient and HttpURLConnection, with and without Docker
For Java, auto-connect is a file, vallkey-agent.jar, so it has to be part of what Railway deploys. Kotlin and Scala work the same way. Two ways, both tested:
Without Docker: railpack.json
Download auto-connect into your project
Terminalnpx vallkey hook javaIt saves
.vallkey/vallkey-agent.jarand checks its checksum. Commit the.vallkeyfolder: Railway builds what is in your repository.Add railpack.json next to it
railpack.json{ "deploy": { "inputs": ["...", { "local": true, "include": [".vallkey"] }], "variables": { "JAVA_TOOL_OPTIONS": "-javaagent:/app/.vallkey/vallkey-agent.jar" } }}inputscopies the auto-connect file into the deployed app: Railway's Java build keeps only target/, so without it the file would not be there.variablessets the setting for the running app only, never for the build.npx vallkey hook javaprints this file for you.Deploy
Push, or run
railway up.
With Docker
Railway builds your Dockerfile when it finds one. Add the two lines npx vallkey hook java prints to the final stage, after your app is built. They download auto-connect, check its checksum, and turn it on:
# The first line of the file must be: # syntax=docker/dockerfile:1ADD --checksum=sha256:<from npx vallkey hook java> https://vallkey.com/downloads/hooks/0.1.0/vallkey-agent-0.1.0.jar /opt/vallkey/vallkey-agent.jarENV JAVA_TOOL_OPTIONS="-javaagent:/opt/vallkey/vallkey-agent.jar"Tested: .NET 10 on Railway, HttpClient, with and without Docker
For .NET, auto-connect is a file, Vallkey.Auto.dll, turned on by DOTNET_STARTUP_HOOKS. It needs .NET 8 or later; apps published as native AOT or trimmed cannot load it. Two ways, both tested:
Without Docker: railpack.json
Download auto-connect into your project
Terminalnpx vallkey hook dotnetIt saves
.vallkey/Vallkey.Auto.dlland checks its checksum. Commit the.vallkeyfolder: Railway builds what is in your repository.Add railpack.json next to it
railpack.json{ "deploy": { "inputs": ["...", { "local": true, "include": [".vallkey"] }], "variables": { "DOTNET_STARTUP_HOOKS": "/app/.vallkey/Vallkey.Auto.dll" } }}inputscopies the auto-connect file into the deployed app: the auto-connect file has to be next to your app when it runs.variablessets the setting for the running app only, never for the build.npx vallkey hook dotnetprints this file for you.Deploy
Push, or run
railway up.
With Docker
Railway builds your Dockerfile when it finds one. Add the two lines npx vallkey hook dotnet prints to the final stage, after your app is built. They download auto-connect, check its checksum, and turn it on:
# The first line of the file must be: # syntax=docker/dockerfile:1ADD --checksum=sha256:<from npx vallkey hook dotnet> https://vallkey.com/downloads/hooks/0.1.0/Vallkey.Auto-0.1.0.dll /opt/vallkey/Vallkey.Auto.dllENV DOTNET_STARTUP_HOOKS="/opt/vallkey/Vallkey.Auto.dll"Check that it works
Every answer that went through Vallkey carries an x-guard-request-id header. With one real request from your deployed service, the CLI confirms it was that request, through that key:
# 1. Call your deployed app once, then copy the x-guard-request-id# header from the response Vallkey sent back to your service.# 2. Check that exact request:npx vallkey verify KEY_ID --request-id REQUEST_ID ✓ Verified This exact request went through Vallkey and got a successful answer.- On a test key, make a harmless request your rules block, and check it with
--expect denied. Seeing it refused proves your rules apply to the deployed service. npx vallkey statuslists every app the key is used from. A working service shows up by itself, such as Railway · Node.js 20.20 · production or Railway · Java 21.0 · production.
Every Railway service shows up on its own
Give each service its own ghost key for the same real key (Add another app on the key's page): each gets its own rules, limits and kill switch, and vallkey status shows each one apart.
If something is off
The build fails with Cannot find module 'vallkey/auto'
NODE_OPTIONS is set in the service's variables. Delete it there and turn on auto-connect in your start script. The same goes for RUBYOPT (the build fails at gem install bundler) and JAVA_TOOL_OPTIONS (it fails with Error opening zip file).
Java or .NET starts, but requests do not go through Vallkey
The auto-connect file is not in the deployed app, or the setting points to the wrong folder. Check that the .vallkey folder is committed and that railpack.json includes it. The path in the setting starts with /app/, where Railway puts your app.
The provider says the API key is invalid
The request went straight to the provider with the ghost key. Auto-connect is not on: check the start command or Procfile Railway actually uses, under the service's Settings → Deploy. A custom start command set there replaces your package.json script and Procfile.
No x-guard-request-id header
That request did not go through Vallkey. Add VALLKEY_DEBUG=1 to the variables to log each request auto-connect sends to Vallkey (never the key), then read the deploy logs.
More
- How auto-connect works, for every language and host
- The same on Vercel
- Auto-connect in the docs, with the HTTP clients each language covers