Webhook forwarding is receiving webhooks on a public URL and relaying each request on to another destination: a server on localhost, a machine on a private network with no public IP, or several endpoints at once. The provider only sees the public URL. The forwarder delivers each request onward with its body and headers intact, so your endpoint handles it as if the provider had called it directly.
relay forward --bucket my-app http://localhost:3000/webhooks
# prints a permanent public input URL, e.g. https://my.webhookrelay.com/v1/webhooks/2a1b…
# give that URL to the provider; each webhook is delivered to localhost:3000/webhooks
Why forward webhooks
- Local development. Providers can't reach
localhost:3000. Forwarding lets you test real Stripe, GitHub or Shopify webhooks against the code on your laptop. - Private and on-prem servers. Jenkins, Home Assistant, internal APIs and Kubernetes services often have no public IP, and opening a firewall port isn't an option.
- One URL, many consumers. A provider allows one webhook URL, but production, staging and analytics all need the events. See webhook fan-out.
- Reliability. The forwarder can store each webhook and retry delivery long after the provider would have given up.
- A stable URL. Move the destination between servers, clouds or environments without touching the provider's settings.
How webhook forwarding works
To a public destination (server-side):
- The provider sends a
POSTto the forwarder's public URL. - The forwarder accepts and stores it, and responds to the provider.
- It sends the same request to each configured destination URL, retrying on failure.
To localhost or a private network (agent-based):
- A small agent runs inside your network and opens an outbound connection to the forwarding service. No inbound port is opened.
- The provider sends a webhook to the public URL.
- The service pushes the request down the agent's connection.
- The agent makes the HTTP request to your local destination, for example
http://localhost:3000/webhooks, and reports the response.
Provider ──HTTPS──▶ public URL ──▶ forwarding service
│ (outbound connection opened by the agent)
▼
your network: relay agent ──▶ http://localhost:3000/webhooks
Webhook forwarding vs a tunnel
| Webhook forwarding | Tunnel | |
|---|---|---|
| What's exposed | Only requests sent to the webhook URL | The whole local port |
| If your machine is offline | Requests are stored and can be retried or replayed | Requests fail |
| Many destinations from one URL | Yes | No |
| Logs, retries, transforms | Built in | Usually basic inspection |
| Best for | Webhooks | Sharing a web app, APIs, browser access |
Use a tunnel when people or apps need interactive access to your server. Use forwarding when you're receiving webhooks.
Setting it up with Webhook Relay
Install the relay CLI, run relay login, then:
relay forward --bucket my-app http://localhost:3000/webhooks
The command creates a bucket with a permanent public input URL and starts the agent. Re-running it with the same bucket name keeps the same URL, so you configure the provider once. Several developers can forward the same bucket and every connected agent receives every webhook. The full walkthrough, including Docker and troubleshooting, is in receiving webhooks on localhost.
On top of plain forwarding, each destination can have:
- Durable retries with exponential backoff for up to 30 days.
- Replay on agent connect, which delivers webhooks that arrived while your agent was offline as soon as it reconnects.
- Multiple destinations, up to 200 per bucket, mixing public URLs, internal servers and Slack or Discord.
- Transforms and forwarding rules to reshape or filter events.
- Throttling so a burst doesn't overwhelm a small server.
- Logs of every request and response, with one-click resend.
The webhook forwarding overview covers all of these, and forwarding to internal servers covers production use behind a firewall.
Common mistakes
- Forwarding to the wrong path. The destination URL needs the full route,
http://localhost:3000/webhooks, not just the port. - A forwarder that modifies the body. Re-encoded JSON breaks signature verification. Forward raw bytes unless you mean to transform.
- Using a random URL that changes. Every restart then means updating the provider's settings, and events sent to the old URL are lost.
- Looking in the wrong place for errors. Unless the forwarder is set to pass your endpoint's response back, the provider sees the forwarder's response, not your endpoint's. Check the forwarder's logs to see whether your code failed. Webhook Relay can return a dynamic or static response when a provider needs one.
