Guides

Connect your app to Vallkey without code changes

How the startup hook works in Node.js, Bun, Python, Ruby, Java and .NET, which host needs what, and the one setting that breaks builds.

Updated October 2026

Tested on real Vercel and Railway deployments, and with 165 official SDK calls in Docker, October 2026

Most apps call APIs through an SDK: OpenAI, Stripe, Twilio and so on. With Vallkey, the app holds a ghost key instead of the real key. A small hook, loaded when your app starts, sends every request that carries a ghost key to Vallkey. Vallkey checks it against your rules, swaps in the real key and passes the answer back. Your code stays exactly as the SDK's docs show it.

How it works

  1. Your app starts, and loads the hook

    One setting, a start command or a single line loads it, depending on where the app runs. Nothing else in your code changes.

  2. A request carries a ghost key

    The hook sees the ghost key, in a header, the address or a form body, and sends the request to api.vallkey.com with the same path. Requests without a ghost key are not touched: your database, your own APIs and every other call go where they always went.

  3. Vallkey checks it and swaps in the real key

    Your rules decide: which endpoints, how much per day, from where. Allowed requests reach the provider with the real key, which never leaves Vallkey. Blocked ones get a clear error. Every answer comes back with an x-guard-request-id header.

What each host needs

The hook is the same everywhere. What changes is how your host starts the app. Links open the step-by-step guide for that host and language.

LanguageVercelRailwayDocker or your own server
Node.jsNext.js: instrumentation file. Functions: first linetestedIn the start scripttestedNODE_OPTIONS where the app runstested
BunFirst line of each functiontestedIn the start scriptBUN_OPTIONS where the app runstested
PythonVALLKEY_AUTO=1 in the variablestestedVALLKEY_AUTO=1 in the variablestestedVALLKEY_AUTO=1tested
RubyFirst line of each functiontestedIn the ProcfiletestedRUBYOPT where the app runstested
Java, Kotlin, ScalaNot availablerailpack.json or a DockerfiletestedJAVA_TOOL_OPTIONS and the agent filetested
.NETNot availablerailpack.json or a DockerfiletestedDOTNET_STARTUP_HOOKS and the hook filetested
PHPNot availableA DockerfileThe extension, with PHP_INI_SCAN_DIRtested
Elixir, ErlangNot availableA DockerfileELIXIR_ERL_OPTIONS and the hook filetested
Go, RustThe SDK's address settingThe SDK's address settingThe SDK's address setting

Tested means we ran it on a real deployment, or, for Docker, with each language's official SDKs in a container that could reach nothing but Vallkey. The rest follows the same rules but has not been run on that host yet.

The one setting that breaks builds

Node.js, Bun, Ruby, Java and .NET are told to load the hook by a startup setting: NODE_OPTIONS, BUN_OPTIONS, RUBYOPT, JAVA_TOOL_OPTIONS, DOTNET_STARTUP_HOOKS. Every process of that language reads it, including the ones that build your app.

Vercel, Railway, Render and Heroku use your variables while they build

A startup setting in their variables reaches npm install, bundle install or the Java build before the hook is installed, and the build fails. We saw it on Vercel and Railway. Put it where only the running app reads it: the start command, a Procfile, the first line of a function, railpack.json, or the final stage of a Dockerfile, after your app is built.

SettingIn the host's variablesWhere it goes instead
VALLKEY_AUTO=1Safe (Python)Nowhere else needed
VALLKEY_ENVSafeNames the place, such as production
VALLKEY_DEBUG=1SafeLogs each request sent to Vallkey, never the key
NODE_OPTIONSBreaks the buildThe start script, or the first line of a function
RUBYOPTBreaks the buildThe Procfile, or the first line of a function
JAVA_TOOL_OPTIONSBreaks the buildrailpack.json, or the final stage of a Dockerfile
DOTNET_STARTUP_HOOKSNot where the file israilpack.json, or the final stage of a Dockerfile

What the hook covers

The HTTP clients each language's hook reaches, and with them the SDKs built on them:

LanguageCovers
Node.js and Bunfetch, http and https: almost every JavaScript SDK
Pythonhttpx, requests, urllib3, boto3, aiohttp, urllib
RubyNet::HTTP, and Faraday, HTTParty and rest-client on top of it
Java, Kotlin, ScalaOkHttp, Apache HttpClient 4 and 5, the JDK HttpClient, HttpURLConnection
.NETHttpClient, on .NET 8 and later
PHPThe curl extension: Guzzle, Symfony HttpClient and the SDKs' own clients
Elixir, ErlangFinch (Req), hackney (HTTPoison), httpc (Tesla's default)

Set it up from your terminal

In your project folder, the CLI tells the language from your files and does or prints each step:

Terminal
npx vallkey@latest connect   # the best way to connect this project; changes nothingnpx vallkey@latest hook      # sets up the hook: start script, Procfile, instrumentation file or hook filenpx vallkey@latest doctor    # checks this project is ready on this computer

It knows the hosts in this guide: in a Vercel project it shows the first-line setup, for a Node.js app with a start script it offers to change the script, and for Java and .NET it prints the Dockerfile lines and the railpack.json.

Check every deployment

A setup that works on your computer can still miss on a host. One real request from the deployed app settles it:

Terminal
npx vallkey verify KEY_ID --request-id REQUEST_ID               # REQUEST_ID: the x-guard-request-id of your app's answernpx vallkey verify KEY_ID --request-id REQUEST_ID --expect denied  # a request your rules block, on a test keynpx vallkey status                                               # every place each key is used from

Each deployment shows up by itself

The hook tells Vallkey where it runs, so a key's Activity tab and vallkey status list each place, such as Vercel · Node.js 24.21 · production or Railway · Python 3.13 · production, with its requests today and anything wrong there.

When to use the address setting instead

Some code cannot load a startup hook. There, set the SDK's API address to Vallkey, one line, shown on each key's Connect tab:

  • Edge functions and Next.js Edge routes: the hook covers the Node.js runtime only.
  • Go and Rust: compiled apps cannot be changed once built.
  • .NET native AOT or trimmed builds, which cannot load startup hooks.
  • Web pages and mobile apps: use short-lived keys instead, made for code that runs on people's devices.

Step-by-step guides

  • Vercel: Next.js, Node.js and Bun functions, Python, Ruby
  • Railway: Node.js, Python, Ruby, Java and .NET, with and without Docker
  • No-code mode in the docs: every setting, Docker lines for each language, and the CLI

Protect your app's keys without touching its code

Create a ghost key, turn on the hook, deploy. Your app never holds the real key again. Free plan, no card needed.