Webhooks
HLE webhook tunnels let external services (GitHub, Stripe, GitLab, etc.) deliver HTTP callbacks to your home lab without opening ports or configuring dynamic DNS.
How it works
Webhook tunnels are a specialized mode of HLE tunnels designed for incoming HTTP callbacks:
- No authentication gate — external services can reach your endpoint directly (no SSO prompt)
- HTTP-only — WebSocket is disabled (webhooks are simple POST/GET requests)
- Path-restricted — only requests matching your webhook path prefix are forwarded
- Randomized URL — the subdomain includes a cryptographic token to prevent enumeration
When you create a webhook tunnel, HLE generates a URL like:
https://wh-a3f7b2c9e1d0f485-x7k.hle.world/hook/githubThe wh- prefix and 16-character hex token make the URL unguessable. Your 3-character user code (x7k) is appended so the URL updates automatically if you purchase a custom tunnel code.
Quick start
# Forward GitHub webhooks to a local servicehle webhook --path /hook/github --forward-to http://localhost:3000
# Forward Stripe webhookshle webhook --path /stripe --forward-to http://localhost:4242
# With a custom label (used for custom zones only)hle webhook --path /hook --forward-to http://localhost:8080 --label my-webhookThe CLI prints the public URL you can paste into your webhook provider’s settings.
Understanding URL and path routing
The --path and --forward-to flags work together to control how requests are routed:
--pathsets the path prefix on the public tunnel URL that external services must include- The full incoming path (including the prefix) is forwarded to your local service
--forward-tois the base URL of your local service
Example flow
hle webhook --path /hook --forward-to http://localhost:3000# Tunnel URL: https://wh-a3f7b2c9-x7k.hle.world| External service sends to | Your local service receives |
|---|---|
https://wh-…x7k.hle.world/hook | GET http://localhost:3000/hook |
https://wh-…x7k.hle.world/hook/github | POST http://localhost:3000/hook/github |
https://wh-…x7k.hle.world/other | 404 Not Found (doesn’t match /hook prefix) |
Security
Randomized subdomains
Unlike regular tunnels (e.g. myapp-x7k.hle.world), webhook tunnels use a randomized subdomain with 16 hex characters (2^64 possibilities). This prevents attackers from guessing your webhook URL.
Server-side path enforcement
The HLE relay server enforces that incoming requests match the registered webhook path prefix. Even if someone discovers your webhook URL, they can only reach the specific path you configured — not your entire local service.
Signature verification
HLE forwards all HTTP headers from the external service, including signature headers like X-Hub-Signature-256. You need to configure the webhook secret in both places:
- In the external service (e.g. GitHub webhook settings) — this is where the secret is set
- In your local application — this is where the secret is verified
| Provider | Header | Docs |
|---|---|---|
| GitHub | X-Hub-Signature-256 | Securing webhooks |
| Stripe | Stripe-Signature | Check signatures |
| GitLab | X-Gitlab-Token | Webhook secrets |
| Slack | X-Slack-Signature | Verifying requests |
Rate limits
Webhook tunnels have per-tier rate limits to prevent abuse:
| Plan | Rate limit | Webhook tunnels | Overage |
|---|---|---|---|
| Free | 10 req/min | Unlimited | Blocked + email alert |
| PAYG | 1,000 req/min | Unlimited | Charged per request from credits |
Rate limit headers are included in every webhook response:
X-RateLimit-Limit: 1000X-RateLimit-Remaining: 942When the per-minute limit is exceeded, the server returns HTTP 429 with a Retry-After: 60 header.
Daily limits and billing
All tiers receive a daily free webhook allowance. After the daily limit:
- Free tier: Requests are blocked (HTTP 429) and you receive one email alert per day. Add credits to your account to unlock PAYG and continue.
- PAYG tier: Requests are charged from your credit balance. If credits are depleted, requests are blocked and you receive an email alert.
All webhook rate limits and pricing are configurable by the admin from the dashboard.
Payload limits
- Maximum body size: 10 MB per request
- Requests exceeding this limit receive HTTP
413
CLI reference
hle webhook
Create a webhook tunnel.
| Flag | Type | Default | Description |
|---|---|---|---|
--path | string | required | Webhook path prefix (e.g. /hook/github). Cannot be /. |
--forward-to | string | required | Local URL to forward webhooks to |
--label | string | auto-generated | Service label (used for custom zones) |
--api-key | string | — | API key (also checked in HLE_API_KEY and config file) |
--zone | string | — | Custom zone domain |
Examples
GitHub → self-hosted Gitea
Forward push events from GitHub to a Gitea mirror:
hle webhook --path /hook/github --forward-to http://localhost:3000In GitHub repository settings → Webhooks → Add webhook:
- Payload URL: paste the full HLE URL from the CLI output (e.g.
https://wh-…x7k.hle.world/hook/github) - Content type:
application/json - Secret: set a secret and configure the same secret in your Gitea instance
Stripe → local dev server
Test Stripe webhooks against your development environment:
hle webhook --path /stripe --forward-to http://localhost:4242In the Stripe Dashboard → Developers → Webhooks → Add endpoint:
- Endpoint URL: paste the full HLE URL (e.g.
https://wh-…x7k.hle.world/stripe) - Events: select the events you need (e.g.
checkout.session.completed)
n8n workflow triggers
Receive webhook triggers for n8n automations:
hle webhook --path /n8n --forward-to http://localhost:5678In n8n, create a Webhook node and set its path to /n8n to match the HLE tunnel path.
Renovate bot
Self-hosted Renovate with GitHub webhook delivery:
hle webhook --path /renovate --forward-to http://localhost:8080Configure your Renovate instance to accept webhooks on the /renovate path, and set the GitHub webhook URL to the full HLE URL.
Troubleshooting
404 Not Found
The request path doesn’t match the configured webhook path prefix. If you registered with --path /hook/github, only requests to /hook/github and /hook/github/* are forwarded.
Common cause: The external service sends to the tunnel URL without the path prefix. Make sure the full URL (including the path) is pasted into the webhook provider settings.
Path appears duplicated (e.g. /webhook/webhook)
This usually means the external service is appending its own webhook path to the URL you provided. For example, if you gave a service https://wh-…x7k.hle.world/webhook as the base URL, and the service also appends /webhook, the final request goes to /webhook/webhook.
Fix: Use a different --path (e.g. --path /hook) so it doesn’t collide with the external service’s own path. Or configure the external service with the tunnel base URL only (without the path) if it adds its own path automatically.
429 Rate Limit Exceeded
You’ve exceeded your plan’s webhook rate limit. Wait 60 seconds for the per-minute limit to reset, or upgrade your plan for higher limits.
If you’re hitting the daily limit, check your email for an alert with details. Add credits or upgrade to PAYG for higher allowances.
413 Payload Too Large
The webhook payload exceeds 10 MB. This is uncommon for webhooks — check if the sender is including large attachments.
Webhook secret not working
HLE forwards all HTTP headers (including X-Hub-Signature-256, Stripe-Signature, etc.). The webhook secret itself is never transmitted over the wire — it’s used locally by your application to verify signatures. Make sure the same secret is configured in both the webhook provider and your local application.