Token Vault
Guides

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_SEED secret (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-code if you support TOTP) — they need Access-Control-Allow-Origin set 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.txt is conformant. A wire-parity conformance suite ships in examples/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:

ngrok — needs a static domain
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 --build
Cloudflare Tunnel — no open ports
git clone https://github.com/c-lgrant/tvault.git
cd tvault/examples/webhook
CF_TUNNEL_TOKEN=<your-tunnel-token> \
TUNNEL=cloudflared docker compose up --build

Replace:

  • <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:

  1. Continue the guided setup in the app — paste the URL at the "Enter URL" step, or open your webhook's own /bind page directly.
  2. Your webhook mints a one-time registration link and redirects you to Token Vault's confirm screen.
  3. 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.
  4. Click Connect Webhook. Token Vault calls your webhook's /v1/exchange endpoint once, receives the HMAC secret, and the two sides now share a secret for every future request.

Next

On this page