Horizon

Receive webhooks in self-hosted n8n

Receive webhooks in self-hosted n8n on localhost with a Horizon tunnel, and set WEBHOOK_URL so n8n shows the public URL.

Give webhook services a public URL for your local n8n, and make the Webhook node show that URL instead of localhost:5678.

Before you begin

  • Node.js 18 or later
  • A Horizon account and the CLI (see Getting started)
  • A reserved subdomain for -s. Reserve one on the Subdomains page.
  • A self-hosted n8n that you can restart with environment variables

Start a tunnel

n8n listens on port 5678. Use -s with a subdomain you reserved. Without it, the subdomain is random and changes every run, so every webhook you registered would point at a dead URL. Reserved subdomains are a paid feature, see Pricing.

hrzn tunnel http://localhost:5678 -s my-app
Output
HORIZON: Tunnel connected
  URL          https://my-app.hrzn.run (reserved)
  Forwarding   http://localhost:5678
  Request log  https://hrzn.run/dashboard/tunnels/my-app

Keep this terminal open.

Set the webhook URL

n8n runs on port 5678 and doesn't know it sits behind a proxy. Set N8N_WEBHOOK_URL to the public URL, with a trailing slash. n8n then shows that URL in the editor and registers it with external services.

export N8N_WEBHOOK_URL=https://my-app.hrzn.run/

From n8n 2.35.0, WEBHOOK_URL is deprecated. It still works as an alias and n8n logs a warning on startup. On an older n8n, use WEBHOOK_URL.

n8n's reverse proxy guide also sets N8N_PROXY_HOPS=1, the number of reverse proxies in front of n8n. n8n uses it to read the right entry of X-Forwarded-For. Horizon sends X-Forwarded-For with the visitor's IP address, so set it to read the visitor's IP, for example for an IP allowlist in n8n.

export N8N_PROXY_HOPS=1

Restart n8n in the same shell so it reads both variables.

Copy the URL from the Webhook node

Open a workflow and add a Webhook node. The node now shows URLs that start with https://my-app.hrzn.run/. The test URL uses the path webhook-test and the production URL uses webhook. Paste the one you need into the service that sends the webhook.

Check it works

Send a request to the test URL while the Webhook node listens for a test event.

curl -X POST https://my-app.hrzn.run/webhook-test/your-path -d '{"hello":"world"}' -H "Content-Type: application/json"

The node shows the received data. Your Horizon terminal prints one line:

Output
  POST    200  /webhook-test/your-path

Troubleshooting

The Webhook node still shows localhost:5678

n8n didn't read N8N_WEBHOOK_URL. Restart n8n from the shell where you ran export, or add the variable to your container or service configuration. On n8n before 2.35.0, set WEBHOOK_URL instead.

The URL changed after a restart

You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-app and the URL stays the same. -s needs a subdomain you reserved, see Pricing.

Nothing reaches n8n

  • Check that the Horizon terminal is still running.
  • The test URL works only while the Webhook node is listening. The production URL works only while the workflow is active.

Next steps

On this page