Docs

Quick start

# create a bin (plain text, one command)
curl https://hookden.pages.dev/new

# or JSON
curl -s -X POST https://hookden.pages.dev/api/bins

# send anything to the capture URL
curl -X POST https://hookden.pages.dev/h/BIN_ID/any/path?x=1 -d 'payload'

# list captured requests (JSON)
curl -s https://hookden.pages.dev/api/bins/BIN_ID/requests

Terminal CLI

A tiny shell client (curl + sed, ~50 lines, no account needed) lives at https://hookden.pages.dev/cli:

# install
curl -s https://hookden.pages.dev/cli -o hookden && chmod +x hookden

# create a bin
./hookden new

# live-tail captures into your terminal (Ctrl-C to stop)
./hookden tail BIN_ID        # also accepts the full /h/ or /b/ URL

# re-deliver captures to a server on YOUR machine (Ctrl-C to stop)
./hookden relay BIN_ID http://localhost:3000          # new captures only
./hookden relay BIN_ID http://localhost:3000 --all    # history too

Each capture is printed with method, path, signature badge (if signature verification is on), headers, and a pretty-printed body. Under the hood it polls GET https://hookden.pages.dev/api/bins/BIN_ID/tail?after=N, a plain-text endpoint you can also script against directly — the x-hookden-last response header carries the cursor for the next poll.

Relay: webhooks to localhost, no tunnel

./hookden relay turns a bin into a bridge to your dev machine: point the webhook provider at https://hookden.pages.dev/h/BIN_ID, run the relay, and every capture is re-delivered to your local server with the original method, sub-path, query string, headers (host/proxy headers stripped) and byte-identical body — so HMAC signatures like GitHub's X-Hub-Signature-256 still verify against the relayed request. No tunnel daemon, no firewall holes, no ngrok account: it's outbound polling with plain curl, so it works behind NAT, corporate proxies, and CI runners. Captures also stay in the dashboard, so you get inspection + local delivery at the same time. See the step-by-step guide.

Capture endpoint

Your bin's capture URL is https://hookden.pages.dev/h/BIN_ID. Any method, any sub-path, any query string is captured: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS. Bodies are stored up to 100 KB (larger bodies are truncated). JSON bodies are pretty-printed in the dashboard, and application/x-www-form-urlencoded bodies (Slack, Twilio, GitHub's form mode…) are decoded into readable key = value lines. Binary bodies (protobuf, gzip, file uploads…) are kept byte-exact — the dashboard shows a hex dump with a "download raw bytes" link, and replay, CLI relay, and exports all resend the original bytes, so HMAC signatures still verify downstream. CORS is open on capture endpoints, so browser apps can hit them too.

Custom URL slugs

Signed-in users can give any bin a memorable alias in its settings: set a slug like my-stripe-dev and the bin answers on https://hookden.pages.dev/h/my-stripe-dev (the dashboard moves to /b/my-stripe-dev too). The original random URL keeps working. Slugs are 3–40 chars of lowercase letters, digits, and hyphens, unique across Hookden — first come, first served. Handy when you re-configure the same provider often, or want a URL you can remember and type.

Custom responses

By default a bin answers 200 with {"ok":true}. In the bin dashboard you can set any status code (e.g. 500 to test your retry logic), content type, body, and a response delay of up to 10 seconds — handy for testing client timeouts and retry/backoff behaviour.

Response templates (echo the request back)

The response body supports {{variable}} placeholders that are filled in from each incoming request. This is how you pass provider URL-verification handshakes that require echoing a challenge — e.g. set the body to:

{"challenge":"{{body.challenge}}"}

and your bin passes Slack's (and Zoom's, Dropbox's…) endpoint verification while still capturing every event. Available variables:

VariableValue
{{body}}the raw request body (full echo)
{{body.a.b}}field from a JSON body (dot-path into nested objects) or a form-urlencoded body
{{query.name}}a query-string parameter ({{query}} alone = the whole query string)
{{header.x-foo}}a request header (case-insensitive)
{{method}} / {{path}}HTTP method / sub-path under the bin URL
{{ip}} / {{timestamp}} / {{bin}}caller IP / current Unix time (seconds) / bin ID

Unknown or missing variables render as an empty string. Values are substituted literally (no quoting added) — challenge tokens are alphanumeric so this is safe for the handshake use case. A body without {{ is returned exactly as-is, byte for byte.

Signature verification

Debugging "invalid signature" errors is the worst part of webhook work. Set a signing secret in the bin's settings and Hookden verifies every incoming request's HMAC, showing a ✓ or ✗ badge per request. Supported schemes:

SchemeHeader checkedHow it's verified
GitHubX-Hub-Signature-256sha256= + hex HMAC-SHA256 of the raw body
StripeStripe-Signaturev1 = hex HMAC-SHA256 of {t}.{body} (timestamp tolerance not enforced — this is a debugging tool)
Generic HMACany header you namehex or base64 HMAC-SHA256 of the raw body, optional sha256= prefix

Verification runs on the full raw body, even when the stored copy is truncated. Many providers (Shopify, Slack-adjacent tools, most "webhook secret" implementations) use one of these three shapes.

Replay

Captured a webhook you wish had hit your real endpoint? Every request has a Replay ▸ button (bin owner only): give it a target URL and Hookden re-sends the exact method, headers, and body to it, then shows you the status code and latency your endpoint returned. The replayed request carries an X-Hookden-Replay: 1 header so you can tell it apart on your side.

Targets must be public http(s) hosts — for localhost, use a tunnel (e.g. cloudflared tunnel --url localhost:3000). Replays time out after 10s. Or use "Copy as" (curl / fetch / Python / HTTPie) and run it yourself.

Auto-forward (live proxy)

Don't want to take your webhook offline while you debug? Set a Forward to URL in the bin's settings and every captured request is also mirrored to your real endpoint — same method, headers, and body, with the sub-path and query string appended to your URL. Point your provider at Hookden, keep production flowing, and watch everything in the live dashboard at the same time.

Forwarded requests carry X-Hookden-Forwarded: 1 and X-Hookden-Bin headers. Forwarding is fire-and-forget (your webhook sender always gets the bin's configured response) and times out after 10s. Same public-host rule as Replay.

Copy any capture as code

Every capture has a Copy as menu: get the exact request as a ready-to-run curl command, a JavaScript fetch() call, a Python requests snippet, or an HTTPie command — headers and raw body included (hop-by-hop and CF headers stripped). Paste a real webhook straight into your test suite.

Diff two requests

Why did yesterday's delivery work and today's fail? Hit Diff… on any two captures in the dashboard and get a colorized, line-by-line diff of the request line, headers, and (pretty-printed) body — long unchanged stretches are collapsed so the change jumps out. Works for everyone viewing the bin, no account needed.

Limits

AnonymousFree account
Requests kept per bin1001,000
Retention24 hours30 days
Binsunlimitedunlimited
Ingest rate per bin120/minute300/minute

Over the ingest rate, the bin answers 429 with a Retry-After header until the minute window clears. New-bin creation is capped at 30/hour per IP.

When a bin is full, oldest requests are dropped first. Need more? Tell me — limits are soft while Hookden grows.

Heads-up: Cloudflare's edge (not Hookden) answers 403 error code: 1010 to Python's default Python-urllib user-agent. If you test with urllib, set any User-Agent header — requests, curl, Go, Java, Ruby and every real webhook sender work as-is.

API

GET  https://hookden.pages.dev/new                        # create bin (plain-text answer)
POST https://hookden.pages.dev/api/bins                  # create bin → {bin,url,inspect}
GET  https://hookden.pages.dev/api/bins/:id/requests      # list requests (?after=ID for polling)
DELETE https://hookden.pages.dev/api/bins/:id/requests    # clear captured requests
GET  https://hookden.pages.dev/api/bins/:id/export        # download all captures as JSON
GET  https://hookden.pages.dev/api/bins/:id/har           # download as HAR 1.2 (Chrome DevTools, Insomnia…)
DELETE https://hookden.pages.dev/api/bins/:id             # owner; delete the bin entirely
POST https://hookden.pages.dev/api/bins/:id/requests/:rid/replay   # owner; body {"target":"https://…"}
GET  https://hookden.pages.dev/api/bins/:id/requests/:rid/share    # mint read-only share link → {url}

Share a capture

Every capture has a Share link button: it copies a read-only permalink (https://hookden.pages.dev/r/…) to just that one request — headers, body, signature badge. Paste it in Slack, a GitHub issue, or a bug report. The link shows only that capture: it never exposes your bin, its URL, or other requests, and it can't be guessed (HMAC-signed). It works as long as the capture is retained (24h anonymous / 30 days with an account).