Documentation

Capture, inspect, forward and monitor webhooks. Put HookWatcher between your webhook provider and your application to see exactly what was sent, what your server answered, and resend anything that failed.

Getting Started

What HookWatcher does

HookWatcher gives you a URL that captures every HTTP request sent to it, exactly as it arrived. You can inspect headers and payloads, compare requests, replay them and generate documentation. With forwarding, HookWatcher also delivers each request to your own server and records the response, so it sits between your webhook provider and your application:

Webhook provider  →  HookWatcher (captures, inspects, retries, alerts)  →  Your application

You configure the provider once. You can then point forwarding at development, staging or production, see exactly what was sent and received, resend any request, and get an email when deliveries keep failing.

For anything that needs your local app's live answer, tunnels give a service on your machine a public HTTPS URL through hwcli.

Getting Started

Creating an endpoint

Open HookWatcher and you get an endpoint immediately; no account is needed to try it. Signed-in users create more from the sidebar (+) or with ⌘K → Create endpoint. Each endpoint URL looks like:

https://hookwatcher.com/<endpoint-id>

Anonymous endpoints keep history for 7 days. Sign up to keep endpoints, set retention and use forwarding.

Getting Started

Sending your first webhook

Paste the URL into your provider's webhook settings, press Send test in HookWatcher, or use cURL:

curl -X POST 'https://hookwatcher.com/<endpoint-id>' \
  -H 'Content-Type: application/json' \
  -d '{"event":"order.created","data":{"order_id":"ORD-1001","amount":1499}}'

The request appears in Requests instantly. Test requests carry an X-HookWatcher-Test header and a "test" badge.

Getting Started

Inspecting a request

Select a request (or press j/k). The Overview shows its lifecycle (Received → Captured → Forwarded), forwarding result, key facts and actions. GraphQL requests also get a GraphQL tab (see GraphQL requests). Other tabs show the Body (a JSON tree — click a key to copy its path such as data.items[0].price), Headers, Query, Cookies, Raw bytes, the Response HookWatcher sent, Replay, Schema, Signature, Forwarding history and Compare.

Hooks

Endpoints (hooks), paths and query strings

An endpoint (also called a hook) is one capture URL with its own settings, history and forwarding. It accepts GET, POST, PUT, PATCH, DELETE, HEAD and OPTIONS, and any sub-path: /<endpoint-id>/orders/42?source=mobile is captured by the same endpoint, and the full path and query string are recorded with the request.

All endpoints lists every endpoint with its URL, last request, recent volume and forwarding state. Open goes to its requests; the ⋯ menu copies the URL, sends a test request, renames, configures or disables forwarding, clears captured requests or deletes the endpoint. The endpoint's Overview page has Overview, Forwarding, Security, Documentation and Settings tabs.

Request bodies up to 128 KiB are captured. HookWatcher replies immediately with the response configured in settings (default 200), including hw-request-id and hw-webhook-id headers.

Captured Requests

Captured requests

HookWatcher stores the exact bytes of every request with a SHA-256 fingerprint; formatting, masking and exports are derived views and never change what was captured. Header names are case-insensitive: Content-Type and content-type are the same header, and HookWatcher shows them as they arrived.

  • Filter history by method, IP, provider, response status, signature, forwarding status, content type, protocol (GraphQL), GraphQL operation and date.
  • Search event names and payload text, and sort newest or oldest first.
  • Copy any request as cURL, Node.js or Python (sensitive headers masked by default), or export as JSON.
  • Label, delete or bulk-export requests; history expires according to the endpoint's retention setting.

Forwarding

What forwarding does

Forwarding sends every request a hook receives to your own destinations after HookWatcher has captured it. One hook can forward to several forwarding endpoints at once — for example your production API, a staging API, an event archive and your laptop through hwcli. The sender gets HookWatcher's response straight away; delivery happens in the background, so a slow or failing destination never delays your provider or the other destinations.

Configure it

  • Open the hook's Overview or Forwarding tab (or Endpoint settings) and choose Add forwarding endpoint.
  • Pick the type — Remote URL or CLI endpoint — give it a name, the destination, the path behavior, and whether query parameters and credentials are forwarded.
  • New requests are forwarded to every enabled endpoint. Use Test on an endpoint to send it a marked test event first.
  • Disable pauses one endpoint without deleting it; requests are still captured. Remove deletes its configuration; past delivery records stay.

URL rules

A remote URL must be an https:// or http:// URL with a public hostname or address. Paths and query strings are kept, for example https://api.example.com/api/v1/orders/webhook?env=dev. Credentials in the URL, fragments and other schemes (file:, ftp:, data:, javascript:…) are rejected. See Security for network restrictions.

Forwarding

Multiple forwarding endpoints

Endpoint types

TypeDelivered byDestination
Remote URLHookWatcher's serversA public HTTP or HTTPS server (your API, staging, an archive).
CLI endpointhwcli on a computer you're signed in onA server on that machine: localhost, 127.0.0.1 or [::1].

Plan limits

Your plan sets how many forwarding endpoints a hook can have, remote URLs and CLI endpoints combined:

PlanForwarding endpoints per hook
Free2
Developer3
Pro5
Team10
EnterpriseCustom

Disabled and offline endpoints still count — only removing an endpoint frees its place. There is no daily limit on forwarding: every request is delivered to every configured endpoint, however many you receive. Only technical protections apply (payload size, timeouts, abuse prevention and queue capacity).

Independent deliveries

Each endpoint gets its own delivery, retries, response and history. One endpoint failing never delays, retries or blocks the others. A request shows the result per endpoint — for example 2 of 3 delivered — and filters can find fully delivered, partially delivered and failed requests, or requests for one destination. Order doesn't matter: endpoints don't depend on each other.

CLI endpoints

Run hwcli listen <hook> --to http://localhost:3000/webhooks, or add a CLI endpoint here and choose one of your signed-in hwcli devices. HookWatcher queues the delivery and hwcli on that machine picks it up and delivers it locally. When the device is offline the endpoint stays configured and shows Offline; its deliveries fail as "CLI device offline" after the usual retries and are summarised in one line of the failure email, not one email per request.

Security

HookWatcher's servers never connect to localhost or private networks — remote URLs to those addresses are refused, including through DNS. CLI endpoints can target localhost only because the delivery is made by hwcli on your own machine, after it signed in to your account.

Signatures

Every endpoint receives the exact captured body, so provider signatures stay valid. Settings that change what is sent (for example leaving out credential headers) don't alter the body, but your server needs the signature header to verify it.

Forwarding

Wildcard paths

Choose Keep the incoming path and end the destination with /*. The * is replaced by whatever follows the endpoint ID in the incoming URL. Only a single trailing * is supported; it changes the path only, never the scheme, host or port.

Incoming requestDestinationForwarded to
/abc123/orders/42https://example.com/webhooks/*https://example.com/webhooks/orders/42
/abc123/customer/profile/updatehttps://dev.example.com/hooks/*https://dev.example.com/hooks/customer/profile/update
/abc123https://example.com/webhooks/*https://example.com/webhooks/
/abc123/anythinghttps://example.com/hook (exact mode)https://example.com/hook

Repeated slashes are collapsed. Segments such as .., encoded slashes (%2F) or backslashes are refused and the delivery is marked failed, so an incoming path can never climb out of the configured base path.

Forwarding

Query, header and body behavior

Query parameters

With Forward query parameters on (the default for new destinations), incoming parameters are appended after the destination's own. When both define the same name, the destination's configured value wins and the incoming value is dropped. Repeated incoming names keep their order.

Destination: https://api.example.com/payment?env=dev
Incoming:    ?source=mobile&env=prod
Forwarded:   https://api.example.com/payment?env=dev&source=mobile

Body

The original method and the exact body bytes are sent unchanged, with the original Content-Type. HookWatcher never re-serializes JSON, so provider signatures computed over the raw body still verify on your server.

Headers

HeaderForwarded?
Content-Type, Content-Encoding, Accept and other content headersYes
Provider signatures and events (Stripe-Signature, X-Hub-Signature-256, X-GitHub-Event, X-Shopify-*, webhook-id…)Yes, unchanged
Custom X-* headers and User-AgentYes
Authorization, Cookie, X-Api-Key and other credentialsOnly when Include credentials is on
Host, Content-Length, Connection, Keep-Alive, Transfer-Encoding, TE, Trailer, Upgrade, Proxy-*No — the HTTP client sets its own
X-Forwarded-*, Forwarded, X-Real-IP, CF-*No — they would misstate the source
Incoming X-HookWatcher-*No — replaced by HookWatcher's own

Headers HookWatcher adds

X-HookWatcher-Forwarded: true
X-HookWatcher-Request-Id: <captured request ID>
X-HookWatcher-Hook-Id: <endpoint ID>
X-HookWatcher-Received-At: <ISO 8601 capture time>
X-HookWatcher-Forward-Attempt: <attempt number>

No account or user information is added. Test deliveries also carry X-HookWatcher-Test: true.

Forwarding

How responses are evaluated

A delivery succeeds when your server returns any 2xx status. Everything else is a failure: 3xx redirects (HookWatcher does not follow redirects; update the destination to the final URL), 4xx and 5xx responses, timeouts, DNS failures, refused connections and TLS errors. For each attempt HookWatcher stores the status, response time, response headers (credentials masked) and the first 8 KiB of the response body, marked truncated when longer. Response bodies are shown as text and never rendered as HTML.

Timeouts depend on the endpoint owner's plan (5–30 seconds); connecting and DNS lookup must each finish within 3 seconds.

Automatic Retry

Automatic retry

Automatic delivery makes 3 attempts in total:

AttemptWhen
1Immediately after the request is captured
2About 10 seconds after attempt 1 fails
3About 45 seconds after attempt 2 fails

If any attempt succeeds, no further attempts are made. After attempt 3 fails, the request is marked Failed and becomes eligible for the hourly failure email. Retries are per endpoint: a failing endpoint retries on its own while the others are done. Destinations blocked by the security policy, a removed endpoint or an invalid incoming path fail immediately without retries. Retries are scheduled in the background and survive restarts.

Delivery is at-least-once: if your server processed a request but the response was lost (for example a timeout), a retry can deliver it again. Use X-HookWatcher-Request-Id to deduplicate.

Manual Retry

Send again

Send again appears next to each forwarding endpoint of a request (and Forward now for endpoints added after the request arrived). It makes exactly one new delivery attempt to that endpoint only using its current destination, records it as a Manual attempt in that endpoint's history, and shows the result immediately. Other endpoints are not sent the request again, and earlier attempts are never overwritten.

  • Manual attempts are not retried automatically.
  • Manual failures never trigger failure emails.
  • Sending again replaces any automatic retry that was still waiting, so the destination does not receive the request twice.
  • Each request allows up to 10 attempts per endpoint in total. Forwarding volume itself is not limited.

Failure Alerts

Failure alerts

When automatic delivery fails after all 3 attempts, HookWatcher emails the endpoint owner. To avoid spam you receive at most one alert email per hour: failures that happen after an email are collected and included in the next one.

Each email summarizes the failures since the previous email, grouped by endpoint: endpoint name, destination host and path, number of failures, and for each request the event type or method, the failure reason or status code, the number of attempts and the time, with a link to view them. Large digests show the first 20 and a count of the rest. Emails never include payloads, headers, query strings or secrets.

A failure that is later delivered by Send again before the next email is dropped from it. Turn alerts on or off under My Account → Notifications (on by default).

Forwarding Status

Forwarding status

StatusMeaning
Not configuredThe endpoint has no forwarding destination.
Not forwardedForwarding exists, but this request arrived before it was enabled. Use Forward now.
DisabledForwarding is paused for this endpoint; the request was captured but not delivered.
QueuedWaiting to be sent.
ForwardingBeing delivered now.
RetryingAn attempt failed and another automatic attempt (up to 3) is scheduled or running.
SendingA manual Send again is in progress.
DeliveredThe latest attempt got a 2xx response.
FailedThe latest attempt failed and no automatic attempt remains.

Security

Security

Forwarding, replay and destination tests share one outbound policy:

  • Only http and https. URLs with credentials or fragments are rejected.
  • Localhost (localhost, *.localhost, *.local, 127.0.0.0/8, ::1, 0.0.0.0) cannot be used.
  • Private, shared, link-local, multicast and reserved ranges are blocked, including 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10, 169.254.0.0/16 (including the cloud metadata address 169.254.169.254), fc00::/7, fe80::/10 and IPv4-mapped IPv6 forms.
  • Hostnames are resolved before every delivery and every returned address must be public; the connection is pinned to the checked address, which prevents DNS rebinding.
  • Redirects are never followed, so a public URL cannot redirect a delivery into a private network.
  • Response size, header size and time are bounded.

To deliver to your laptop, add a CLI endpoint (hwcli listen delivers from your machine) or forward to a HookWatcher tunnel URL. Sensitive request headers are masked in the interface, exports and AI requests unless you explicitly reveal or include them. Signing secrets are encrypted at rest and never shown after saving.

Security

Signatures and MFA

Add a Stripe or GitHub signing secret in Endpoint settings to verify signatures against the captured bytes; results appear on each request. Verification describes a request and never blocks it. Because forwarding keeps the exact body and signature headers, your own server can verify the same signature.

Enable two-factor authentication under My Account → Security with an authenticator app. MFA is reset when you reset your password.

Troubleshooting

Troubleshooting deliveries

What you seeWhat to check
400 Bad Request / 422Your handler rejected the payload. Compare the forwarded body in the request with what your parser expects; parse the raw body before JSON-decoding when verifying signatures.
401 / 403Your server requires authentication. Turn on Include credentials to forward the provider's Authorization header, or allow HookWatcher's requests (identified by X-HookWatcher-Forwarded).
404 Not FoundThe path is wrong. Check the destination URL and, in wildcard mode, the mapping shown in the forwarding dialog.
3xx redirectRedirects are not followed. Use the final URL (often https:// or a trailing slash).
429 Too Many RequestsYour server is rate limiting. Automatic retries back off; raise the limit for HookWatcher or Send again later.
500 / 502 / 503 / 504Your server or its proxy failed. Check its logs using X-HookWatcher-Request-Id, then Send again.
TimeoutRespond quickly (queue slow work) or ask for a longer plan timeout.
DNS errorThe hostname does not resolve. Check for typos and that the record is public.
TLS errorThe certificate is invalid, expired or for another name. Fix the certificate or use a hostname that matches it.
Blocked destinationThe URL or one of its DNS answers is private or local. Use a public address, a HookWatcher tunnel URL (hwcli tunnel http), or a CLI endpoint (hwcli listen).

Captured Requests

GraphQL requests

HookWatcher recognises GraphQL over HTTP and shows it as GraphQL — operation type, operation name, formatted document and variables — instead of escaped JSON. Detection never changes what was captured: the raw bytes are stored once, and the GraphQL view is derived when you open the request.

GraphQL request flowA client sends a GraphQL request with an operation, variables and an operation name to a hook. HookWatcher captures it unchanged, then you inspect, forward or replay it.ClientGraphQL requestOperationmutation CreateOrder(…)Variables{ "input": { … } }Operation nameCreateOrderHookexact bytesInspectForwardReplay

Supported request formats

FormatExample
JSON body (most common){"query": "…", "variables": {…}, "operationName": "CreateOrder"}
application/graphqlThe body is the GraphQL document; operationName and variables may be query parameters.
GET/graphql?query=…&variables=…&operationName=…
BatchA JSON array of request objects; each operation is listed separately.
Persisted queryextensions.persistedQuery.sha256Hash with or without the query text.

A JSON body counts as GraphQL only with reasonable confidence: its query must parse as a GraphQL document, or the object must carry GraphQL request fields (variables, operationName, extensions) and nothing else. {"query": "red shoes", "page": 2} stays an ordinary JSON request.

In the request inspector

The list shows the operation — for example GraphQL · mutation and CreateOrder — and the Overview adds Protocol and Operation. The GraphQL tab shows:

  • Operation type and name. An unnamed operation shows as Anonymous operation; a document with several operations and no operationName shows Multiple operations detected — HookWatcher doesn't guess which one ran. When operationName selects one, it is marked.
  • The formatted document, with syntax highlighting, and its fragments listed once.
  • Declared variables with the types written in the operation, and the variables in the JSON viewer (search, copy value, copy path). Variables with sensitive names (password, token, secret…) are masked until you choose to include sensitive values.
  • Extensions (persisted query hash, tracing, client data) as JSON. A persisted query without text shows its hash and says the text isn't known.
  • Copy operation, Copy variables, Copy operation name and Copy as cURL. The cURL command writes the GraphQL body literally (--data-raw), byte for byte.

If the document doesn't parse, the tab says Unable to parse GraphQL operation, shows the parser's message and the query exactly as sent.

Example

mutation CreateOrder($input: OrderInput!) {
  createOrder(input: $input) {
    id
    status
  }
}
{
  "input": {
    "amount": 1499,
    "currency": "USD"
  }
}

This request appears as POST · GraphQL · mutation · CreateOrder; its GraphQL tab lists $input: OrderInput! under declared variables and the amount and currency under variables.

Search and filters

Use the Protocol, Operation type and Operation name filters, or type protocol:graphql, graphql.type:mutation or graphql.operation:CreateOrder in the search box (combine them with other words). Requests captured before GraphQL support are recognised when you open them.

Forwarding

GraphQL requests are forwarded like any other request: every forwarding endpoint receives the same method, raw body, Content-Type, headers and query parameters — never a reserialized body, so custom signatures still verify. CLI endpoints (hwcli forward) deliver them to localhost the same way.

GraphQL errors versus delivery

GraphQL servers often answer HTTP 200 with an errors array. HookWatcher shows both: the delivery result (for example 200 OK) and, separately, GraphQL errors: 2 with each error's message, path and location. Retries follow the HTTP result only — a GraphQL error never triggers an automatic retry. The endpoint analytics count forwarding responses with GraphQL errors.

Replay

Edit & Replay on a GraphQL request has separate GraphQL query, Variables (JSON) and Operation name fields, plus Format GraphQL (shows the parse error if the query is invalid). Unedited fields replay the exact captured bytes; edits rebuild the request in its own format — the JSON body with its other fields kept, the application/graphql body, or GET parameters. Use original raw body undoes the edits. The replay response shows GraphQL data, errors and extensions.

Compare and operation changes

Comparing two GraphQL requests shows the operation type and name, a line diff of the formatted documents and a structural diff of the variables. When a named operation's document changes between captures, the GraphQL tab says the operation changed and can show the diff. This is a GraphQL operation change, not schema drift: HookWatcher has no GraphQL schema.

What HookWatcher does not do

  • It doesn't validate operations against a schema, execute them or run introspection against your destinations.
  • Subscriptions captured as HTTP requests are shown as subscriptions; GraphQL subscriptions over WebSocket are not proxied.
  • AI documentation uses the operation, declared variable types and redacted variables — it doesn't claim field meanings a schema would provide.

FAQ

GraphQL FAQ

QuestionAnswer
Does HookWatcher support GraphQL?Yes, GraphQL over HTTP: JSON bodies, application/graphql bodies, GET, batches and persisted queries.
Can I inspect GraphQL variables?Yes, in the GraphQL tab's JSON viewer. Variables with sensitive names are masked until you include sensitive values.
Can I replay GraphQL mutations?Yes. Replay sends the exact original bytes, or edit the query, variables and operation name first.
Are GraphQL errors detected?Yes, in forwarding and replay responses — shown separately from the HTTP status, without changing retries.
Can GraphQL requests be forwarded to multiple endpoints?Yes. Every forwarding endpoint receives the unchanged request.
Does the CLI support GraphQL requests?Yes. hwcli requests get shows the operation, formatted query and variables; --raw prints the exact body; hwcli listen delivers GraphQL requests to localhost unchanged.

FAQ

Forwarding FAQ

QuestionAnswer
Does HookWatcher alter the payload?No. The captured bytes are forwarded unchanged with their Content-Type.
Does it retry automatically? How many times?Yes — 3 attempts in total, about 10 and 45 seconds apart.
Can I send a request again manually?Yes, with Send again on the request. It makes one attempt and is recorded separately.
Does Send again trigger a failure email?No. Only final automatic failures are emailed, at most once per hour.
Can I forward to localhost or a private IP?Not as a remote URL. Add a CLI endpoint with hwcli listen, or forward to the URL of a HookWatcher tunnel (hwcli tunnel http).
Can I preserve request paths?Yes — choose Keep the incoming path and end the destination with /*.
Are the original headers forwarded?Content, signature, provider and custom headers are. Connection and proxy headers are not, and credentials only when enabled.
Will webhook signatures still verify?Yes, as long as your server verifies the raw body; HookWatcher does not change the bytes or signature headers.

Debugging

Replay, debug and test

Every captured event can be replayed exactly, modified, run against your local code and saved as a regression test. The original event never changes: replays, debug attempts and test runs are separate records linked to it.

  • Replay Locally sends the event to an hwcli session that is listening on your machine. Replay to URL sends it from HookWatcher to a public URL. Modify & Replay changes fields and headers first, with a side-by-side view.
  • Replay Lab sends duplicate, concurrent, delayed, burst, unsigned or malformed deliveries, or events in any order, marked X-HookWatcher-Synthetic: replay-lab.
  • Debug Sessions record each attempt while you fix the handler. The successful attempt can be saved as a test.
  • Regression tests store the exact request and the expected response. Run them from the Tests view or hwcli test run in CI.
  • Failures lists failed deliveries, replays and tests across the workspace. Diff compares a failed event with the most similar successful one, hiding volatile fields.
  • Share creates an expiring, read-only link with secrets redacted.

Viewers can inspect and compare. Developers can also replay, debug, and create and run tests. Admins also manage everyone's tests and Workspace settings → Debugging (production replay, ignored diff fields, redaction rules). Replays to hosts that look like production always ask for confirmation. Step-by-step instructions are in the guides.

CLI

Install hwcli

hwcli is the HookWatcher command line: watch requests as they arrive, deliver them to a server on your machine, give a local service a public HTTPS URL with a tunnel, replay and debug requests, and turn them into regression tests for CI. It is one small executable for macOS, Linux and Windows (x64 and ARM64) with nothing else to install.

PlatformInstallUpdate
macOS (Apple silicon, Intel)brew install hookwatcher/tap/hwclibrew upgrade hwcli
Linux (x86-64, ARM64)Homebrew, as on macOS, or the downloadsame way
Windows 10/11 (x64, ARM64)scoop install hookwatcher/hwcliscoop update hwcli

macOS and Linux (Homebrew)

brew tap hookwatcher/tap https://gitlab.com/hookwatcher/hwcli-hb
brew install hookwatcher/tap/hwcli
hwcli version

Windows (Scoop)

scoop bucket add hookwatcher https://gitlab.com/hookwatcher/hwcli-wn
scoop install hookwatcher/hwcli
hwcli version

Direct download

The Developer page lists the downloads for your platform with copyable install commands. Each /downloads/hwcli/latest/<platform> link always serves the newest version:

curl -fL -o hwcli.tar.gz https://hookwatcher.com/downloads/hwcli/latest/darwin-arm64
tar -xzf hwcli.tar.gz
mkdir -p ~/.local/bin && install -m 0755 hwcli ~/.local/bin/hwcli
hwcli version

Replace darwin-arm64 with darwin-amd64, linux-amd64 or linux-arm64; on Windows use windows-amd64 or windows-arm64 (a zip). Linux builds are statically linked and run on any distribution, including Alpine. On Windows, unpack the zip and add its folder to your PATH; hwcli isn't code-signed, so SmartScreen may ask the first time.

On macOS, a copy downloaded with a browser is quarantined, and macOS says it "cannot be opened" because hwcli isn't notarized. Install with Homebrew, or download with curl as shown on the Developer page. Don't turn off Gatekeeper.

hwcli checks at most once a day whether a newer version exists and prints a notice; it never updates itself (hwcli config set update-check false turns the check off). hwcli version --check asks on demand.

CLI

Get started with hwcli

hwcli login                                        # approve the code in your browser
hwcli workspace use                                # pick a workspace (if you have several)
hwcli hooks create --name payments --copy          # new endpoint, URL on the clipboard
hwcli listen payments --to http://localhost:3000/webhooks   # deliver new webhooks to your app
hwcli requests list                                # what arrived

hwcli login shows a one-time code and opens your browser; approve the matching code while signed in to HookWatcher (--no-browser prints the link for SSH sessions and containers). The session lasts 90 days and can be revoked on the Developer page, with hwcli sessions revoke or with hwcli logout. The token is kept in the macOS Keychain, the Linux Secret Service when available, or a file only you can read. hwcli whoami shows the account, plan, workspace and where the token is stored.

Run hwcli with no arguments for an interactive menu, and add --help to any command. An event can be given in full, as evt_ plus the first 8 characters (as hwcli prints it) or as any unique prefix of 8 or more characters. A hook is an endpoint ID or its name in the selected workspace; -w picks another workspace for one command. Your workspace role applies exactly as in the web app: Viewers can read, Developers can also replay, debug and run tests, and Admins and the Owner can change endpoints and forwarding.

CLI

hwcli commands

CommandsWhat they do
login · logout · whoami · sessionsSign in and out, show the account, list or revoke CLI sessions
workspace list · use · currentChoose the workspace other commands use
hooks list · get · create · copy-url · deleteEndpoints; create --name N --copy, --template stripe
requests list · watch · tailSearch history, stream new requests; filters --hook --method --event --provider --status --forwarding --graphql --since --until
requests getMetadata, forwarding results, headers (masked unless --reveal), body; --raw prints the exact bytes
requests replay · resendSend from HookWatcher to a public URL (--to), or again to one forwarding endpoint (--endpoint)
listenDeliver a hook's webhooks to a server on this machine
forwarding list · add · edit · enable · disable · test · removeForwarding endpoints: --url for public URLs, --cli --to for this device
replay · debug · diffReplay exactly or modified, Replay Lab, debug sessions, compare two events
test create · apply · list · show · run · deleteRegression tests from real events, as YAML in your repository, run locally or in CI
monitorForwarding health per endpoint over 24 hours (--failing, --watch)
config · version · completion · helpSettings, version check, shell completion for bash, zsh, fish and PowerShell

events, endpoints and fwd are aliases of requests, hooks and forwarding. Every option is listed by hwcli <command> --help.

CLI

Deliver webhooks to your machine

hwcli listen <hook> --to http://localhost:3000/webhooks (:3000/webhooks works as shorthand) creates or reuses a CLI endpoint for this device and delivers each new request, byte for byte, to a server on this machine. Only localhost, 127.0.0.1 and [::1] are accepted, and HookWatcher never connects to your machine: hwcli asks for deliveries. hwcli listen without a hook serves every CLI endpoint bound to this device.

OptionEffect
--to URLLocal URL (also --forward, -t, -f)
--name NAMEEndpoint name (default Local (<this device>))
--append-pathAppend the path called after the hook ID (…/<id>/orders → <URL>/orders)
--include-sensitiveAlso deliver signature and credential headers, needed when your server verifies signatures

A CLI endpoint is a forwarding endpoint like any other: it counts toward the hook's plan limit, keeps its place while hwcli isn't running (shown as Offline) and is removed with hwcli forwarding remove. Failed local deliveries are retried like any endpoint's. hwcli reconnects on its own after network interruptions and marks the device offline when you press Ctrl+C. Dashboard Replay Locally requests for this device are delivered by the same command.

CLI

Replay, debug and test from the terminal

hwcli replay <event> sends a capture again exactly (method, path, query, headers and raw body) from this machine to your listen target or --target :8080/webhooks, or from HookWatcher to a public URL with --to. Several events are replayed in the order given. The capture itself never changes.

OptionsUse
--set a.b=5000 · --remove a.c · --header N:V · --remove-header N · --query k=vChange the JSON body, headers or query
-X PUT · --path /x · --content-type T · --body-file F · --editChange the method, path, type or whole body, or edit in $EDITOR
--duplicate N · --concurrent N · --burst N --duration 5s · --delay 5sReplay Lab: idempotency, races, load, timing (marked X-HookWatcher-Synthetic)
--resign · --invalid-signature · --remove-signature · --malformed invalid_jsonSignature and broken-body tests

hwcli debug <event> --forward :8080/webhook opens a Debug Session: Enter replays after you change your code, e edits the request, s saves the passing attempt as a test, q quits. hwcli diff <failed> <successful> compares two events and hides volatile fields unless --all.

hwcli test create payment-ok --from evt_9da96b80 --expect-status 200 \
  --assert 'order.status equals "paid"' --max-latency 500
hwcli test show payment-ok -o yaml > tests/payment-ok.yaml   # tests as code
hwcli test apply tests/*.yaml
hwcli test run --target http://localhost:3000 [--ci --junit report.xml]

CLI

hwcli in scripts and CI

In CI, set HWCLI_TOKEN to an API key instead of signing in (tests need tests:read and tests:run), and use -o json or -o yaml for machine-readable output on stdout; messages go to stderr and --quiet prints only IDs or URLs. CLI calls count toward your plan's API requests per minute; listen deliveries don't count.

VariableEffect
HWCLI_TOKENAPI key or session token; overrides the saved sign-in and is never written to disk
HWCLI_SERVERServer for every command
HWCLI_WORKSPACEWorkspace ID for every command
HWCLI_TEST_TARGETDefault --target for test run
HWCLI_CA_BUNDLEExtra CA certificates (PEM) for TLS-inspecting proxies; TLS is always verified
HTTPS_PROXY · NO_PROXY · NO_COLOR · HWCLI_NO_UPDATE_CHECKProxy, no colors, no update check
Exit codeMeaning
0Success
1A replay, debug attempt or test didn't succeed, or diff found differences
2 · 3 · 4 · 5Usage error · not signed in · not permitted · not found
6 · 7 · 8 · 9Rejected by the server · API limit reached · network or server error · version no longer supported
130Interrupted (Ctrl+C)

hwcli test run uses its own codes: 0 all passed, 1 a test failed, 2 configuration error, 3 authentication error, 4 your app or HookWatcher unreachable.

CLI

hwcli troubleshooting

SymptomWhat to do
"You are not signed in" (exit 3)hwcli login, or check HWCLI_TOKEN and hwcli config get server
macOS: "cannot be opened"It was downloaded with a browser. Install with Homebrew, or download with curl
Windows SmartScreen warninghwcli isn't code-signed yet. Install with Scoop to avoid the prompt
listen: "connection refused"Your local server isn't running on that port
listen: endpoint limit reachedThe hook already has your plan's number of endpoints: hwcli forwarding list <hook>
listen: signatures fail locallyAdd --include-sensitive
"Secure connection … failed"Behind a TLS-inspecting proxy, set HWCLI_CA_BUNDLE to its CA certificate
"API request limit reached" (exit 7)Wait a minute; watch and listen resume on their own
"no longer supported" (exit 9)Install the current version

Tunnels

How tunnels work

A tunnel gives a service on your machine a public HTTPS hostname, https://<name>.hookwatcher.com. Requests to it go live to your local app and the app's response goes back to the caller. Unlike hwcli listen, which delivers webhooks HookWatcher already captured, a tunnel returns your app's real answer, which OAuth callbacks, URL verification challenges and demos need.

Caller → HookWatcher tunnel service ⇄ hwcli (your machine) → http://localhost:3000
                                  ↑ encrypted connection opened by hwcli, outbound
  • hwcli opens the connection from your machine. Nothing on your machine accepts connections from the internet, and no router or firewall change is needed.
  • Each connection is authorized by HookWatcher with a one-time ticket tied to your hwcli sign-in. Only your hwcli can serve your hostname.
  • Request history is kept only on your computer, in hwcli's local panel. HookWatcher keeps connection details and monthly totals.

Tunnels

Start, stop and manage tunnels

Install hwcli (CLI install) and sign in with hwcli login. Then:

CommandDoes
hwcli tunnel http 3000Serve this device's tunnel (reserved on first use, so the URL stays the same) and show requests live. Ctrl+C stops it.
hwcli tunnel http 3000 --name payment-testThe same with a name you choose: https://payment-test.hookwatcher.com (plans with custom names)
hwcli tunnel create --name payment-testReserve a hostname without starting it
hwcli tunnel start payment-test --port 3000Serve an existing tunnel from this machine
hwcli tunnel listYour tunnels, their state and plan usage
hwcli tunnel status [name]Connection, agent, gateway and request count
hwcli tunnel stop <name>Disconnect a running session, from any machine. The hostname stays reserved
hwcli tunnel delete <name>Delete the tunnel and release its hostname

Useful options: --secret (callers must send the access secret as X-Tunnel-Secret or a Basic auth password), --preserve-host (send the public hostname as Host), --max-concurrency (parallel requests to your app; more wait their turn) and --capture (what the local history keeps). The dashboard's Tunnels view creates, renames, disables and deletes tunnels too, and shows whether each one is Connected.

Tunnels

Requests, limits and reconnection

  • Forwarded as sent: method, path, query, headers and body. Bodies stream both ways. Your app also gets X-Forwarded-For, X-Forwarded-Proto: https and X-Forwarded-Host; headers added by the network edge are removed first.
  • Cookies stay on the tunnel's hostname: the Domain attribute is removed from Set-Cookie.
  • Timing: your app must start answering within 60 seconds and a request may last 5 minutes (503 "Timed out" otherwise).
  • Plan limits: connected tunnels, requests in flight per tunnel and per account (429), request body size (413) and the monthly request and traffic allowance (429 once used up). Values per plan are on the pricing page.
  • Not supported: WebSocket and other protocol upgrades (501).
  • Connection loss: hwcli detects a dead connection within about a minute and reconnects with a new session, at once after a HookWatcher restart and with backoff otherwise. Requests in flight at that moment fail for their callers and are not re-sent.
  • One hwcli per tunnel: starting the same tunnel elsewhere takes it over; the earlier hwcli stops and says so.

Tunnels

Tunnel usage and monthly totals

CounterWhat it counts
TunnelsHostnames reserved on your account (connected or not)
ConnectedTunnels whose hwcli is connected and was seen by the tunnel service in the last minute
Requests this monthRequests handed to hwcli, including ones your app answered with an error or never answered. Requests refused before that (tunnel offline, wrong access secret, a limit reached) don't count. A caller's retry is a new request.
Traffic this monthRequest and response body bytes through the tunnel service. Headers aren't counted. An interrupted transfer counts the bytes that were sent.

Months are calendar months in UTC. Totals are reported by the tunnel service every 15 seconds and kept in the database, so they survive restarts. Tunnels belong to your account, not to a workspace: they count against your plan, whichever workspace you are viewing.

Tunnels

Tunnel troubleshooting

SymptomWhat to do
404 "No tunnel here"hwcli isn't running for that hostname. Run hwcli tunnel http <port> or hwcli tunnel start <name> --port <port>
503 "Local service unreachable"hwcli is connected, but nothing answered on the local port. Start your app, or check the port. hwcli warns about this when it starts
503 "Timed out"Your app didn't start answering within 60 seconds
401 on every requestThe tunnel requires its access secret. Send X-Tunnel-Secret, or turn the secret off in the dashboard
429A plan limit was reached: requests in flight, or this month's allowance
"Another hwcli session took over"The tunnel was started on another machine or terminal. Stop that one first
"This tunnel is disabled"Enable it in the dashboard (a tunnel disabled by HookWatcher needs support)
"…connected tunnels at a time"Your plan's limit of tunnels connected at once is reached. hwcli tunnel list shows which are connected
Stuck on "Reconnecting"The connection to HookWatcher fails. Check your network; behind a TLS-inspecting proxy set HWCLI_CA_BUNDLE
"Tunnels are not set up on this server"Tunnels aren't available on this HookWatcher installation

API

API

The workspace uses these session-authenticated endpoints (owner or manage permission for changes; same-origin requests only):

GET    /api/webhooks/:id/forward                 destination, 7-day stats, limits
POST   /api/webhooks/:id/forward                 { forwardUrl, enabled, includeSensitive, pathMode: "exact"|"wildcard", forwardQuery }
DELETE /api/webhooks/:id/forward                 disable (keeps configuration); ?remove=true deletes it
POST   /api/webhooks/:id/forward/test            send a marked test event
GET    /api/webhooks/:id/forward/attempts        ?page=1&requestId=… (20 per page)
POST   /api/webhooks/:id/forward/attempts/:attemptId/retry   manual Send again for a failed attempt
GET    /api/webhooks/:id/requests/:requestId/forwarding      { status, destination?, attempts? }
POST   /api/webhooks/:id/requests/:requestId/forwarding      manual Send again / Forward now
GET    /api/webhooks/:id/history?forwarding=failed          filter by forwarding status
GET|PUT /api/profile/notifications                { forwardingFailureAlerts: boolean }

Replay, debug and tests

With an API key (Authorization: Bearer) or an hwcli session. Pick the workspace with X-HookWatcher-Workspace. Test endpoints need the tests:read, tests:write or tests:run scope.

POST   /api/v1/requests/:id/replays          { target: { type: "url"|"cli"|"local", url?, cliSessionId? },
                                              modifications?, scenario?, sequence?, signatureMode?, confirmProduction? }
GET    /api/v1/replay-runs/:groupId          every execution in a run (Lab runs have several)
POST   /api/v1/replays/:executionId/result   hwcli reports a local delivery
GET    /api/v1/requests/:id/lineage          derived replays, sessions and tests
GET    /api/v1/requests/:id/diff/:otherId    ?hideVolatile=true&ignore=path
GET    /api/v1/requests/:id/similar          successful events most like this one
GET    /api/v1/failures                      ?endpoint=&event=&provider=&status=&since=
POST   /api/v1/requests/:id/debug-sessions   then POST /api/v1/debug-sessions/:id/attempts
GET    /api/v1/tests                         POST { yaml } or { name, from, expect }; GET|PATCH|DELETE /api/v1/tests/:name
POST   /api/v1/test-runs                     { names?, suite?, target, ci? } → POST …/:runId/results, …/:runId/finish

Validation errors return HTTP 422 with { "error": "<plain-language reason>", "errorCode": "invalid_destination" }. Delivery endpoints return the attempt with status, responseStatus, durationMs and errorType, using HTTP 502 (or 504 for timeouts) when the destination failed. Scoped API keys (/developer) cover read, replay and export under /api/v1.

More

AI-generated documentation

Owners and managers can generate Markdown documentation for a request with AI, add context, polish edits, and save or delete it. Generate and polish each use one AI credit (Free: 30/month, Developer: 100/month, Pro: 300/month, Team: 600/month). Documentation is saved only when you click Save, and saved edits are never overwritten unless you generate or polish again. Sensitive headers are masked before content is sent to the AI provider.

Workspaces

Workspaces

A workspace holds endpoints, their captured requests, forwarding rules, alerts, API keys and an activity log, and the people who can see them. Use one for each project, client or environment. The workspace switcher at the top of the sidebar shows which one you are in; endpoints, search, analytics and live updates always cover only the active workspace.

Switching workspace never changes an endpoint's URL. Providers keep sending to the same address no matter who opens it or from which workspace.

Workspaces

Your Default Workspace

Every account has a Default Workspace. Endpoints you created before workspaces existed are already in it, with their URLs, history, forwarding and settings unchanged. You can rename it and invite people to it, but it can't be deleted while it is your only workspace. HookWatcher opens the workspace you used last.

Workspaces

Workspace limits

Limits come from the workspace owner's plan. The owner doesn't count towards the member limit; pending invitations do.

PlanWorkspaces you ownMembers per workspaceRetention
Free1310 days
Developer31030 days
Pro52060 days
Team205090 days
EnterpriseCustomCustomCustom

Workspaces you were invited to don't count towards your own workspace limit. When you reach a limit, HookWatcher tells you which one and which plan raises it.

Workspaces

Members and roles

OwnerAdminMember
View endpoints and requestsYesYesYes
Create, edit and delete endpoints; forwarding; replayYesYesNo
Invite people and remove membersYesYes (members only)No
See the activity logYesYesNo
Change roles, rename, set retentionYesNoNo
Transfer ownership or delete the workspaceYesNoNo

Each workspace has exactly one owner. Anyone except the owner can leave a workspace from its settings; the owner transfers ownership first. Removing a member ends their access straight away, including any page they have open.

Workspaces

Invitations

Owners and admins invite people by email from Workspace settings → Members. The invitation link works for 7 days and only for an account signed in with the invited email address; someone without an account can sign up with that address first. Pending invitations can be revoked at any time. The invited account also sees them as a banner in the app and can accept from there.

Workspaces

Retention

The owner sets how long captured requests are kept for the whole workspace, up to the plan's maximum. An endpoint can keep requests for less time by setting its own retention in Endpoint settings; leave that field empty to follow the workspace. Each request shows the date it will be removed.

Shortening retention removes older requests at the next hourly cleanup, so HookWatcher asks you to confirm first. Lengthening it applies to requests captured from then on.

Workspaces

Changing plans

Upgrading raises the limits at once. Moving to a lower plan brings your account within that plan's limits, and the plan picker shows exactly what will be removed — by name — before you confirm:

  • Workspaces above the new limit are deleted with their endpoints and requests. Kept first: your Default Workspace, the workspace you're using, then the most recently active.
  • In workspaces with more members than the new limit, pending invitations are revoked first (newest first), then members are removed: admins are kept before members, and recently active people before inactive ones. Removed people keep their own HookWatcher accounts.
  • Workspace retention above the new maximum is lowered to it, and requests older than that are deleted.

Deleting stored data requires typing DOWNGRADE to confirm. Afterwards you get an email summarizing what changed. A lower paid plan starts when its payment completes; time left on the previous plan isn't carried over.

Workspaces

If a subscription isn't renewed

Paid plans are prepaid and never renew or charge automatically. If a paid plan ends without a renewal:

WhenWhat happensEmail
The day it expiresYour account stays available with Free limits; nothing is deleted.Subscription expired, with both dates below
17 days laterNothing changes yet.Deactivated in 3 days
20 days after expiryThe account is deactivated: sign-in shows only its status and renewal, endpoints answer 410 and stop capturing, forwarding and alerts stop, API keys are revoked. Members lose access to your workspaces.Account deactivated, with the deletion date
8 days after deactivationStill recoverable.Data deleted in 2 days
10 days after deactivationThe account and its data — endpoints, requests, workspaces you own, forwarding, API keys — are permanently deleted. Payment records are kept for accounting.Account deleted

Renewing at any point before deletion restores everything; create new API keys afterwards. Before deactivation you can also switch to the Free plan, which keeps the account within Free limits. Dates are shown exactly (UTC) in the emails, on the account status page and in the plan picker.

Workspaces

Workspace security

Every request is checked against your membership on the server. A workspace you don't belong to looks the same as one that doesn't exist. API keys are bound to the workspace that was active when you created them and can't reach endpoints in any other workspace. Owners and admins can review invitations, role changes, removals and settings changes in the activity log.

Workspaces

Moving from endpoint sharing

Workspaces replace sharing single endpoints. People you shared an endpoint with before keep exactly that access and nothing more; they still see the endpoint in their Default Workspace. New sharing happens by inviting people to a workspace; the Members button on an endpoint opens that workspace's members.

More

Plans

Plans set the number of endpoints, retained requests, retention, forwarding allowance and timeouts, replay and AI limits. Team and Enterprise endpoints can be made public; Enterprise limits are set per contract. See Pricing for current limits.

More

Need help?

Email [email protected] or read the FAQ.