Deployment guide

WebHook Tester

Overview

WebHook Tester is an open source, self-hosted endpoint for capturing, inspecting and debugging inbound HTTP requests. You create a session, which gives you a unique capture URL, and point a third-party service at it. Every request that arrives is recorded, so you can read back the exact method, headers and body that were actually sent.

This guide covers deploying and configuring WebHook Tester on Ubuntu 24.04 on Azure, using the SecureAnyCloud Azure Marketplace image. It answers the question every webhook integration eventually raises: what did that service really send me? Instead of guessing from documentation, you point the sender at a capture URL and read the truth off the wire.

The image installs the official WebHook Tester binary (pinned and checksum-verified at build). It runs as the non-root webhook-tester user under systemd, bound to loopback behind an nginx reverse proxy on port 80, so a capture endpoint is listening within minutes of launch.

What you get:

  • WebHook Tester (official pinned binary), run under systemd as the unprivileged webhook-tester user (webhook-tester.service).
  • An nginx reverse proxy on port 80 in front of the loopback-bound service (nginx.service).
  • The filesystem storage driver, so captured requests persist across reboots with no bundled database. WebHook Tester supports memory, filesystem and Redis storage. With the filesystem driver, there is no database to run, secure or patch.
  • A per-VM web UI password, generated on first boot and written to a root-only credentials file. It is never baked into the image.
  • A shipped round-trip self test that proves an anonymous POST is captured and readable back, and that unauthenticated reads are rejected.
  • Retention guard rails: a 7-day session lifetime, 128 requests per session and a 1 MiB request body cap.
  • Unattended security upgrades left enabled, so the server keeps receiving patches.

Security by design: the auth split. A webhook receiver has an unusual requirement. The capture URL must accept unauthenticated requests, because that is the entire point. Everything else must not be world-readable. This image mirrors WebHook Tester's own routing rule in nginx:

  • Capture URLs are anonymous. A request whose first path segment is a session UUID is a capture. Any third-party service can POST to http://<your-vm-ip>/<session-uuid> with no credentials, and the request is recorded.
  • The web UI and the management API require a password. Creating sessions, listing them and reading captured history are all behind HTTP Basic auth. The credential is generated uniquely on this VM's first boot and never baked into the image. An unauthenticated request returns HTTP 401.
  • Nobody can mint endpoints on your instance. Automatic session creation is left off, so a session can be created only through the authenticated API or UI. An anonymous caller can post to an endpoint you already made, but cannot create new ones.
  • Treat a capture URL as a capability. So that the UI's live feed works, the live-update WebSocket for a session accepts an unauthenticated handshake. Browsers cannot send credentials on a WebSocket handshake, so requiring auth there would permanently break live updates. Whoever holds a session UUID can therefore both post to that endpoint and watch that one session's live stream. They still cannot create sessions, list them, read captured history over the REST API, or open the web UI. If different senders must not see each other's traffic, give each one its own session.

Prerequisites

  • An active Azure subscription, an SSH public key, and a VNet and subnet in the target region.
  • A subscription to the WebHook Tester listing on Azure Marketplace.
  • Network Security Group rules that allow TCP 22 (admin) and TCP 80 (WebHook Tester via nginx) from the services that will post to your capture URLs.
  • Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM), which is ample for debugging and integration work. Increase the size only if you expect sustained high-volume capture traffic.
1

Deploy from the Azure Portal

Search the Marketplace for WebHook Tester, select the SecureAnyCloud publisher, and click Create.

Configure the Network Security Group to allow TCP 80 from the services that will send you webhooks, and TCP 22 for administration. For internet exposure, front port 80 with TLS (see Step 8).

2

Deploy from the Azure CLI

RG="webhook-tester-prod"; LOCATION="eastus"; VM_NAME="webhooktester1" IMAGE_URN="sac1647283583153:webhook-tester-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION"
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:webhook-tester-ubuntu-24-04:default:latest
az vm create \
  --resource-group "$RG" --name "$VM_NAME" \
  --image "$IMAGE_URN" \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values "$SSH_KEY" \
  --public-ip-sku Standard \
  --os-disk-delete-option Delete --nic-delete-option Delete
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 80 --priority 1001
3

First boot

On first boot, the image generates a unique web UI password for this VM, writes it to a root-only credentials file, and arms the service. Nothing is shared between VMs. Two machines launched from the same image get different passwords.

Read your credentials over SSH:

sudo cat /root/webhook-tester-credentials.txt
# WebHook Tester — Per-VM Instance Info
# Generated: Sun Jul 19 07:13:34 UTC 2026
#
# There is NO default password. The value below was generated uniquely on THIS
# VM's first boot and was never baked into the image. Keep this file secret (root only). # webhooktester.host=<your-vm-ip> webhooktester.url=http://<your-vm-ip>/ webhooktester.username=admin webhooktester.password=****
4

Confirm the server is running

systemctl is-active webhook-tester nginx /usr/local/bin/webhook-tester --version ss -tln
active active webhook-tester version 2.3.0 (go1.26.2)

The listener set is deliberately small. nginx on port 80 and sshd on port 22 are the only externally bound services, and WebHook Tester itself is reachable only on loopback.

LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("webhook-tester",...)) LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",...)) LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",...))
5

Run the round trip self test

The image ships a self test that proves the product captures data, rather than merely that a port is open. The test creates a session through the authenticated API and posts a known payload to the capture URL with no credentials. It reads the request back through the authenticated API and asserts that the exact payload and header survived. Finally, it checks that unauthenticated reads are refused.

sudo bash /usr/local/lib/sac/webhook-tester-roundtrip.sh
ROUNDTRIP OK: created a capture session through the authenticated API; an ANONYMOUS POST to the capture URL was accepted (200) and the exact payload plus its custom header were read back through the authenticated API; the same read without credentials was rejected 401, a wrong password was rejected 401, and the web UI root was rejected 401. The live-update WebSocket upgraded (101) while a plain unauthenticated GET to the same path was rejected 401. Data capture and the auth split are both proven end to end through nginx.
6

Capture a webhook yourself

First, create a capture session through the authenticated management API. Substitute your own password from Step 3.

PASSWORD="<your-password-from-step-3>" curl -sS -u "admin:$PASSWORD" -X POST http://127.0.0.1/api/session \
  -H 'Content-Type: application/json' \
  -d '{"status_code":200,"headers":[],"delay":0,"response_body_base64":""}'
{ "created_at_unix_milli": 1784445686996, "response": { "delay": 0, "headers": [], "response_body_base64": "", "status_code": 200 }, "uuid": "b6946d26-430a-4c9c-b98f-d141858c2def" }

The uuid is your capture endpoint. Your capture URL is http://<your-vm-ip>/<uuid>. Give this address to the service that will send you webhooks.

Now post to it the way a third party would, with no credentials at all:

SID="<the-uuid-from-above>" curl -sS -o /dev/null -w 'HTTP %{http_code}\n' -X POST "http://127.0.0.1/$SID" \
  -H 'Content-Type: application/json' \
  -H 'X-Demo-Source: order-service' \
  -d '{"event":"order.created","order_id":"A-1024","total":49.99}'
HTTP 200

Read the captured request back through the authenticated API:

curl -sS -u "admin:$PASSWORD" "http://127.0.0.1/api/session/$SID/requests"

The response records the exact method, the client address, every header the caller sent (including your custom X-Demo-Source), and the body, base64-encoded:

[ { "captured_at_unix_milli": 1784445687047, "client_address": "127.0.0.1", "headers": [ { "name": "Content-Type", "value": "application/json" }, { "name": "User-Agent", "value": "curl/8.5.0" }, { "name": "X-Demo-Source", "value": "order-service" } ], "method": "POST", "request_payload_base64": "eyJldmVudCI6Im9yZGVyLmNyZWF0ZWQiLCJvcmRlcl9pZCI6IkEtMTAyNCIsInRvdGFsIjo0OS45OX0=", "url": "http://127.0.0.1/b6946d26-430a-4c9c-b98f-d141858c2def" } ]

Decoding request_payload_base64 returns the payload byte for byte as it was sent:

echo 'eyJldmVudCI6Im9yZGVyLmNyZWF0ZWQiLCJvcmRlcl9pZCI6IkEtMTAyNCIsInRvdGFsIjo0OS45OX0=' | base64 -d
{"event":"order.created","order_id":"A-1024","total":49.99}
7

Use the web UI

Browse to http://<your-vm-ip>/ and sign in with admin and the password from Step 3. The UI shows your current capture URL, ready-made snippets for curl, wget, HTTPie, PowerShell and several languages, and the response the endpoint will return to callers.

Requests appear in the left-hand column live, as they arrive, with no page reload. The image ships the WebSocket proxying that makes this work.

Select any request to see its full detail: path, method, originating address, timestamp, size, the complete header set, and the body rendered as text or binary.

Expand Show all headers to see every header exactly as the sender transmitted it. This is usually where webhook debugging gets resolved: signature headers, content types and custom event fields.

8

Front the server with TLS for internet exposure

Capture URLs are anonymous by design, so anything sent to them travels in the clear over plain HTTP. If your capture URLs are reachable from the internet, terminate TLS in front of nginx. This encrypts webhook payloads and your UI password in transit.

Point a DNS A record at the VM's public IP. Then use certbot's nginx plugin, which edits the shipped server block in place and sets up renewal:

sudo apt-get update
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d webhooks.example.com

Certbot offers to redirect HTTP to HTTPS. Accept the offer, then re-issue your capture URLs as https://webhooks.example.com/<uuid>.

9

Security recommendations

  • Restrict port 80/443 where you can. Most webhook providers publish their egress IP ranges. Scope the Network Security Group to those ranges instead of leaving the capture port open to the whole internet.
  • Give each sender its own session. Anyone holding a session UUID can post to that endpoint and watch its live stream. Separate sessions keep separate senders from seeing each other's traffic.
  • Terminate TLS before you expose capture URLs to the internet (Step 8).
  • Keep the credentials file root-only. It is written 0600 root:root. Leave it that way, and rotate the password if it is ever shared.
  • Treat captured data as sensitive. Webhooks routinely carry customer records, tokens and signatures. Captured requests expire after 7 days, and you can clear them at any time with Delete all requests in the UI.
  • Leave unattended upgrades enabled so the VM keeps receiving security patches.
10

Support and Licensing

WebHook Tester is open source software distributed under the MIT Licence. SecureAnyCloud packages it as a preconfigured Azure image. The software itself remains under its upstream licence and is available from the upstream project.

For image-specific issues, contact SecureAnyCloud support at sales@secureanycloud.com. For questions about WebHook Tester itself, consult the upstream project documentation.

Deploy on Azure

Launch WebHook Tester on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure