Vallkey's startup hook 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 Vercel for each runtime Vercel runs, 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 Vercel, under Project → Settings → Environment Variables, put the ghost key where the real key was, under the same name, such as
OPENAI_API_KEY. Then the real key is no longer in Vercel at all. - Never in a variable that starts with
NEXT_PUBLIC_: those are copied into the browser.
Do not put NODE_OPTIONS or RUBYOPT in Vercel's variables
Vercel uses your project's variables while it builds your app too. With NODE_OPTIONS=--require vallkey/auto there, every Node.js process in the build loads the hook before it is installed, and the deploy stops with Command "npm install" exited with 1. RUBYOPT breaks Ruby builds the same way. On Vercel, the hook is loaded from your code instead, as below. Only Python uses a variable, which is safe.
Set it up
Choose what your project runs. Each tab is complete on its own.
Tested: Next.js 16.4 on Node.js 24, fetch and node:https
Install the package
Terminalnpm install vallkeyAdd the instrumentation file
Next.js runs
register()once when a server starts, on your own machine and on Vercel.npx vallkey hookwrites the file for you:instrumentation.ts, or.jsin a JavaScript project, insidesrc/if your app lives there.Terminalnpx vallkey hook --yesinstrumentation.tsexport async function register() { // Sends server requests that carry a ghost key to Vallkey. if (process.env.NEXT_RUNTIME === 'nodejs') { await import('vallkey/auto'); }}Already have an instrumentation file? Add the
ifblock inside yourregister(). The check onNEXT_RUNTIMEmatters: the hook is for the Node.js runtime only.Deploy
Push, or run
vercel deploy --prod. Nothing else changes in your code.
Edge routes are not covered
Routes with export const runtime = 'edge' do not load the hook: in our test the provider received the ghost key and refused it. For those, set the SDK's API address to Vallkey instead, such as baseURL: 'https://api.vallkey.com/v1' for OpenAI. Each key's Connect tab in the dashboard has the exact line.
Tested: Vercel Functions (api/*.js) on Node.js 24, fetch and node:https
Install the package
Terminalnpm install vallkeyLoad the hook on the first line of each function
A function has no start command to put it in, so it goes at the very top of each file in
api/that calls an API:api/charge.jsrequire('vallkey/auto') // first line: sends requests that carry a ghost key to Vallkey const Stripe = require('stripe')const stripe = new Stripe(process.env.STRIPE_SECRET_KEY) // the ghost key module.exports = async (req, res) => { const balance = await stripe.balance.retrieve() res.json(balance)}In ES modules or TypeScript, the same line is
import 'vallkey/auto'. It must come before the SDK's own import.Deploy
Push, or run
vercel deploy --prod.
Tested: Vercel Functions on Bun 1.3, fetch
Run your functions on Bun
vercel.json{ "bunVersion": "1.x"}Install the package
Terminalbun add vallkeyLoad the hook on the first line of each function
api/check.tsimport 'vallkey/auto' // first line: sends requests that carry a ghost key to Vallkey export default { async fetch(request: Request) { const res = await fetch('https://api.openai.com/v1/models', { headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY}` }, // the ghost key }) return new Response(res.body, { status: res.status }) },}Deploy
Push, or run
vercel deploy --prod.
Tested: Vercel Functions on Python 3.12, requests, httpx and urllib
Add vallkey to your dependencies
In
requirements.txt, or underdependenciesinpyproject.toml:requirements.txtvallkeyopenaiTurn the hook on with one variable
In Project → Settings → Environment Variables, add
VALLKEY_AUTOwith the value1. Python loads the hook by itself when it starts. No line of code changes, and this variable is safe during the build.Terminalprintf 1 | vercel env add VALLKEY_AUTO productionDeploy
Push, or run
vercel deploy --prod.
If Vercel says "No python entrypoint found"
That is Vercel's own check, not Vallkey's. Its message names your function; add what it suggests to pyproject.toml, for example:
[tool.vercel]entrypoint = "api.check:handler"Tested: Vercel Functions on Ruby 3.3, Net::HTTP
Add the gem
Gemfilesource "https://rubygems.org"gem "vallkey"Load the hook on the first line of each function
api/check.rbrequire "vallkey/auto" # first line: sends requests that carry a ghost key to Vallkeyrequire "net/http" Handler = Proc.new do |request, response| # call the API with the ghost key, as the provider's docs showendFaraday, HTTParty and rest-client are built on Net::HTTP, so they are covered by the same line.
Deploy
Push, or run
vercel deploy --prod.
Check that it works
Every answer that went through Vallkey carries an x-guard-request-id header. With one real request from your deployed app, 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 function.# 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 app. npx vallkey statuslists every place the key is used from. A working Vercel deployment shows up by itself, such as Vercel · Node.js 24.21 · production. Name it withVALLKEY_ENV=productionin the project's variables.
If something is off
The build fails at npm install, or at gem install bundler
NODE_OPTIONS or RUBYOPT is set in Vercel's variables. Remove it there, load the hook in code as above, and deploy again.
The provider says the API key is invalid
The request went straight to the provider with the ghost key, which only Vallkey understands. The hook is not loaded in that function: check that its first line loads vallkey/auto, that a Next.js route is not on the Edge runtime, and for Python that VALLKEY_AUTO=1 is set for that environment (Production, Preview).
No x-guard-request-id header
Same cause: that request did not go through Vallkey. Add VALLKEY_DEBUG=1 to the variables to log each request the hook sends to Vallkey (never the key), then look at the function's logs.
Verify says the request was blocked, or not found
Blocked means it reached Vallkey and your rules refused it: open the key's Activity tab for the reason. Not found means it is older than five minutes, or belongs to another key: make a new request and verify again.
More
- How the hook works, for every language and host
- The same on Railway
- No-code mode in the docs, with the HTTP clients each language covers