Guide · ~4 min · Webhooks

Receive webhooks on your
home lab

GitHub push events, Stripe payments, CI/CD triggers — delivered straight to your self-hosted services. No port forwarding, no static IP, no DDNS.

🛠

GitHub & GitLab

Trigger builds, sync mirrors, deploy on push — all on your own hardware.

💳

Stripe & Payments

Process payment webhooks locally during development. No ngrok needed.

n8n & Automations

Receive triggers for your self-hosted automation workflows from any service.

Webhooks need a public URL

Webhook providers — GitHub, Stripe, Slack, your CI system — need to send HTTP POST requests to your server. But your home lab is behind NAT, your ISP may block inbound ports, and your IP changes every few days.

The usual workarounds are painful: port forwarding, dynamic DNS, reverse proxies, or paying for a cloud VM just to relay a few HTTP requests. HLE webhook tunnels solve this with a single command.

One command

Create a webhook tunnel that forwards incoming requests to your local service:

hle webhook --path /hook/github --forward-to http://localhost:3000/webhook

The CLI prints a public URL like:

https://wh-a3f7b2c9e1d0f485-x7k.hle.world/hook/github

Paste that URL into your webhook provider's settings. Done. Requests to that URL are forwarded to your local service in real time.

No authentication gate. Unlike regular HLE tunnels, webhook tunnels skip the SSO login page — external services can reach your endpoint directly. Security comes from the randomized URL and path restrictions instead.

How webhook tunnels work

Webhook Provider
GitHub / Stripe
HLE Relay
Path check + rate limit
Your Home Lab
Local service
  1. You create a webhook tunnel The CLI opens an outbound WebSocket connection to the HLE relay. No inbound ports needed.
  2. HLE generates a randomized URL The subdomain includes a 16-character hex token (264 possibilities) — unguessable by design.
  3. Provider sends a POST request GitHub, Stripe, or any service sends an HTTP request to your webhook URL.
  4. Relay enforces path + rate limit The server checks the request path matches your configured prefix and applies per-tier rate limits.
  5. Request is forwarded to your service The request travels through the encrypted tunnel to your local endpoint. Response flows back the same way.

Three layers of protection

Randomized subdomains

Regular tunnels use predictable subdomains like myapp-x7k.hle.world. Webhook tunnels use wh-a3f7b2c9e1d0f485-x7k.hle.world — 16 hex characters that are cryptographically random. An attacker would need to guess 1 out of 18 quintillion possible URLs.

Server-side path enforcement

The relay server only forwards requests that match your webhook path prefix. If you registered with --path /hook/github, requests to /admin or / are blocked with a 404 — they never reach your local service.

Signature verification

HLE secures the transport. But you should also verify that requests actually come from the expected provider. Most webhook services sign their payloads:

ProviderSignature HeaderWhat to check
GitHubX-Hub-Signature-256HMAC-SHA256 of body with your webhook secret
StripeStripe-SignatureTimestamp + HMAC with endpoint signing secret
GitLabX-Gitlab-TokenStatic secret token you configure
SlackX-Slack-SignatureHMAC-SHA256 with signing secret
Always verify webhook signatures. The randomized URL prevents casual discovery, but a determined attacker who obtains your URL could send forged requests. Signature verification is your last line of defense.

Common setups

GitHub → self-hosted Gitea

Mirror push events from GitHub to your Gitea instance:

hle webhook --path /hook/github \
  --forward-to http://localhost:3000/api/v1/repos/mirror/hooks/trigger

In GitHub → Repository Settings → Webhooks → Add webhook, paste the HLE URL as the Payload URL. Set Content type to application/json and add a secret for signature verification.

Stripe → local dev server

Test Stripe webhooks during development without the Stripe CLI:

hle webhook --path /stripe \
  --forward-to http://localhost:4242/webhook

In the Stripe Dashboard → Developers → Webhooks → Add endpoint. Select the events you need (e.g. checkout.session.completed) and paste the HLE URL.

n8n workflow triggers

Receive external triggers for your self-hosted n8n automations:

hle webhook --path /webhook \
  --forward-to http://localhost:5678/webhook

In n8n, create a Webhook node, copy the webhook path, and use the HLE URL with that path as the trigger URL in any external service.

Per-plan limits

Webhook tunnels include rate limiting to protect your services from abuse. Limits scale with your plan:

PlanWebhooks/minWebhook tunnelsPrice
Free301$0
Pro603$5/mo
Business12010$12/mo

Rate limit headers are included in every webhook response so your provider can track usage:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42

When the limit is exceeded, the server returns HTTP 429 with a Retry-After: 60 header. Most webhook providers handle 429 responses gracefully by retrying after the specified delay.

Maximum payload size: 10 MB. Webhook payloads larger than 10 MB receive HTTP 413. This is rarely an issue — most webhook payloads are a few KB.

hle webhook vs hle expose

Both commands create tunnels, but they're designed for different use cases:

Featurewebhookexpose
Auth gate (SSO)DisabledEnabled by default
WebSocketDisabledEnabled by default
SubdomainRandomized (wh-<token>)Predictable (<label>)
Path restrictionServer-enforcedNone
Best forMachine-to-machine callbacksHuman-facing services

Use hle webhook for automated callbacks from external services. Use hle expose for anything humans will visit in a browser.

Tips

Webhook tunnels persist across restarts. Set up a systemd service or Docker container to keep your webhook tunnel running permanently. See the Proxmox guide for systemd examples.

Path prefix matching is strict. If you register --path /hook/github, only requests to /hook/github and /hook/github/* are forwarded. Requests to /hook or / get a 404.

The free tier includes 1 webhook tunnel. Enough for one provider. Need to receive webhooks from multiple services? Upgrade your plan.

Test with curl. You can verify your webhook tunnel is working by sending a test request: curl -X POST https://wh-your-token-x7k.hle.world/hook/test -d '{"test": true}'

Start receiving webhooks

Create a free account, grab your API key, and run one command.