webhooks.xyz

Private Relay

Your server stays closed to the internet. A small agent, whx relay, runs inside your network. It pulls each delivery from webhooks.xyz over one outbound HTTPS connection, calls your server on the local network, and reports the result. No inbound rule, no public address, no VPN.

relay · acme-vpc · agent 1.0no inbound ports
Stripe, GitHub any sender webhooks.xyz bin · verify · queue events wait here your VPC · private subnet relay pull or push your API · 10.0.3.12 no public address one outbound HTTPS connection scoped token · polls every 5 s, or long-poll local call
$ whx relay --bin k7f3-stripe --to http://10.0.3.12:8080/hooks --mode pull

Set it up

  1. On the bin, add a destination and choose Inside my network. Enter the URL as seen from inside: http://10.0.3.12:8080/webhooks. Private and local addresses are accepted here.
  2. In the Private Relay box that appears, make a token. It is shown once. It delivers for this bin only.
  3. On a machine that can reach your server, install the CLI and start the relay:
    curl -fsSL https://webhooks.xyz/cli/install.sh | sh
    whx relay --token whr_…
    The bin page shows the token as connected within a few seconds. Against the staging service: whx relay --host staging --token whr_… (or WHX_HOST=staging; a full https:// URL works too).
  4. Send a request to the bin. The delivery shows in the deliveries list with via relay, the status your server returned, and the time it took.

How it works

Run it as a service

systemd (Linux): whx-relay.service. The unit runs as a system user with a read-only file system, no capabilities and only outbound network. The token goes in /etc/whx/relay.env (0600).

Docker: Dockerfile (distroless, non-root, the static binary only) and a compose file.

docker build -t whx-relay https://webhooks.xyz/static/relay/Dockerfile
docker run -d --name whx-relay --restart unless-stopped --network host \
  -e WHX_RELAY_TOKEN=whr_… whx-relay

Kubernetes: one Deployment with one replica, the token in a Secret as WHX_RELAY_TOKEN, a liveness probe on /healthz port 9229. Run two replicas with two tokens when you want no gap during node drains.

Health and metrics

The agent listens on 127.0.0.1:9229 (--health to change, empty to disable).

Tokens

Security review: the answer sheet

Inbound rulesNone. The agent makes outbound HTTPS connections only. Nothing on the internet can connect to it.
EgressOne host, webhooks.xyz, port 443, TLS 1.2 or newer. Allow this in the egress policy; no other address is contacted.
CredentialOne token per bin, deliver-only scope, stored hashed on our side and in your secret store on yours. Rotation without downtime.
What leaves your networkThe HTTP status, response headers and up to 64 KB of the response body of each call, plus the time it took. Nothing else.
What entersThe webhook requests you asked for, exactly as the sender sent them. The agent calls only the destination URLs you configured on the bin.
Data retentionRequests and deliveries are kept for your plan's retention (10 days Free, 30 days Dev), then deleted. Delete a request or a bin at any time.
AgentOne static binary built from the cli/ source in the repository, standard library only, no dependencies, checksum in version.json. Runs as an unprivileged user; the systemd unit and container image are hardened.
AvailabilityDeliveries wait in the queue while the agent is down and resume when it reconnects. Retries for 24 hours, then replay by hand or API.

API

# with your account token (whx_…)
POST   /api/v1/bins/<bin>/destinations        {"url": "http://10.0.3.12:8080/webhooks", "mode": "relay"}
GET    /api/v1/bins/<bin>/relay/tokens
POST   /api/v1/bins/<bin>/relay/tokens        {"name": "vpc-eu-1"}   → {"token": "whr_…", "run": "whx relay --token whr_…"}
DELETE /api/v1/bins/<bin>/relay/tokens/<id>

# what the agent calls, with the relay token (whr_…)
GET    /api/relay/<bin>/next?wait=30&max=10
POST   /api/relay/<bin>/ack                    {"id": 12, "status": 200, "ms": 41, "resp_headers": {…}, "resp_body": "…"}

Write your own agent if you prefer: the two calls above are the whole protocol.