Deployment guide

webhook

Overview

webhook is a lightweight, configurable tool that creates HTTP endpoints (hooks) which run configured commands when they are called. It is the simple way to connect an external event to an action on your server: a Git push triggering a deployment, a build job triggering a packaging step, or a monitoring alert triggering a remediation script.

This guide covers deploying and configuring webhook on Ubuntu 24.04 on Azure from the SecureAnyCloud Azure Marketplace image. The image installs the official webhook binary (pinned at build) and runs it as the non-root webhook user under systemd. It is bound to loopback behind an nginx reverse proxy on port 80, so an endpoint runner is listening within minutes of launch.

Because webhook runs commands in response to HTTP requests, an open or careless configuration is a remote code execution risk, so the image is locked down by default. It ships exactly one demonstration hook. Its command is a fixed, non-parameterised script that receives no request-supplied data, so a caller can never inject a command through it.

What you get:

  • webhook (official pinned binary), run under systemd as the unprivileged webhook user (webhook.service). It is hardened with systemd sandboxing (ProtectSystem=strict, NoNewPrivileges, a single writable path) and bound to loopback rather than exposed directly.
  • An nginx reverse proxy on port 80 in front of the loopback-bound endpoint runner (nginx.service).
  • One demo hook at /hooks/demo that runs a fixed, non-parameterised command. It is gated by a trigger rule that requires a secret matched from the X-Webhook-Token request header.
  • A per-VM trigger secret generated on first boot, written to a root-only credentials file and never baked into the image. A request without the correct secret is rejected with an HTTP 403, and the command is not run.
  • A shipped round-trip self test that proves an authorized request runs the command and returns its output, and that an unauthorized request is rejected.
  • Unattended security upgrades left enabled, so the server keeps receiving patches.

Prerequisites

  • An active Azure subscription, an SSH public key, and a VNet and subnet in the target region.
  • A subscription to the webhook listing on Azure Marketplace.
  • Network Security Group rules allowing TCP 22 (admin) and TCP 80 (webhook via nginx) from the clients that will call your hooks.

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) for light use. Busy endpoints should use Standard_D2s_v5 or larger.

1

Deploy from the Azure Portal

Search for webhook in Azure Marketplace, select the SecureAnyCloud publisher, and click Create. Configure the Network Security Group to allow TCP 80 (webhook via nginx) from the systems that will call your hooks, and TCP 22 for administration. For internet exposure, front port 80 with TLS (see Step 8).

2

Deploy from the Azure CLI

RG="webhook-prod"; LOCATION="eastus"; VM_NAME="webhook1" IMAGE_URN="sac1647283583153:webhook-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-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
# Open the webhook and admin ports on the VM's NSG:
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 80 --priority 1001
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 22 --priority 1002
3

First boot

On first boot, the image resolves the VM public IP, generates a per-VM trigger secret, writes the real hooks.json with that secret embedded in the demo hook, and starts webhook behind nginx. This completes within a minute. SSH in as azureuser and read the per-VM details, including the demo endpoint URL and the trigger secret:

sudo cat /root/webhook-credentials.txt

The file contains the endpoint (webhook.url), the host (webhook.host), the secret (webhook.token) and a ready-to-run authorized curl command. Keep it secret. It is readable only by root.

4

Confirm the server is running

Confirm that webhook.service and nginx.service are active. webhook is bound to loopback on 9000, and nginx serves the reverse proxy on 80.

systemctl is-active webhook.service nginx.service /usr/local/bin/webhook -version ss -tln | grep -E '127.0.0.1:9000|:80 '
5

Trigger the demo hook with the round trip self test

The image ships a self test. It reads the per-VM secret, sends an authorized request to the demo hook through nginx, and confirms the command ran and its output was returned. It also confirms that an unauthorized request is rejected with an HTTP 403. Run it to prove the endpoint runner is healthy end to end:

sudo bash /usr/local/lib/sac/webhook-roundtrip.sh
6

Trigger the demo hook yourself

Send an authorized request with the per-VM secret in the X-Webhook-Token header, and webhook runs the demo command and returns its output. Send the same request without the secret, and it is rejected with an HTTP 403 and the command does not run.

sudo bash -c 'TOKEN=$(grep ^webhook.token= /root/webhook-credentials.txt | cut -d= -f2-); \
  echo "--- authorized request (with the secret): ---"; \
  curl -sS -X POST -H "X-Webhook-Token: $TOKEN" http://127.0.0.1/hooks/demo; echo; \
  echo "--- unauthorized request (no secret) returns HTTP: ---"; \
  curl -sS -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1/hooks/demo'

From another machine, call the demo endpoint over the network using the URL and secret from the credentials file:

curl -X POST -H 'X-Webhook-Token: <WEBHOOK_TOKEN>' <WEBHOOK_URL>
7

Define your own hook

Hooks are defined in /etc/webhook/hooks.json (webhook also supports YAML). Each hook has an id (which becomes /hooks/<id>), an execute-command, and optional rules.

Add a hook that runs your own script, and keep the two habits the demo hook models: do not pass request data to the command, and gate it with a secret. Edit the file, then restart the service:

[ { "id": "deploy", "execute-command": "/usr/local/bin/my-deploy.sh", "command-working-directory": "/var/lib/webhook", "http-methods": ["POST"], "include-command-output-in-response": true, "trigger-rule-mismatch-http-response-code": 403, "trigger-rule": { "match": { "type": "value", "value": "<WEBHOOK_TOKEN>", "parameter": { "source": "header", "name": "X-Webhook-Token" } } } } ]
sudo systemctl restart webhook

Your new hook is now live at http://<WEBHOOK_HOST>/hooks/deploy and callable only with the secret. Write my-deploy.sh so that it takes no arguments from the request, exactly as the shipped /usr/local/bin/webhook-demo.sh does.

8

Front the server with TLS for internet exposure

Port 80 speaks plain HTTP. For exposure to the public internet, terminate TLS in front of webhook. The reverse proxy is already nginx, so add a TLS server block with your certificate and key (for example from your own domain or a certificate authority) and redirect port 80 to 443.

The nginx site lives at /etc/nginx/sites-available/sac-webhook. Add a listen 443 ssl; server that proxies to http://127.0.0.1:9000 just as the port 80 server does, then reload nginx:

sudo nginx -t && sudo systemctl reload nginx

After that, call your hooks with https:// instead of http://.

9

Security recommendations

Restrict the NSG. Allow TCP 80 only from the systems that call your hooks, and TCP 22 for administration only.

Front public exposure with TLS. Terminate TLS in nginx (Step 8) whenever the endpoint is reachable from the internet.

Never pass request data into a command. Do not configure pass-arguments-to-command or pass-file-to-command with untrusted request fields. Keep every hook command fixed and non-parameterised, as the shipped demo command is.

Gate every hook with a secret or signature. Require a per-hook secret in a header (as the demo hook does), or verify an HMAC signature such as a GitHub X-Hub-Signature-256. Never ship a hook with no trigger rule.

Keep the credentials secret. The demo hook secret lives only in /root/webhook-credentials.txt (root only) and is unique to this VM. hooks.json is readable only by root and the webhook group.

Keep the OS and webhook patched. Unattended security upgrades remain enabled on the running VM.

10

Support and Licensing

webhook is developed by Adnan Hajdarevic (adnanh) and distributed under the MIT license. This SecureAnyCloud image bundles the unmodified official webhook binary. SecureAnyCloud provides the packaging, the secure-by-default first boot (per-VM trigger secret, a fixed non-parameterised demo command, a non-root hardened service behind nginx), the systemd integration, and 24/7 support with a guaranteed 24-hour response SLA.

SecureAnyCloud is not affiliated with or endorsed by the webhook project. webhook is a mark of its respective owner and is used here only to identify the software.

Deploy on Azure

Find webhook on Ubuntu 24.04 LTS in Azure Marketplace, published by SecureAnyCloud. Deploy from the Portal or the Azure CLI as shown above.

Need Help?

Email sales@secureanycloud.com for deployment help, configuration questions or licensing enquiries.

Deploy on Azure

Launch webhook HTTP Endpoint Command Runner 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