Stage 2 · Tools
Network Scripting
Webhook Handler Scripts
Receive and respond to GitHub/Alertmanager webhooks with plain Bash.
Webhook Fundamentals
Webhooks are HTTP callbacks — one system sends an HTTP POST to another when an event occurs. Bash can receive and process webhooks using netcat or a lightweight HTTP server.
# Webhook flow:
# 1. Service A detects an event
# 2. Service A sends POST to Service B's URL
# 3. Service B receives and processes the payload
# Simple webhook receiver using netcat
while true; do
REQUEST=$(nc -l -p 9000)
echo "$REQUEST"
doneA webhook is just an HTTP POST request. The sender sends data as JSON or form-encoded body. The receiver processes the data and optionally responds with a status code.
Simple Webhook Listener
#!/usr/bin/env bash
set -euo pipefail
PORT=9000
LOG_FILE="/var/log/webhooks.log"
handle_webhook() {
local request="$1"
# Parse the HTTP request
local method path
method=$(echo "$request" | head -1 | awk '{print $1}')
path=$(echo "$request" | head -1 | awk '{print $2}')
# Get body (everything after the blank line)
local body
body=$(echo "$request" | sed -n '/^$/,$ p' | tail -n +2)
# Log the request
echo "$(date): $method $path" >> "$LOG_FILE"
# Process based on path
case "$path" in
/deploy)
echo "Deploy triggered"
/opt/scripts/deploy.sh "$body"
echo -e "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK"
;;
*)
echo -e "HTTP/1.1 404 Not Found\r\nContent-Length: 9\r\n\r\nNot Found"
;;
esac
}
# Main listener loop
echo "Webhook listener started on port $PORT"
while true; do
REQUEST=$(nc -l "$PORT")
handle_webhook "$REQUEST" | nc -l "$PORT" # Simplified
doneThis simple listener parses HTTP requests, extracts the method, path, and body, then routes based on the path. For production, use a proper HTTP server like socat or a lightweight framework.
socat provides more robust TCP handling than netcat: socat TCP-LISTEN:9000,fork EXEC:/opt/scripts/handler.sh. Each connection runs the handler script with the request on stdin.
GitHub Webhooks
GitHub sends webhooks for repository events: pushes, pull requests, issues, and more. Processing these enables automated deployments, CI triggers, and notifications.
#!/usr/bin/env bash
set -euo pipefail
# Read the request
REQUEST=$(cat)
PAYLOAD=$(echo "$REQUEST" | sed -n '/^$/,$ p' | tail -n +2)
# Verify webhook signature (important!)
SECRET="$GITHUB_WEBHOOK_SECRET"
SIGNATURE=$(echo "$REQUEST" | grep "X-Hub-Signature-256" | awk '{print $2}')
EXPECTED="sha256=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $NF}')"
if [[ "$SIGNATURE" != "$EXPECTED" ]]; then
echo "Invalid signature"
exit 1
fi
# Extract event type
EVENT=$(echo "$REQUEST" | grep "X-GitHub-Event" | awk '{print $2}' | tr -d '\r')
# Process based on event
case "$EVENT" in
push)
BRANCH=$(echo "$PAYLOAD" | jq -r '.ref' | sed 's|refs/heads/||')
echo "Push to $BRANCH"
if [[ "$BRANCH" == "main" ]]; then
/opt/scripts/deploy.sh
fi
;;
pull_request)
ACTION=$(echo "$PAYLOAD" | jq -r '.action')
echo "Pull request $ACTION"
;;
ping)
echo "pong"
;;
esacGitHub includes X-Hub-Signature-256 for payload verification. Always validate this to prevent spoofed webhooks. The event type is in X-GitHub-Event header.
Alertmanager Webhooks
#!/usr/bin/env bash
set -euo pipefail
PAYLOAD=$(cat)
# Extract alert information
STATUS=$(echo "$PAYLOAD" | jq -r '.status')
ALERTS=$(echo "$PAYLOAD" | jq -r '.alerts[] | "(.labels.alertname): (.labels.severity)"')
if [[ "$STATUS" == "firing" ]]; then
echo "ALERT: $ALERTS" | mail -s "Alert Firing" ops@example.com
/opt/scripts/page-oncall.sh "$ALERTS"
elif [[ "$STATUS" == "resolved" ]]; then
echo "RESOLVED: $ALERTS" | mail -s "Alert Resolved" ops@example.com
fiAlertmanager sends JSON with alerts array. Each alert has labels, annotations, and status. Process the payload with jq to extract actionable information.
Security Considerations
Webhook handlers must validate incoming requests to prevent spoofing and injection attacks. Never trust webhook payloads without verification.
# Validate HMAC signature
verify_signature() {
local payload="$1"
local signature="$2"
local secret="$3"
local expected="sha256=$(echo -n "$payload" | openssl dgst -sha256 -hmac "$secret" | awk '{print $NF}')"
[[ "$signature" == "$expected" ]]
}
# Validate IP allowlist
is_allowed_ip() {
local ip="$1"
local allowlist=("1.2.3.4" "5.6.7.8")
for allowed in "${allowlist[@]}"; do
[[ "$ip" == "$allowed" ]] && return 0
done
return 1
}
# Sanitize input
sanitize() {
echo "$1" | tr -cd '[:alnum:]_\-\./'
}Always verify HMAC signatures. Use IP allowlists when possible. Sanitize all input before processing. Never execute webhook data as commands.
Production Patterns
#!/usr/bin/env bash
set -euo pipefail
# Lock file to prevent concurrent deployments
DEPLOY_LOCK="/tmp/deploy.lock"
deploy() {
if ! mkdir "$DEPLOY_LOCK" 2>/dev/null; then
echo "Deploy already in progress"
exit 0
fi
trap 'rmdir "$DEPLOY_LOCK"' EXIT
cd /opt/app
git pull origin main
npm ci --production
pm2 restart app
echo "Deploy complete at $(date)"
}
# Process webhook
PAYLOAD=$(cat)
EVENT=$(echo "$PAYLOAD" | jq -r '.ref // empty')
if [[ -n "$EVENT" ]]; then
deploy >> /var/log/deploy.log 2>&1
fiProduction webhook handlers should: 1) Validate signatures. 2) Prevent concurrent execution with lock files. 3) Log all activity. 4) Handle errors gracefully. 5) Respond quickly (process asynchronously).
Webhook providers (GitHub, Slack, etc.) expect a response within 10 seconds. If your processing takes longer, respond immediately and process asynchronously with a background job.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.