Vibecode Webhook.site
track this build5 phases, 10 steps, beginner friendly0%Catch a request, store it, display it. This is the smallest useful server there is, and the paid tier mostly buys permanence and the workflow actions. One process on any host covers the inspector in a sitting.
You are building a lean indie version of Webhook.site. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Webhook.site · indie build A webhook inspector you host: mint a URL, catch anything sent to it with headers and body intact, watch requests arrive live, replay one to your localhost, and let bins expire. Nine dollars a month buys permanence and scripting; this buys understanding. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | store and display | | Live updates | A 2-second vanilla-JS poll | no websocket library for a dev tool | | Hosting | A VPS behind Caddy | the catcher must be public | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. The URL must be reachable from the services that call it. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: A public address you own, so links you share never break when a provider changes. - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server - [ ] **Something that sends webhooks to test with** · free - Why: Phase 1 and 3 need real deliveries. - Get it: Stripe test mode, a GitHub repository webhook, or curl. ## Quick start ```sh mkdir hooks && cd hooks && git init && npm init -y && npm pkg set type=module mkdir -p data && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Custom actions, email hooks, DNS hooks, teams. - custom actions and workflow scripting - email and DNS hooks - team accounts - someone else hosting the public endpoint If one of those is essential to you, that is the reason to keep paying for Webhook.site, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Webhook.site The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a webhook inspector like Webhook.site. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22 with node:http and node:sqlite. No framework. One process behind Caddy on a public host. - Server-rendered HTML with a small vanilla-JS poller for live updates. No websockets library. ### Data model (create this before Phase 1) - bins: id (uuid), created_at, expires_at, note - requests: id, bin_id, received_at, method, path, query, headers (JSON), body (text or base64 when binary), content_type, size, ip_hash ### Phase 1 · Catch anything Build: any method on /b/:uuid and any subpath stores a row and answers 200 with a tiny JSON body. Cap the body at 1 MB, store binary as base64 with a flag, and never fail because the bin is busy (WAL mode). Done when: GET, POST with JSON, POST with form data and a PUT with a binary body all store correctly with headers intact, and a 2 MB body is refused with 413. Do not build yet: UI, replay, retention. ### Phase 2 · Inspector Build: /b/:uuid/inspect lists requests newest first with method, path, size and time, and a detail view with headers, pretty-printed JSON, raw body and a copy-as-curl button. A vanilla-JS poll every 2 seconds appends new rows without a reload. Done when: a request appears in the inspector within 2 seconds, JSON pretty-prints, and copy-as-curl reproduces the request. ### Phase 3 · Replay Build: a replay button that re-sends the stored request to a target URL you type (typically your localhost via a tunnel), preserving method, headers and body, and shows the response. Done when: a captured Stripe test event replays to a local server and the local server's response is shown. ### Phase 4 · Bins and retention Build: a home page that mints a bin, an expiry per bin (default 7 days, extendable behind basic auth), a nightly prune, and a per-IP rate limit on minting. Done when: an expired bin returns 410, pruning deletes its requests, and minting is rate limited. ### Phase 5 · Deploy Build: a /healthz endpoint, a systemd unit, the Caddy config, the README. Done when: state survives a restart and a stranger can mint a bin from the README's URL. ### Out of scope (and why) - Custom actions, email hooks, DNS hooks, teams. That is the subscription. ### README must contain - The body cap and the retention default. - A note that anyone with the URL can read the bin · do not send secrets to a test catcher. ===== AGENTS.md ===== # Agent instructions · Webhook.site indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22, node:http and node:sqlite, A 2-second vanilla-JS poll, A VPS behind Caddy. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ===== BUILD_PLAN.md ===== # Build plan · Webhook.site A webhook inspector you host: mint a URL, catch anything sent to it with headers and body intact, watch requests arrive live, replay one to your localhost, and let bins expire. Nine dollars a month buys permanence and scripting; this buys understanding. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Catch anything Any method, any subpath, stored intact, body capped. ### Steps 1. Create the project and tables bins (id, created_at, expires_at, note), requests (id, bin_id, received_at, method, path, query, headers JSON, body, content_type, size, ip_hash). ```sh mkdir hooks && cd hooks && git init && npm init -y && npm pkg set type=module mkdir -p data && cp .env.example .env ``` 2. Store any method on /b/:uuid and subpaths; body up to 1 MB, binary as base64; answer 200 with a tiny JSON ### Done when - [ ] GET, JSON POST, form POST and a binary PUT all store with headers intact - [ ] A 2 MB body is refused with 413 ## Phase 2 · Inspector Newest first, detail view, copy as curl, live poll. ### Steps 1. /b/:uuid/inspect listing requests and a detail view with pretty JSON and raw body 2. Copy-as-curl and a 2-second poll appending new rows ### Done when - [ ] A request appears within 2 seconds - [ ] JSON pretty-prints - [ ] Copy-as-curl reproduces the request ## Phase 3 · Replay Re-send a stored request to a URL you type. ### Steps 1. A replay form posting the stored method, headers and body to a target 2. Show the target's response ### Done when - [ ] A captured Stripe test event replays to a local server ## Phase 4 · Bins and retention Mint, expire, prune, rate limit. ### Steps 1. A home page that mints a bin; expiry per bin, extendable behind basic auth 2. Nightly prune and a per-IP mint limit ### Done when - [ ] An expired bin returns 410 - [ ] Pruning deletes its requests - [ ] Minting is rate limited ## Phase 5 · Deploy HTTPS, service, README. ### Steps 1. /healthz, systemd, Caddy 2. README: the body cap, retention, and that anyone with the URL can read the bin Files: `README.md` ### Done when - [ ] State survives a restart - [ ] A stranger can mint a bin from the README's URL ## Not in this build - Custom actions, email hooks, DNS hooks, teams. ## After v1, if you want it - A forwarding rule per bin to a real endpoint - Request search ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite file. DATABASE_PATH=./data/hooks.db # Required. Public base URL. SITE_URL=https://hooks.yourdomain.com # Optional. Default expiry for a bin. BIN_TTL_DAYS=7 # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
You are building a lean indie version of Webhook.site. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Webhook.site · indie build A webhook inspector you host: mint a URL, catch anything sent to it with headers and body intact, watch requests arrive live, replay one to your localhost, and let bins expire. Nine dollars a month buys permanence and scripting; this buys understanding. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | store and display | | Live updates | A 2-second vanilla-JS poll | no websocket library for a dev tool | | Hosting | A VPS behind Caddy | the catcher must be public | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. The URL must be reachable from the services that call it. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: A public address you own, so links you share never break when a provider changes. - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server - [ ] **Something that sends webhooks to test with** · free - Why: Phase 1 and 3 need real deliveries. - Get it: Stripe test mode, a GitHub repository webhook, or curl. ## Quick start ```sh mkdir hooks && cd hooks && git init && npm init -y && npm pkg set type=module mkdir -p data && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Custom actions, email hooks, DNS hooks, teams. - custom actions and workflow scripting - email and DNS hooks - team accounts - someone else hosting the public endpoint If one of those is essential to you, that is the reason to keep paying for Webhook.site, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Webhook.site The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a webhook inspector like Webhook.site. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22 with node:http and node:sqlite. No framework. One process behind Caddy on a public host. - Server-rendered HTML with a small vanilla-JS poller for live updates. No websockets library. ### Data model (create this before Phase 1) - bins: id (uuid), created_at, expires_at, note - requests: id, bin_id, received_at, method, path, query, headers (JSON), body (text or base64 when binary), content_type, size, ip_hash ### Phase 1 · Catch anything Build: any method on /b/:uuid and any subpath stores a row and answers 200 with a tiny JSON body. Cap the body at 1 MB, store binary as base64 with a flag, and never fail because the bin is busy (WAL mode). Done when: GET, POST with JSON, POST with form data and a PUT with a binary body all store correctly with headers intact, and a 2 MB body is refused with 413. Do not build yet: UI, replay, retention. ### Phase 2 · Inspector Build: /b/:uuid/inspect lists requests newest first with method, path, size and time, and a detail view with headers, pretty-printed JSON, raw body and a copy-as-curl button. A vanilla-JS poll every 2 seconds appends new rows without a reload. Done when: a request appears in the inspector within 2 seconds, JSON pretty-prints, and copy-as-curl reproduces the request. ### Phase 3 · Replay Build: a replay button that re-sends the stored request to a target URL you type (typically your localhost via a tunnel), preserving method, headers and body, and shows the response. Done when: a captured Stripe test event replays to a local server and the local server's response is shown. ### Phase 4 · Bins and retention Build: a home page that mints a bin, an expiry per bin (default 7 days, extendable behind basic auth), a nightly prune, and a per-IP rate limit on minting. Done when: an expired bin returns 410, pruning deletes its requests, and minting is rate limited. ### Phase 5 · Deploy Build: a /healthz endpoint, a systemd unit, the Caddy config, the README. Done when: state survives a restart and a stranger can mint a bin from the README's URL. ### Out of scope (and why) - Custom actions, email hooks, DNS hooks, teams. That is the subscription. ### README must contain - The body cap and the retention default. - A note that anyone with the URL can read the bin · do not send secrets to a test catcher. ===== AGENTS.md ===== # Agent instructions · Webhook.site indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22, node:http and node:sqlite, A 2-second vanilla-JS poll, A VPS behind Caddy. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ===== BUILD_PLAN.md ===== # Build plan · Webhook.site A webhook inspector you host: mint a URL, catch anything sent to it with headers and body intact, watch requests arrive live, replay one to your localhost, and let bins expire. Nine dollars a month buys permanence and scripting; this buys understanding. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Catch anything Any method, any subpath, stored intact, body capped. ### Steps 1. Create the project and tables bins (id, created_at, expires_at, note), requests (id, bin_id, received_at, method, path, query, headers JSON, body, content_type, size, ip_hash). ```sh mkdir hooks && cd hooks && git init && npm init -y && npm pkg set type=module mkdir -p data && cp .env.example .env ``` 2. Store any method on /b/:uuid and subpaths; body up to 1 MB, binary as base64; answer 200 with a tiny JSON ### Done when - [ ] GET, JSON POST, form POST and a binary PUT all store with headers intact - [ ] A 2 MB body is refused with 413 ## Phase 2 · Inspector Newest first, detail view, copy as curl, live poll. ### Steps 1. /b/:uuid/inspect listing requests and a detail view with pretty JSON and raw body 2. Copy-as-curl and a 2-second poll appending new rows ### Done when - [ ] A request appears within 2 seconds - [ ] JSON pretty-prints - [ ] Copy-as-curl reproduces the request ## Phase 3 · Replay Re-send a stored request to a URL you type. ### Steps 1. A replay form posting the stored method, headers and body to a target 2. Show the target's response ### Done when - [ ] A captured Stripe test event replays to a local server ## Phase 4 · Bins and retention Mint, expire, prune, rate limit. ### Steps 1. A home page that mints a bin; expiry per bin, extendable behind basic auth 2. Nightly prune and a per-IP mint limit ### Done when - [ ] An expired bin returns 410 - [ ] Pruning deletes its requests - [ ] Minting is rate limited ## Phase 5 · Deploy HTTPS, service, README. ### Steps 1. /healthz, systemd, Caddy 2. README: the body cap, retention, and that anyone with the URL can read the bin Files: `README.md` ### Done when - [ ] State survives a restart - [ ] A stranger can mint a bin from the README's URL ## Not in this build - Custom actions, email hooks, DNS hooks, teams. ## After v1, if you want it - A forwarding rule per bin to a real endpoint - Request search ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite file. DATABASE_PATH=./data/hooks.db # Required. Public base URL. SITE_URL=https://hooks.yourdomain.com # Optional. Default expiry for a bin. BIN_TTL_DAYS=7 # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
You are building a production product version of Webhook.site. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Webhook.site · product brief ## Problem Catch a request, store it, display it. This is the smallest useful server there is, and the paid tier mostly buys permanence and the workflow actions. One process on any host covers the inspector in a sitting. ## Product outcome A team inspector with permanent bins and replay, on your infrastructure. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a small always-on host with a public URL ## Explicit non-goals for v1 - Custom actions, email hooks, DNS hooks, teams. - custom actions and workflow scripting - email and DNS hooks - team accounts - someone else hosting the public endpoint ## Success criteria - Body cap and expiry verified - One restore drill performed ===== BRIEF.md ===== # Build brief · Webhook.site The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a webhook inspector like Webhook.site. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22 with node:http and node:sqlite. No framework. One process behind Caddy on a public host. - Server-rendered HTML with a small vanilla-JS poller for live updates. No websockets library. ### Data model (create this before Phase 1) - bins: id (uuid), created_at, expires_at, note - requests: id, bin_id, received_at, method, path, query, headers (JSON), body (text or base64 when binary), content_type, size, ip_hash ### Phase 1 · Catch anything Build: any method on /b/:uuid and any subpath stores a row and answers 200 with a tiny JSON body. Cap the body at 1 MB, store binary as base64 with a flag, and never fail because the bin is busy (WAL mode). Done when: GET, POST with JSON, POST with form data and a PUT with a binary body all store correctly with headers intact, and a 2 MB body is refused with 413. Do not build yet: UI, replay, retention. ### Phase 2 · Inspector Build: /b/:uuid/inspect lists requests newest first with method, path, size and time, and a detail view with headers, pretty-printed JSON, raw body and a copy-as-curl button. A vanilla-JS poll every 2 seconds appends new rows without a reload. Done when: a request appears in the inspector within 2 seconds, JSON pretty-prints, and copy-as-curl reproduces the request. ### Phase 3 · Replay Build: a replay button that re-sends the stored request to a target URL you type (typically your localhost via a tunnel), preserving method, headers and body, and shows the response. Done when: a captured Stripe test event replays to a local server and the local server's response is shown. ### Phase 4 · Bins and retention Build: a home page that mints a bin, an expiry per bin (default 7 days, extendable behind basic auth), a nightly prune, and a per-IP rate limit on minting. Done when: an expired bin returns 410, pruning deletes its requests, and minting is rate limited. ### Phase 5 · Deploy Build: a /healthz endpoint, a systemd unit, the Caddy config, the README. Done when: state survives a restart and a stranger can mint a bin from the README's URL. ### Out of scope (and why) - Custom actions, email hooks, DNS hooks, teams. That is the subscription. ### README must contain - The body cap and the retention default. - A note that anyone with the URL can read the bin · do not send secrets to a test catcher. ===== ARCHITECTURE.md ===== # Architecture · Webhook.site ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | store and display | | Live updates | A 2-second vanilla-JS poll | no websocket library for a dev tool | | Hosting | A VPS behind Caddy | the catcher must be public | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Catcher | /b routes | Any listener writing the rows | | Inspector | views and poll | Websockets later | | Replay | re-sending | Add scheduled replays | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PORT` · required · Any free port. - `DATABASE_PATH` · required · SQLite file. - `SITE_URL` · required · Public base URL. - `BIN_TTL_DAYS` · optional · Default expiry for a bin. - `ADMIN_USER` · required · Any username for the basic-auth admin pages. - `ADMIN_PASS` · required, secret · Generate one: openssl rand -base64 24. Never reuse a real password. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Webhook.site product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22, node:http and node:sqlite, A 2-second vanilla-JS poll, A VPS behind Caddy. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ===== MILESTONES.md ===== # Delivery milestones · Webhook.site Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Catch anything Any method, any subpath, stored intact, body capped. ### Steps 1. Create the project and tables bins (id, created_at, expires_at, note), requests (id, bin_id, received_at, method, path, query, headers JSON, body, content_type, size, ip_hash). ```sh mkdir hooks && cd hooks && git init && npm init -y && npm pkg set type=module mkdir -p data && cp .env.example .env ``` 2. Store any method on /b/:uuid and subpaths; body up to 1 MB, binary as base64; answer 200 with a tiny JSON ### Done when - [ ] GET, JSON POST, form POST and a binary PUT all store with headers intact - [ ] A 2 MB body is refused with 413 ## M2 · Inspector Newest first, detail view, copy as curl, live poll. ### Steps 1. /b/:uuid/inspect listing requests and a detail view with pretty JSON and raw body 2. Copy-as-curl and a 2-second poll appending new rows ### Done when - [ ] A request appears within 2 seconds - [ ] JSON pretty-prints - [ ] Copy-as-curl reproduces the request ## M3 · Replay Re-send a stored request to a URL you type. ### Steps 1. A replay form posting the stored method, headers and body to a target 2. Show the target's response ### Done when - [ ] A captured Stripe test event replays to a local server ## M4 · Bins and retention Mint, expire, prune, rate limit. ### Steps 1. A home page that mints a bin; expiry per bin, extendable behind basic auth 2. Nightly prune and a per-IP mint limit ### Done when - [ ] An expired bin returns 410 - [ ] Pruning deletes its requests - [ ] Minting is rate limited ## M5 · Deploy HTTPS, service, README. ### Steps 1. /healthz, systemd, Caddy 2. README: the body cap, retention, and that anyone with the URL can read the bin Files: `README.md` ### Done when - [ ] State survives a restart - [ ] A stranger can mint a bin from the README's URL ## M6 · Operate it like a product (production only) Only for the product-builder path: know when the catcher is down, never lose the database, and keep the server patched. ### Steps 1. Add a /healthz endpoint and an external uptime check against it Answer 200 with the build id and a quick database read. Point a free uptime monitor (or your own, from the Healthchecks entry on this site) at it so an outage is noticed before a user notices. 2. Write structured request logs and rotate them One JSON line per request: method, path, status, duration, no raw IPs. Rotate weekly with logrotate, keep eight. 3. Back the SQLite file up off the machine nightly and test a restore SQLite's .backup command makes a consistent copy while the app runs. Copy it to object storage or a second machine; then, once, restore it into a fresh checkout and confirm the app reads it. ```sh sqlite3 data/app.db ".backup '/tmp/app-$(date +%F).db'" rclone copy /tmp/app-$(date +%F).db remote:backups/ ``` 4. Lock the box down Firewall allowing only 22, 80 and 443; unattended security updates on; the app running as an unprivileged user under systemd with Restart=on-failure. ### Done when - [ ] Stopping the service triggers an uptime alert within a few minutes - [ ] A restore from last night's backup contains yesterday's data - [ ] A port scan from another machine shows only 22, 80 and 443 ===== OPERATIONS.md ===== # Operations · Webhook.site ## Backup SQLite .backup nightly; bins are mostly disposable. ## Restore Copy back. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on /healthz. ## Incident checklist A bin receiving secrets by mistake: delete it; rotate the secret. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Body cap and expiry verified - [ ] One restore drill performed ## Launch constraint Do not market omitted Webhook.site capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite file. DATABASE_PATH=./data/hooks.db # Required. Public base URL. SITE_URL=https://hooks.yourdomain.com # Optional. Default expiry for a bin. BIN_TTL_DAYS=7 # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
# Webhook.site · indie build A webhook inspector you host: mint a URL, catch anything sent to it with headers and body intact, watch requests arrive live, replay one to your localhost, and let bins expire. Nine dollars a month buys permanence and scripting; this buys understanding. Estimated effort: **one sitting**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | store and display | | Live updates | A 2-second vanilla-JS poll | no websocket library for a dev tool | | Hosting | A VPS behind Caddy | the catcher must be public | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Everything in this build runs on it: the server, the scripts, the tests. - Get it: Download the LTS installer from nodejs.org, or install with your package manager (brew install node, or nvm install 22). Restart the terminal afterwards. - Verify: node --version prints v22 or higher - [ ] **A terminal and a code editor** · free - Why: Every step below is a command you type or a file you edit. - Get it: VS Code (code.visualstudio.com), Cursor or Zed. Open a folder for the project and use the editor's built-in terminal. - Verify: You can open a folder and run a command in its terminal - [ ] **Git** · free - Why: History for your code, and the way most hosts deploy. - Get it: Install from git-scm.com or with your package manager, then run git init in the project folder once it exists. - Verify: git --version prints a version - [ ] **A small always-on server (VPS)** (optional) · about $5 a month - Why: This needs one process running all the time with a public address. The URL must be reachable from the services that call it. - Get it: Hetzner Cloud (from about 4 EUR), DigitalOcean or Fly.io. Ubuntu 24.04, the smallest size. You need SSH access and a public IP. Only needed for the deploy phase; develop locally first. - [ ] **A domain or subdomain** (optional) · roughly $10 a year, or free on an existing domain - Why: A public address you own, so links you share never break when a provider changes. - Get it: Register at Cloudflare Registrar, Porkbun or Namecheap, or use a subdomain of one you already own. You add one DNS record in the deploy phase. - [ ] **Caddy on the server** (optional) · free - Why: Automatic HTTPS in front of the Node process. Without TLS the browser features this relies on (and your visitors' trust) do not work. - Get it: On the VPS: follow the install steps at caddyserver.com/docs/install for Ubuntu. One Caddyfile with your domain and a reverse_proxy line is the whole config. - Verify: caddy version prints a version on the server - [ ] **Something that sends webhooks to test with** · free - Why: Phase 1 and 3 need real deliveries. - Get it: Stripe test mode, a GitHub repository webhook, or curl. ## Quick start ```sh mkdir hooks && cd hooks && git init && npm init -y && npm pkg set type=module mkdir -p data && cp .env.example .env ``` Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Custom actions, email hooks, DNS hooks, teams. - custom actions and workflow scripting - email and DNS hooks - team accounts - someone else hosting the public endpoint If one of those is essential to you, that is the reason to keep paying for Webhook.site, and the README should say so rather than pretend.
# Build brief · Webhook.site The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a webhook inspector like Webhook.site. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22 with node:http and node:sqlite. No framework. One process behind Caddy on a public host. - Server-rendered HTML with a small vanilla-JS poller for live updates. No websockets library. ### Data model (create this before Phase 1) - bins: id (uuid), created_at, expires_at, note - requests: id, bin_id, received_at, method, path, query, headers (JSON), body (text or base64 when binary), content_type, size, ip_hash ### Phase 1 · Catch anything Build: any method on /b/:uuid and any subpath stores a row and answers 200 with a tiny JSON body. Cap the body at 1 MB, store binary as base64 with a flag, and never fail because the bin is busy (WAL mode). Done when: GET, POST with JSON, POST with form data and a PUT with a binary body all store correctly with headers intact, and a 2 MB body is refused with 413. Do not build yet: UI, replay, retention. ### Phase 2 · Inspector Build: /b/:uuid/inspect lists requests newest first with method, path, size and time, and a detail view with headers, pretty-printed JSON, raw body and a copy-as-curl button. A vanilla-JS poll every 2 seconds appends new rows without a reload. Done when: a request appears in the inspector within 2 seconds, JSON pretty-prints, and copy-as-curl reproduces the request. ### Phase 3 · Replay Build: a replay button that re-sends the stored request to a target URL you type (typically your localhost via a tunnel), preserving method, headers and body, and shows the response. Done when: a captured Stripe test event replays to a local server and the local server's response is shown. ### Phase 4 · Bins and retention Build: a home page that mints a bin, an expiry per bin (default 7 days, extendable behind basic auth), a nightly prune, and a per-IP rate limit on minting. Done when: an expired bin returns 410, pruning deletes its requests, and minting is rate limited. ### Phase 5 · Deploy Build: a /healthz endpoint, a systemd unit, the Caddy config, the README. Done when: state survives a restart and a stranger can mint a bin from the README's URL. ### Out of scope (and why) - Custom actions, email hooks, DNS hooks, teams. That is the subscription. ### README must contain - The body cap and the retention default. - A note that anyone with the URL can read the bin · do not send secrets to a test catcher.
# Agent instructions · Webhook.site indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Node 22, node:http and node:sqlite, A 2-second vanilla-JS poll, A VPS behind Caddy. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs".
# Build plan · Webhook.site A webhook inspector you host: mint a URL, catch anything sent to it with headers and body intact, watch requests arrive live, replay one to your localhost, and let bins expire. Nine dollars a month buys permanence and scripting; this buys understanding. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Catch anything Any method, any subpath, stored intact, body capped. ### Steps 1. Create the project and tables bins (id, created_at, expires_at, note), requests (id, bin_id, received_at, method, path, query, headers JSON, body, content_type, size, ip_hash). ```sh mkdir hooks && cd hooks && git init && npm init -y && npm pkg set type=module mkdir -p data && cp .env.example .env ``` 2. Store any method on /b/:uuid and subpaths; body up to 1 MB, binary as base64; answer 200 with a tiny JSON ### Done when - [ ] GET, JSON POST, form POST and a binary PUT all store with headers intact - [ ] A 2 MB body is refused with 413 ## Phase 2 · Inspector Newest first, detail view, copy as curl, live poll. ### Steps 1. /b/:uuid/inspect listing requests and a detail view with pretty JSON and raw body 2. Copy-as-curl and a 2-second poll appending new rows ### Done when - [ ] A request appears within 2 seconds - [ ] JSON pretty-prints - [ ] Copy-as-curl reproduces the request ## Phase 3 · Replay Re-send a stored request to a URL you type. ### Steps 1. A replay form posting the stored method, headers and body to a target 2. Show the target's response ### Done when - [ ] A captured Stripe test event replays to a local server ## Phase 4 · Bins and retention Mint, expire, prune, rate limit. ### Steps 1. A home page that mints a bin; expiry per bin, extendable behind basic auth 2. Nightly prune and a per-IP mint limit ### Done when - [ ] An expired bin returns 410 - [ ] Pruning deletes its requests - [ ] Minting is rate limited ## Phase 5 · Deploy HTTPS, service, README. ### Steps 1. /healthz, systemd, Caddy 2. README: the body cap, retention, and that anyone with the URL can read the bin Files: `README.md` ### Done when - [ ] State survives a restart - [ ] A stranger can mint a bin from the README's URL ## Not in this build - Custom actions, email hooks, DNS hooks, teams. ## After v1, if you want it - A forwarding rule per bin to a real endpoint - Request search
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite file. DATABASE_PATH=./data/hooks.db # Required. Public base URL. SITE_URL=https://hooks.yourdomain.com # Optional. Default expiry for a bin. BIN_TTL_DAYS=7 # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
# Webhook.site · product brief ## Problem Catch a request, store it, display it. This is the smallest useful server there is, and the paid tier mostly buys permanence and the workflow actions. One process on any host covers the inspector in a sitting. ## Product outcome A team inspector with permanent bins and replay, on your infrastructure. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a small always-on host with a public URL ## Explicit non-goals for v1 - Custom actions, email hooks, DNS hooks, teams. - custom actions and workflow scripting - email and DNS hooks - team accounts - someone else hosting the public endpoint ## Success criteria - Body cap and expiry verified - One restore drill performed
# Build brief · Webhook.site The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a webhook inspector like Webhook.site. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Node 22 with node:http and node:sqlite. No framework. One process behind Caddy on a public host. - Server-rendered HTML with a small vanilla-JS poller for live updates. No websockets library. ### Data model (create this before Phase 1) - bins: id (uuid), created_at, expires_at, note - requests: id, bin_id, received_at, method, path, query, headers (JSON), body (text or base64 when binary), content_type, size, ip_hash ### Phase 1 · Catch anything Build: any method on /b/:uuid and any subpath stores a row and answers 200 with a tiny JSON body. Cap the body at 1 MB, store binary as base64 with a flag, and never fail because the bin is busy (WAL mode). Done when: GET, POST with JSON, POST with form data and a PUT with a binary body all store correctly with headers intact, and a 2 MB body is refused with 413. Do not build yet: UI, replay, retention. ### Phase 2 · Inspector Build: /b/:uuid/inspect lists requests newest first with method, path, size and time, and a detail view with headers, pretty-printed JSON, raw body and a copy-as-curl button. A vanilla-JS poll every 2 seconds appends new rows without a reload. Done when: a request appears in the inspector within 2 seconds, JSON pretty-prints, and copy-as-curl reproduces the request. ### Phase 3 · Replay Build: a replay button that re-sends the stored request to a target URL you type (typically your localhost via a tunnel), preserving method, headers and body, and shows the response. Done when: a captured Stripe test event replays to a local server and the local server's response is shown. ### Phase 4 · Bins and retention Build: a home page that mints a bin, an expiry per bin (default 7 days, extendable behind basic auth), a nightly prune, and a per-IP rate limit on minting. Done when: an expired bin returns 410, pruning deletes its requests, and minting is rate limited. ### Phase 5 · Deploy Build: a /healthz endpoint, a systemd unit, the Caddy config, the README. Done when: state survives a restart and a stranger can mint a bin from the README's URL. ### Out of scope (and why) - Custom actions, email hooks, DNS hooks, teams. That is the subscription. ### README must contain - The body cap and the retention default. - A note that anyone with the URL can read the bin · do not send secrets to a test catcher.
# Architecture · Webhook.site ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Node 22, node:http and node:sqlite | store and display | | Live updates | A 2-second vanilla-JS poll | no websocket library for a dev tool | | Hosting | A VPS behind Caddy | the catcher must be public | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Catcher | /b routes | Any listener writing the rows | | Inspector | views and poll | Websockets later | | Replay | re-sending | Add scheduled replays | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `PORT` · required · Any free port. - `DATABASE_PATH` · required · SQLite file. - `SITE_URL` · required · Public base URL. - `BIN_TTL_DAYS` · optional · Default expiry for a bin. - `ADMIN_USER` · required · Any username for the basic-auth admin pages. - `ADMIN_PASS` · required, secret · Generate one: openssl rand -base64 24. Never reuse a real password. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Webhook.site product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Node 22, node:http and node:sqlite, A 2-second vanilla-JS poll, A VPS behind Caddy. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone.
# Delivery milestones · Webhook.site Estimated effort: **one sitting** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Catch anything Any method, any subpath, stored intact, body capped. ### Steps 1. Create the project and tables bins (id, created_at, expires_at, note), requests (id, bin_id, received_at, method, path, query, headers JSON, body, content_type, size, ip_hash). ```sh mkdir hooks && cd hooks && git init && npm init -y && npm pkg set type=module mkdir -p data && cp .env.example .env ``` 2. Store any method on /b/:uuid and subpaths; body up to 1 MB, binary as base64; answer 200 with a tiny JSON ### Done when - [ ] GET, JSON POST, form POST and a binary PUT all store with headers intact - [ ] A 2 MB body is refused with 413 ## M2 · Inspector Newest first, detail view, copy as curl, live poll. ### Steps 1. /b/:uuid/inspect listing requests and a detail view with pretty JSON and raw body 2. Copy-as-curl and a 2-second poll appending new rows ### Done when - [ ] A request appears within 2 seconds - [ ] JSON pretty-prints - [ ] Copy-as-curl reproduces the request ## M3 · Replay Re-send a stored request to a URL you type. ### Steps 1. A replay form posting the stored method, headers and body to a target 2. Show the target's response ### Done when - [ ] A captured Stripe test event replays to a local server ## M4 · Bins and retention Mint, expire, prune, rate limit. ### Steps 1. A home page that mints a bin; expiry per bin, extendable behind basic auth 2. Nightly prune and a per-IP mint limit ### Done when - [ ] An expired bin returns 410 - [ ] Pruning deletes its requests - [ ] Minting is rate limited ## M5 · Deploy HTTPS, service, README. ### Steps 1. /healthz, systemd, Caddy 2. README: the body cap, retention, and that anyone with the URL can read the bin Files: `README.md` ### Done when - [ ] State survives a restart - [ ] A stranger can mint a bin from the README's URL ## M6 · Operate it like a product (production only) Only for the product-builder path: know when the catcher is down, never lose the database, and keep the server patched. ### Steps 1. Add a /healthz endpoint and an external uptime check against it Answer 200 with the build id and a quick database read. Point a free uptime monitor (or your own, from the Healthchecks entry on this site) at it so an outage is noticed before a user notices. 2. Write structured request logs and rotate them One JSON line per request: method, path, status, duration, no raw IPs. Rotate weekly with logrotate, keep eight. 3. Back the SQLite file up off the machine nightly and test a restore SQLite's .backup command makes a consistent copy while the app runs. Copy it to object storage or a second machine; then, once, restore it into a fresh checkout and confirm the app reads it. ```sh sqlite3 data/app.db ".backup '/tmp/app-$(date +%F).db'" rclone copy /tmp/app-$(date +%F).db remote:backups/ ``` 4. Lock the box down Firewall allowing only 22, 80 and 443; unattended security updates on; the app running as an unprivileged user under systemd with Restart=on-failure. ### Done when - [ ] Stopping the service triggers an uptime alert within a few minutes - [ ] A restore from last night's backup contains yesterday's data - [ ] A port scan from another machine shows only 22, 80 and 443
# Operations · Webhook.site ## Backup SQLite .backup nightly; bins are mostly disposable. ## Restore Copy back. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Uptime on /healthz. ## Incident checklist A bin receiving secrets by mistake: delete it; rotate the secret. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Body cap and expiry verified - [ ] One restore drill performed ## Launch constraint Do not market omitted Webhook.site capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Any free port. PORT=3000 # Required. SQLite file. DATABASE_PATH=./data/hooks.db # Required. Public base URL. SITE_URL=https://hooks.yourdomain.com # Optional. Default expiry for a bin. BIN_TTL_DAYS=7 # Required. Any username for the basic-auth admin pages. ADMIN_USER=admin # Required · secret. Generate one: openssl rand -base64 24. Never reuse a real password. ADMIN_PASS=change-me-to-a-long-random-string
$ choose a build depth, inspect the files, then open the complete pack in your agent
Nine dollars to never think about hosting a public catcher, plus the scripting layer that turns a webhook into a Slack message without code.
xcustom actions and workflow scripting
xemail and DNS hooks
xteam accounts
xsomeone else hosting the public endpoint
Webhook.site pricing
basic$9/mo · monthly flat · $108/yr
free tierFree URLs work without an account but expire after 7 days and cap stored requests.
verified 2026-09-04 · source ↗
Is Webhook.site free?
Free URLs work without an account but expire after 7 days and cap stored requests. Paid is Basic at $9/mo (checked 2026-09-04).
Vibecode Webhook.site
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Webhook.site replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Webhook.site cost?
Webhook.site costs about $9/month (Basic, checked 2026-09-04), which is $108 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Webhook.site?
Honestly: custom actions and workflow scripting; email and DNS hooks; team accounts; someone else hosting the public endpoint. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Webhook.site?
Yes: smee.io (webhook payload delivery service, open source). Using prior art is also vibecoding; the prompt is for when you want it exactly your way.