DocumentationReceive webhooks on localhost with the relay CLI

Receive webhooks on localhost with the relay CLI

Receive webhooks on localhost or a private network: install the relay CLI, log in, run relay forward --bucket my-app http://localhost:3000/webhooks, and give the printed public URL to Stripe, GitHub or any provider. Permanent URL, team broadcast, replay, Docker and troubleshooting.

To receive webhooks on localhost, run the relay agent: relay forward --bucket my-app http://localhost:3000/webhooks. It creates a bucket with a permanent public URL, prints that URL for you to paste into Stripe, GitHub, Shopify or any provider, and streams every webhook to your local server over an outbound connection. No public IP, no port forwarding, no firewall changes.

Provider ──POST──▶ https://my.webhookrelay.com/v1/webhooks/<id>   (public input, permanent)
                              │  streamed down the agent's outbound connection
                              ▼
                   relay agent on your machine
                              │
                              ▼
                   http://localhost:3000/webhooks                   (your local server)

1. Install the CLI

# macOS and Linux
curl https://my.webhookrelay.com/webhookrelay/downloads/install-cli.sh | bash

# Windows (PowerShell)
iwr https://my.webhookrelay.com/webhookrelay/downloads/install-cli.ps1 -useb | iex

Other options, including direct binaries, Docker and Kubernetes, are on the installation pages.

2. Log in

Create a token on the tokens page and authenticate the CLI:

relay login -k YOUR_TOKEN_KEY -s YOUR_TOKEN_SECRET
relay bucket ls      # should list your buckets (empty on a new account)

The key and secret can also be provided as the RELAY_KEY and RELAY_SECRET environment variables, which is handy in scripts and CI.

3. Forward to localhost

relay forward --bucket my-app http://localhost:3000/webhooks

What happens:

  1. A bucket named my-app is created if it does not exist, with a public input and an internal output pointing at http://localhost:3000/webhooks.
  2. The public input URL is printed, for example https://my.webhookrelay.com/v1/webhooks/2a1b…. Paste this URL into the provider's webhook settings.
  3. The agent stays in the foreground and logs every forwarded request and your server's response.

Stop with Ctrl-C. Start again later with the same command, or attach to the bucket's existing configuration without repeating the destination:

relay forward --bucket my-app

The public URL never changes, so the provider is configured once.

Useful flags

  • --bucket, -b — bucket name. Reuse it to keep one stable URL across restarts and machines.
  • --function, -f <name> — attach a function that transforms or filters each request before delivery.
  • --no-agent — create the configuration only; do not start streaming.
  • --max-retries, --retry-wait-min, --retry-wait-max — retry behaviour when your local server answers with a 5xx.

4. Test it

# terminal 1: a throwaway server that prints what it receives
python3 -m http.server 3000

# terminal 2: forward to it
relay forward -b my-app http://localhost:3000

# terminal 3: simulate the provider hitting the public URL
curl -X POST https://my.webhookrelay.com/v1/webhooks/<id> -d '{"hello":"world"}'

The request appears in the agent's log and on the local server. To see exactly what a real provider sends before your handler exists, point the provider at a free Webhook Bin first.

Share the endpoint with your team

Every agent connected to the same bucket receives every webhook. Each developer runs the same command against my-app on their own machine, and a staging server can run it as a service too. One Stripe or GitHub configuration feeds all of them.

Keep webhooks that arrive while the agent is offline

Webhooks that arrive with no agent connected are stored in the bucket in the received state. Turn on Replay missing on connect on the internal output and they are redelivered automatically, oldest first, when the agent reconnects (details). Any request can also be resent from the bucket's logs, and durable retries keep retrying deliveries that fail for up to 30 days.

Run the agent in Docker or as a service

docker run -d --name whr-relayd --restart always \
  -e RELAY_KEY=YOUR_TOKEN_KEY -e RELAY_SECRET=YOUR_TOKEN_SECRET \
  -e BUCKETS=my-app \
  webhookrelay/webhookrelayd:latest

Use --network host when the destination is on the Docker host's localhost. On a server, install the CLI as a background service with relay service install and relay service start; see the Docker, Kubernetes operator and autostart pages under installation.

Private networks, not just localhost

The destination can be anything the agent can reach: http://10.0.0.5:8080/hooks on a LAN, a Jenkins server on a private subnet, or a service inside a Kubernetes cluster. See webhooks to internal servers and the Jenkins plugin.

Explicit setup for servers and config-as-code

relay bucket create my-app
relay input create --bucket my-app "public endpoint"
relay output create local-app --bucket my-app --destination http://localhost:3000/webhooks
relay forward -b my-app

Name each output (the first argument to relay output create); a second unnamed output in the same bucket fails to create. Inspect with relay bucket inspect my-app, relay input ls and relay output ls; remove everything with relay bucket rm my-app -f.

Troubleshooting

  • The provider reports a timeout or 502. The agent could not reach the destination. Check the local server is listening on the port in the command and that you used http://, not https://, for a plain local server.
  • Nothing arrives. Confirm the provider is posting to the printed input URL (not to localhost) and that the agent is running; the bucket's logs in the dashboard show every request received, whether or not an agent was connected.
  • Signature verification fails. The raw body and headers are forwarded unchanged; verify against the raw body with the endpoint's secret. The signature verifier checks a captured request.
  • Self-signed certificates on the destination. Set INSECURE=true for the Docker agent, or forward to http:// inside the trusted network.

Related: five ways to receive webhooks on localhost compared, ngrok alternatives, provider walkthroughs for Stripe, GitHub and Slack.

Frequently asked questions

How do I receive webhooks on localhost?

Install the relay CLI, run relay login with a token from the dashboard, then run relay forward --bucket my-app http://localhost:3000/webhooks. The command creates a bucket with a public input URL and starts the agent; give that URL to the provider and every webhook is delivered to your local server over the agent's outbound connection.

Does the public URL change when I restart the agent?

No. The bucket's input URL is permanent. Re-run the same relay forward command, or just relay forward --bucket my-app, and the same URL keeps delivering. Reuse the bucket name to keep one stable URL across restarts and machines.

Can several developers receive the same webhooks?

Yes. Any number of agents can run relay forward against the same bucket, and every connected agent receives every webhook. One provider configuration feeds the whole team, and a staging server can subscribe too.

What happens to webhooks that arrive while my agent is not running?

They are stored in the bucket and marked received. Enable Replay missing on connect on the internal output to redeliver them automatically when the agent reconnects, or resend any request from the logs. With durable retries the delivery keeps retrying for up to 30 days.

Do I need a public IP or to open a port?

No. The agent opens an outbound connection to Webhook Relay; nothing listens on your router or firewall. The same setup delivers to servers on a private network, Docker containers and Kubernetes services.

Can I transform or filter webhooks before they reach localhost?

Yes. Attach a function to the output with relay forward --function my-function or in the dashboard, or add forwarding rules on the output. The function can rewrite the body and headers, or drop unwanted events, before the agent delivers the request.

Did this page help you?