Webhooks
Event-driven automation via HTTP callbacks. Receiving, sending, signature verification, and GitHub webhook patterns.
Webhook Concepts
Webhook vs polling — push beats pull
Polling: Client asks server "anything new?" every N seconds
Webhook: Server tells client "something happened" immediately
Polling wastes requests when nothing changes.
Webhooks deliver events in real time with zero wasted requests.
Receiving Webhooks
Minimal webhook receiver — Python + FastAPI
from fastapi import FastAPI, Request, HTTPException
import hmac
import hashlib
app = FastAPI()
WEBHOOK_SECRET = "your-secret" # from gopass/vault in production
@app.post("/webhook/github")
async def github_webhook(request: Request):
body = await request.body()
# Verify signature
signature = request.headers.get("X-Hub-Signature-256", "")
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET.encode(), body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
raise HTTPException(status_code=401, detail="Invalid signature")
payload = await request.json()
event = request.headers.get("X-GitHub-Event")
if event == "push":
branch = payload.get("ref", "").split("/")[-1]
print(f"Push to {branch} by {payload['pusher']['name']}")
return {"status": "ok"}
Always verify webhook signatures. Without verification, anyone can POST fake events to your endpoint.
Minimal receiver — bash + netcat (testing only)
# Listen for a single webhook delivery (debugging)
while true; do
echo -e "HTTP/1.1 200 OK\r\n\r\n" | nc -l -p 8080 | head -50
done
Sending Webhooks
Trigger a GitHub repository dispatch — spoke notifies hub
curl -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $(gopass show -o github/pat)" \
https://api.github.com/repos/EvanusModestus/domus-docs/dispatches \
-d '{"event_type": "component-updated", "client_payload": {"repo": "domus-captures"}}'
Generic webhook delivery — notify on deploy completion
#!/bin/bash
set -euo pipefail
WEBHOOK_URL="https://hooks.example.com/deploy"
notify_webhook() {
local status="$1"
local message="$2"
curl -sf -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "$(jq -n \
--arg status "$status" \
--arg message "$message" \
--arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
'{status: $status, message: $message, timestamp: $timestamp}'
)"
}
notify_webhook "success" "Deploy to production completed"
GitHub Webhooks
Configure a repository webhook
# Create webhook via gh CLI API
gh api repos/EvanusModestus/domus-docs/hooks \
-f url="https://hooks.example.com/github" \
-f content_type="json" \
-f secret="$(gopass show -o webhooks/github-secret)" \
-f events[]="push" \
-f events[]="pull_request"
Common GitHub events
push — commit pushed to a branch
pull_request — PR opened, closed, merged, updated
issues — issue created, edited, closed
release — release published
workflow_run — GitHub Actions workflow completed
repository_dispatch — custom event (API-triggered)
Webhook Security
HMAC signature verification — the standard pattern
import hmac
import hashlib
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), payload, hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
hmac.compare_digest is timing-attack resistant. Never use == for signature comparison.
Defense checklist
1. Verify HMAC signature on every request
2. Use HTTPS endpoint only
3. Store secret in gopass/vault, not in code
4. Validate payload schema before processing
5. Return 200 quickly, process asynchronously
6. Log all webhook deliveries for audit
Webhook Testing
Test with curl — simulate a webhook delivery
curl -X POST http://localhost:8000/webhook/github \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: push" \
-H "X-Hub-Signature-256: sha256=..." \
-d '{"ref": "refs/heads/main", "pusher": {"name": "evan"}}'
ngrok — expose local server to the internet for testing
# Start local server
uvicorn app.main:app --port 8000
# In another terminal — create public URL
ngrok http 8000
# Use the ngrok URL as your webhook endpoint
# https://abc123.ngrok.io/webhook/github
Replay failed deliveries — GitHub UI
Settings → Webhooks → Recent Deliveries
→ Click a delivery → "Redeliver" button
See Also
-
GitHub Actions — repository_dispatch for webhook-triggered workflows
-
API Webhooks — webhook patterns from the API perspective