Self-host your webhook
Run the Token Vault webhook yourself — requirements, supported runtimes, exposing it publicly, and binding it to your account.
Your webhook is the data plane: the small server that actually holds your credentials. Token Vault never runs it and never sees what's inside it — it only ever talks to its public URL. Self-hosting means you run that server yourself, on infrastructure you control, instead of using the one-click Cloudflare Workers deploy.
Prefer the one-click path?
Deploy to Cloudflare Workers needs no server to keep online and is the recommended default. Self-host when you already run infrastructure you'd rather use — a homelab box, a VPS, or your own cloud account — or when you're building a webhook in a language or platform of your own.
Requirements
Whatever you run, it must satisfy all of these:
- A public HTTPS URL. Token Vault's browser and backend both need to reach it —
localhost,127.0.0.1, and private IP ranges (10.0.0.0/8,172.16.0.0/12,192.168.0.0/16, link-local) are rejected at bind time. A tunnel (ngrok, Cloudflare Tunnel) in front of a machine with no public IP satisfies this. - Persistent storage. Your webhook holds token documents, proxy configs, and audit events across restarts — a Docker volume, a database, or Cloudflare D1, depending on runtime. Losing this storage loses every stored credential, not just convenience state.
- The
TV_WEBHOOK_SEEDsecret (or an equivalent HMAC + encryption key pair). This one root secret is what the HMAC signing key and, if you encrypt at rest, the AES-256-GCM key are derived from. Generate it once, keep it durable across redeploys, and never send it anywhere — Token Vault never sees it and never asks for it. - CORS allowing this Token Vault origin. Some endpoints are called by the browser directly (
/v1/credential,/v1/store, and/v1/totp-codeif you support TOTP) — they needAccess-Control-Allow-Originset to your Token Vault frontend's origin, or those browser-direct calls fail even though the webhook itself is healthy. - Conformance to the webhook contract. Every endpoint, header, and payload shape your webhook must implement is specified machine-readably at
tokenvault.uk/llm.txt— feed it to an LLM with your stack of choice, or implement it by hand.
Encryption at rest is optional, not required
Token Vault does not require or verify that credentials are encrypted on your webhook — that choice, like everything else about custody, is yours. Encrypting at rest with the key derived from TV_WEBHOOK_SEED is strongly recommended for anything beyond a local test.
Runtimes
Pick whichever fits how you already deploy:
- Docker image —
ghcr.io/c-lgrant/tvault-webhook:latest, the published build of the TypeScript reference webhook (examples/webhook). Runs anywhere Docker does: a homelab box, a VPS, Cloud Run, or Lambda with a container runtime. - Node, from source — clone
c-lgrant/tvault,cd examples/webhook,npm install,npm run dev:node. Useful if you want to read or modify the implementation before running it. - Your own implementation — any language or framework that implements the contract at
llm.txtis conformant. A wire-parity conformance suite ships inexamples/webhook/test/conformance/if you want to check your implementation against the same fixtures the reference passes.
Exposing it
If your webhook doesn't already have a public IP, put a tunnel in front of it. Both work with the Docker image's docker-compose.yml:
git clone https://github.com/c-lgrant/tvault.git
cd tvault/examples/webhook
NGROK_AUTHTOKEN=<your-ngrok-token> \
NGROK_URL=<your-domain>.ngrok-free.app \
TUNNEL=ngrok docker compose up --buildgit clone https://github.com/c-lgrant/tvault.git
cd tvault/examples/webhook
CF_TUNNEL_TOKEN=<your-tunnel-token> \
TUNNEL=cloudflared docker compose up --buildReplace:
<your-ngrok-token>— your auth token from the ngrok dashboard.<your-domain>— a static domain you've reserved (free tier works). A static domain matters: Token Vault needs the same URL to keep working across tunnel restarts, and a random ngrok domain changes every time the tunnel reconnects.<your-tunnel-token>— the connector token from a tunnel you created in the Cloudflare Zero Trust dashboard (Networks → Tunnels → Create a tunnel).
The named Docker volume keeps the generated HMAC secret and (if enabled) AES key stable across restarts — without it, a redeploy re-provisions keys and breaks the existing binding to your Token Vault account.
Bind it
Once your webhook is reachable at its public URL:
- Continue the guided setup in the app — paste the URL at the "Enter URL" step, or open your webhook's own
/bindpage directly. - Your webhook mints a one-time registration link and redirects you to Token Vault's confirm screen.
- Check the URL shown there before clicking Connect. It's the one anti-phishing control in this flow: the confirm screen exists specifically so you can verify the webhook you're about to bind is the one you deployed, not a URL substituted somewhere in transit. Token Vault also rejects local and private-IP URLs outright at this step.
- Click Connect Webhook. Token Vault calls your webhook's
/v1/exchangeendpoint once, receives the HMAC secret, and the two sides now share a secret for every future request.
Next
How to Give Claude Secure Access to Your API Keys
Three ways to let Claude Code and Claude.ai use your API keys and OAuth tokens without pasting secrets into config files — OAuth 2.1 MCP, credential fetch, and the MCP proxy.
Hardening Your Webhook
Rate limits, backoff, and budget alerts that keep a runaway agent from burning your webhook's quota — Token Vault's defaults plus the Cloudflare-side rules worth adding.