Guide

Test Webhooks Locally With FastHook

This guide shows the complete local testing workflow: create a FastHook source, route it to a CLI destination, run a local receiver, open the authenticated tunnel, send a test webhook, and inspect the resulting request, event, and delivery attempt.

Use this workflow when you want provider traffic, curl test traffic, replays, filters, transformations, retry behavior, and dashboard inspection to behave like production while the final receiver is still running on your machine.

For quick inspection, start with fasthook-log. It is a tiny local HTTP server that prints the method, URL, headers, and body, then responds with JSON. Once the route works, replace fasthook-log with your real local app.

What You Will Build

You will build a local webhook test route that keeps the public provider URL stable while the destination points at localhost through the FastHook tunnel.

  • A source URL such as https://hook-xxxxxx.fasthook.io/ receives the inbound webhook.
  • A CLI destination represents the local receiver inside FastHook routing.
  • A connection routes accepted source traffic to the CLI destination.
  • fasthook-cli keeps an authenticated WebSocket open to the tunnel service.
  • fasthook-log or your own app receives the forwarded request on localhost.
  • Requests, Events, and Attempts show what FastHook accepted, routed, delivered, retried, or rejected.
The provider still sends to the FastHook source URL; fasthook-cli only handles the final local delivery.

How Local Delivery Works

A CLI destination receives routed events like any other destination, but the final delivery goes through the FastHook tunnel service. The CLI opens an outbound WebSocket to FastHook, receives each delivery job, forwards it to the local URL you choose, and returns the local HTTP response to FastHook.

The provider never connects to your laptop directly. The provider still sends traffic to the FastHook source URL, and FastHook still applies source verification, filtering, transformations, connection routing, rate limits, retries, and event inspection before the CLI receives a delivery.

Flow
Source URL
  -> FastHook request
  -> accepted request record
  -> event fan-out
  -> connection rules, filters, transformations, retry policy
  -> CLI destination
  -> FastHook tunnel service
  -> fasthook-cli WebSocket
  -> http://localhost:8080
  -> fasthook-log or your local app
  -> local response returned to FastHook
  -> event attempt status updated

Before You Begin

You need a FastHook dashboard session, a project API key, the FastHook CLI, and a terminal. On Windows, use the released fasthook.exe. To build the CLI from source, use Node.js 20 or newer. You also need either fasthook-log or a local app that can receive HTTP requests.

  • Use the Project Secrets API Key for fasthook-cli, not the signing secret.
  • Keep your source URL from the dashboard nearby. It usually looks like https://hook-xxxxxx.fasthook.io/.
  • Choose the local port before starting. The examples use 8080.
  • Use a separate CLI destination per developer or machine when you want deterministic local routing.

1. Create Or Choose A Source

The source is the public ingress endpoint. Providers, curl, and test clients send requests to the source URL, not to your localhost server.

  • Open Sources in the dashboard.
  • Create a source or reuse an existing test source.
  • Copy the source URL.
  • Check the source allowed methods if your test request is not POST.
  • If verification is enabled, make sure the provider signature settings match the request you are sending.

2. Create A CLI Destination

In the dashboard, the destination field is labeled CLI path. It is the path FastHook should deliver through the tunnel, not a localhost URL. If you leave it blank, the dashboard normalizes it to /.

  • Open Destinations in the FastHook dashboard.
  • Create a new destination and choose CLI as the destination type.
  • Name it after the local service, such as local-api or checkout-dev.
  • Optionally set a path, such as /webhooks/orders, when your local app expects a specific route.
  • Save the destination and copy its destination id, for example des_xxx.
  • Do not put localhost or a port in the destination config. fasthook-cli supplies that at runtime.
CLI destination config stores the route path and delivery method; fasthook-cli supplies the local host and port at runtime.
Path Mapping
CLI destination path: /webhooks/orders
fasthook-cli target:   http://localhost:8080
local request URL:     http://localhost:8080/webhooks/orders

CLI destination path: /
fasthook-cli target:   http://localhost:8080
local request URL:     http://localhost:8080/

API Shape For CLI Destinations

If you create the route with the public API, create the destination as type CLI and send config.path instead of config.url. Use / when you want the local target root.

The project API key can be sent as a Bearer token, and x-team-id scopes the write to the intended team.

CLI destination API
curl -X POST "https://api.fasthook.io/v1/destinations" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "local-orders-dev",
    "type": "CLI",
    "config": {
      "path": "/webhooks/orders",
      "http_method": "POST",
      "rate_limit": 20,
      "rate_limit_period": "minute"
    }
  }'

3. Connect The Source To The CLI Destination

Create or edit a connection so your source routes events to the CLI destination. This is the same routing model you use for HTTP destinations.

  • Create a connection from that source to the CLI destination.
  • Add filters only when you want to test filtered local delivery.
  • Add transformations only when you want the local app to receive the transformed request.
  • Configure retries and delivery rate limits the same way you would for a deployed receiver.
  • Keep the connection enabled while your CLI tunnel is running.
  • Pause or disable the connection when you do not want local delivery.
Connection API
curl -X POST "https://api.fasthook.io/v1/connections" \
  -H "Authorization: Bearer fhp_xxx" \
  -H "x-team-id: tm_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "local-orders-route",
    "source_id": "src_xxx",
    "destination_id": "des_xxx",
    "disabled_at": null,
    "paused_at": null,
    "rules": []
  }'

4. Install The Local Tools

Use the released fasthook.exe on Windows, or build fasthook-cli from source. fasthook-log lives in a separate repository and is useful when you want a simple known-good receiver.

The examples below use fasthook. If fasthook.exe is in your current Windows directory, run the same commands as .\fasthook.exe or add the executable to PATH.

Install
# Windows standalone executable
Invoke-WebRequest `
  -Uri "https://github.com/alencmanis/fasthook-cli/releases/latest/download/fasthook.exe" `
  -OutFile ".\fasthook.exe"

.\fasthook.exe --help

# Source build
git clone https://github.com/alencmanis/fasthook-cli.git
git clone https://github.com/alencmanis/fasthook-log.git

cd fasthook-cli
npm install
npm run build

5. Start A Local Receiver

Start fasthook-log first when you want to prove that FastHook can reach localhost. It listens on http://localhost:8080 by default, prints each request, and responds with { "ok": true }.

Keep this process running in its own terminal. Later, stop it and run your actual app on the same port.

PowerShell
cd fasthook-log
npm install
npm start

# expected output
Log server listening on http://localhost:8080
Max logged body size: 65536 bytes

6. Log In With fasthook-cli

Open a second terminal. Authenticate with the API Key from Project Secrets. The CLI stores the key in ~/.fasthook/config.json by default, so you do not need to pass it every time. If you built from source, replace fasthook with npx . in these examples.

PowerShell
fasthook login --api-key fhp_xxx

# optionally save the default destination
fasthook config --destination des_xxx

# inspect saved config
fasthook config

7. Start The Tunnel

Start the tunnel against the CLI destination. If --to is omitted, fasthook-cli defaults to http://localhost:8080, which matches fasthook-log.

  • --destination is the CLI destination id from the dashboard.
  • --to accepts a bare port, localhost, 127.0.0.1, or ::1 HTTP URL.
  • Non-local URLs are rejected by the CLI.
  • The CLI prints connected, disconnected, failed delivery, and periodic stats lines.
  • Stop the tunnel with Ctrl+C.
Keep one terminal on the local receiver, one on fasthook-cli tunnel, and send the webhook to the source URL.
Shell
# use the saved destination and default localhost:8080
fasthook tunnel

# or pass everything explicitly
fasthook tunnel --destination des_xxx --to 8080
fasthook tunnel --destination des_xxx --to http://localhost:8080

8. Send A Test Webhook

Send a webhook to the source URL. You can use a real provider test button, a curl command, or a replay from the dashboard. FastHook receives the request, creates events, routes matching events through the connection, and delivers the result to the connected CLI tunnel.

  • In fasthook-log, confirm that the method, URL, headers, and body printed.
  • In Requests, confirm the inbound request is accepted or inspect why it was rejected.
  • In Events, confirm the event status and destination response.
  • In the right-side details panel, inspect headers, body, retry context, and attempt status.
PowerShell
$SOURCE_URL = "https://hook-xxxxxx.fasthook.io/"

curl.exe -X POST $SOURCE_URL -H "content-type: application/json" -d "{""type"":""invoice.created"",""id"":""evt_local_123"",""amount"":4200}"

9. Read The Local Logs

fasthook-log prints the forwarded request exactly as your local app would see it after FastHook routing and transformations. The CLI also adds x-fasthook-tunnel: cli, which is useful when your app wants to detect tunneled local traffic.

  • fasthook-log keeps up to 65536 bytes of body text by default.
  • Set MAX_BODY_BYTES when you need larger local log output.
  • GET and HEAD deliveries are forwarded without a body.
  • Hop-by-hop headers such as host, connection, transfer-encoding, upgrade, and content-length are removed before forwarding.
fasthook-log
POST /webhooks/orders
headers:
{
  "content-type": "application/json",
  "x-fasthook-tunnel": "cli"
}
body:
{"type":"invoice.created","id":"evt_local_123","amount":4200}

10. Inspect FastHook Records

The dashboard is the source of truth for the full delivery path. Use local logs to debug your receiver, and use FastHook records to debug ingestion, routing, and retry behavior.

  • Requests shows whether FastHook accepted or rejected the inbound webhook.
  • Rejected requests show causes such as Source Method Not Allowed or verification failure.
  • Events shows the routed event status, source, connection, destination, method, and URL path.
  • Attempts show the destination response status, response body, timing, retry trigger, and failure code.
  • A 2xx local response marks the CLI attempt successful.
  • A 4xx or 5xx local response marks the attempt failed and can trigger retry behavior.

Use Your Real Local App

After fasthook-log proves the route, stop it and run your app on the same port. If your app uses another port, pass that port with --to.

  • Make sure the destination path and local app route agree.
  • Return quickly from your local handler and move heavy work to async code when possible.
  • Return 2xx only after your local app accepts the webhook.
  • Use verbose CLI logs when you need to see every delivery result.
Shell
# app listens on localhost:3000
fasthook tunnel --destination des_xxx --to 3000

# app listens on a base path
fasthook tunnel --destination des_xxx --to http://localhost:3000/api

# destination path /webhooks/provider is appended to the target
http://localhost:3000/api + /webhooks/provider
=> http://localhost:3000/api/webhooks/provider

Replay Existing Traffic Locally

Local testing is not limited to new curl requests. You can connect the CLI tunnel and then retry or replay failed traffic from the dashboard to send the same event shape into your local receiver.

  • Run fasthook-log or your local app.
  • Run fasthook-cli against the CLI destination.
  • Open the failed request or event in the dashboard.
  • Use retry or bulk retry when the page offers it.
  • Inspect the new attempt to compare the local response with the previous production or staging response.

Custom Ports And Environment Variables

fasthook-log and fasthook-cli must agree on the local port. You can pass the port directly or use environment variables when scripting the workflow.

  • FASTHOOK_API_KEY supplies the project API key.
  • FASTHOOK_TEAM_ID supplies the team id for API commands when needed.
  • FASTHOOK_DESTINATION_ID supplies the destination id.
  • FASTHOOK_LOCAL_URL supplies the runtime local target.
  • FASTHOOK_CONFIG changes the config file path when you do not want to use ~/.fasthook/config.json.
  • MAX_BODY_BYTES changes how much request body fasthook-log prints.
PowerShell
# terminal 1, fasthook-log on port 9090
$env:PORT = "9090"
npm start

# terminal 2, fasthook-cli to the same port
$env:FASTHOOK_API_KEY = "fhp_xxx"
$env:FASTHOOK_DESTINATION_ID = "des_xxx"
$env:FASTHOOK_LOCAL_URL = "http://localhost:9090"
fasthook tunnel

Troubleshooting

Most local testing failures are caused by source configuration, disabled routing, a disconnected tunnel, or a local server that is not listening on the port the CLI is using.

  • 404 on the source URL: confirm you copied the FastHook source URL and did not send to a destination URL.
  • Source Method Not Allowed: update the source allowed methods or send the HTTP method the source accepts.
  • Unverified request: check source auth settings and provider signature headers.
  • Nothing reaches fasthook-log: confirm the connection points to the CLI destination and is enabled.
  • cli_tunnel_offline: FastHook tried to deliver, but no CLI was connected for that destination.
  • cli_tunnel_timeout: the CLI did not return a local response before the delivery timeout.
  • Local URL Missing: start the tunnel with --to or FASTHOOK_LOCAL_URL.
  • Invalid Local URL: use localhost, 127.0.0.1, or ::1 with http or https.
  • Local Fetch Failed: your local app was not reachable, crashed, closed the connection, or listened on a different port.
  • Attempt failed with a 4xx or 5xx: inspect your app response body in the Event Attempt details.
Debug the local path by boundary: ingress, source auth, connection routing, tunnel state, local URL, and attempt response.

Security Notes

CLI destinations are intended for local development and controlled debugging. For production delivery, use an HTTP destination that points at a deployed service.

Treat the project API key like a secret. Do not paste it into screenshots, public terminals, or committed scripts. Rotate it from Project Secrets if it is exposed.

fasthook-cli only forwards to local HTTP targets. This keeps the tunnel from becoming a general-purpose proxy to arbitrary public URLs.

Quick Checklist

  • Source exists and accepts the method you are sending.
  • CLI destination exists, is enabled, and has the right path.
  • Connection routes the source to the CLI destination.
  • fasthook-log or your app is listening locally.
  • fasthook.exe or fasthook-cli is logged in with the project API key.
  • Tunnel is connected to the correct des_xxx id.
  • Test webhook appears in Requests.
  • Routed event appears in Events.
  • Delivery attempt response matches the local receiver output.

Next