All Guides

Hermes Agent Guide

Hermes Agent and ngrok: Getting Webhooks Into a Local Gateway

Hermes has a first-class webhook platform, which makes the ngrok question more concrete than usual: there is a specific listener, on a specific port, with per-route HMAC secrets already built in. Get those three things right and a tunnel is straightforward. Get the port wrong and you will spend an afternoon debugging the wrong service.

Why the question comes up

Most of what an agent does is outbound, and outbound works fine from a laptop. The trouble starts with anything inbound: a service that wants to notify Hermes when something happens, or a provider that delivers events by webhook. Those need an address on the public internet, and a local port is not one.

ngrok bridges the gap by running a process on your machine that holds an outbound connection to ngrok's servers; traffic arriving at your public URL is forwarded down that connection to your local port.

Step 1: enable the webhook platform

The guided route is the setup wizard:

hermes gateway setup

You can also configure it directly. In the Hermes environment file:

WEBHOOK_ENABLED=true
WEBHOOK_PORT=8644
WEBHOOK_SECRET=your-global-secret

Or in config.yaml:

platforms:
  webhook:
    enabled: true
    extra:
      port: 8644
      secret: "your-global-hmac-secret"

Then start the gateway:

hermes gateway run

Step 2: tunnel the right port

The webhook listener defaults to 8644, so that is what the tunnel points at:

ngrok http 8644

Do not tunnel 9119. That is hermes dashboard, the web UI. Publishing a control surface is a categorically worse exposure than publishing a signature-verified webhook endpoint. If you changed the webhook port in your config, substitute your own — but keep the dashboard off the tunnel either way.

Step 3: subscribe a route and use its secret

Rather than pointing a provider at a bare port, register a route. Hermes issues each route its own HMAC secret, generating a strong one when you do not pass one in:

hermes webhook subscribe <name>
hermes webhook list
hermes webhook test <name>
hermes webhook remove <name>

The route name is not decorative — it becomes the path, so a route called github is served at /webhooks/github. subscribe, test and remove each take that name as a required argument; only list takes none.

Signature validation happens before Hermes does anything else with the request, and it is not limited to one vendor's format. Upstream handles GitHub's X-Hub-Signature-256, GitLab, Svix (which signs id.timestamp.body rather than the body alone) and Linear, plus a generic HMAC-SHA256 mode via X-Webhook-Signature-V2. Anything that fails is rejected with a 401 before reaching your agent. hermes webhook test <name> lets you confirm the path end to end before involving the real sender.

Per-route secrets are stored locally in a subscriptions file that Hermes writes with restrictive permissions, specifically so other local users cannot read them. Do not undo that by loosening the file mode or copying it around.

Lock the tunnel down

A tunnel does not only let your provider in — it lets in anyone who learns the URL. An agent is a high-value target: it holds credentials and can take actions.

  • Keep signature verification on. It is the actual boundary, not decoration.
  • Restrict at the tunnel too, with basic auth or an IP allowlist limited to the sending service's published egress ranges.
  • Expose only the webhook port. Never the dashboard, and never a shell.
  • Do not leave a tunnel open after you have finished using it.
  • Treat the URL as a secret; an unguessable subdomain is obscurity, not access control.
  • Rotate a route secret with hermes webhook remove <name> followed by a fresh subscribe if it leaks.

What the free tier actually gives you

Worth checking current limits rather than trusting older write-ups, which are widely out of date. The free plan allows up to three online endpoints, includes roughly 1 GB of data transfer per month, and assigns your account one persistent development domain. Request inspection and replay are included on every tier.

The persistent domain matters here: because the address is tied to your account rather than generated per session, a route you registered with a provider keeps working across restarts. The old complaint about re-registering the webhook every time the tunnel drops no longer applies. The inspector is also genuinely useful — you can replay a delivery instead of asking the provider to send another.

When you do not need a tunnel at all

A tunnel works around one constraint: the gateway lives on a machine with no stable public address. That constraint is not a law of nature. A hosted gateway already has a public endpoint, so providers post to it directly, the address survives restarts, TLS is handled, and there is no second piece of software whose failure silently breaks the integration.

If you keep it self-hosted, the VPS guide covers giving the gateway an address of its own, and the tmux guide covers the related trap of keeping the process alive in the first place.

So which should you use

  • Use ngrok while developing locally, when you want to inspect and replay deliveries, or for a short-lived test against a real provider.
  • Use a real public endpoint once the integration is something you rely on. A tunnel is a development tool, not an uptime strategy.

The equivalent walkthrough for the other framework is OpenClaw and ngrok.

Hermes Agent and ngrok FAQ

Which port should I tunnel for Hermes webhooks?

The webhook platform's port, which defaults to 8644. Do not tunnel the dashboard — hermes dashboard serves the web UI on port 9119, and exposing that to the internet is a different and much worse idea than exposing a signed webhook endpoint.

How do I enable the webhook platform?

Run hermes gateway setup, or set WEBHOOK_ENABLED=true and WEBHOOK_SECRET in the Hermes .env, or add a platforms.webhook block to config.yaml. Then start the gateway with hermes gateway run.

Does Hermes verify webhook signatures itself?

Yes, and this is the part worth using. hermes webhook subscribe creates a route with its own HMAC secret, generating a strong one if you do not supply it. Validation runs before anything else and understands several provider schemes — GitHub, GitLab, Svix and Linear — plus a generic HMAC-SHA256 mode using X-Webhook-Signature-V2. A request that fails validation is rejected with a 401.

Do I need ngrok if Hermes is hosted?

No. A hosted gateway already has a stable public address, so a provider can post to it directly. The tunnel exists only to work around an agent living on a machine with no public address of its own.

Related Hermes guides

Skip the tunnel entirely

A hosted Hermes gateway has a stable public endpoint, so providers can post to it directly.

See Hermes Hosting