404 — Receiving Webhooks in Python

Intermediate

Close the API loop by receiving inbound HTTP payloads from external services — without a web framework. Build a webhook server using Python's stdlib http.server, read POST bodies from the raw socket stream, validate GitHub-style HMAC-SHA256 signatures with hmac.compare_digest, and respond with correct HTTP status codes (200, 400, 401, 411). The lesson closes with webhook_receiver.py: a self-contained server that loads the shared secret from .env, routes events by type, and emits structured log output — all patterns used daily in DevOps event pipelines.

Learning Objectives

1
Subclass BaseHTTPRequestHandler to handle POST requests with do_POST()
2
Read the raw POST body from self.rfile using the Content-Length header
3
Parse the JSON payload with json.loads() and return 400 on invalid input
4
Compute an HMAC-SHA256 signature with hmac.new() and inspect its hexdigest format
5
Understand why hmac.compare_digest() is required instead of == for signature comparison
6
Validate a GitHub-style X-Hub-Signature-256 header and return 401 on failure
7
Test the server with curl — valid signature, wrong signature, and missing signature
8
Build webhook_receiver.py: a complete server with argparse, structured logging, and event routing
Step 1

Set up the lesson directory and add WEBHOOK_SECRET to .env

Create the lesson-404 working directory, copy the .env and config files from lesson 403, add WEBHOOK_SECRET to .env, and confirm that http.server, hmac, and hashlib are available in the Python stdlib — no pip install needed.

Commands to Run

mkdir -p ~/devops-python/lesson-404
cd ~/devops-python/lesson-404
source ~/devops-python/lesson-101/devops-env/bin/activate
cp ~/devops-python/lesson-403/.env ~/devops-python/lesson-404/.env
cp ~/devops-python/lesson-403/config.yaml ~/devops-python/lesson-404/config.yaml
cp ~/devops-python/lesson-403/.gitignore ~/devops-python/lesson-404/.gitignore
echo 'WEBHOOK_SECRET=dev-secret-12345' >> ~/devops-python/lesson-404/.env
grep 'WEBHOOK_SECRET' ~/devops-python/lesson-404/.env
python3 -c "from http.server import BaseHTTPRequestHandler, HTTPServer; import hmac, hashlib; print('stdlib imports OK'); print('  http.server.BaseHTTPRequestHandler'); print('  http.server.HTTPServer'); print('  hmac.new, hmac.compare_digest'); print('  hashlib.sha256')"

What This Does

The three stdlib modules used in this lesson require no pip install. http.server provides BaseHTTPRequestHandler — the class you subclass to define how each HTTP method is handled — and HTTPServer — the class that binds a port and dispatches incoming connections to your handler. hmac provides hmac.new(key, msg, digestmod) to compute a keyed hash and hmac.compare_digest(a, b) to compare signatures without timing leaks. hashlib provides hashlib.sha256 as the digest algorithm argument.

WEBHOOK_SECRET=dev-secret-12345 is a placeholder secret for local development.

In production the value would be a random string of at least 32 characters set by the service that sends the webhook.

Expected Outcome

The grep command prints one line: WEBHOOK_SECRET=dev-secret-12345.

The python3 import check prints stdlib imports OK followed by the four symbol names — no ModuleNotFoundError.

Pro Tips

  • 1
    A shared webhook secret works like a password that both sender and receiver know. The sender computes `HMAC-SHA256(secret, payload)` and includes the result in the request header. The receiver recomputes the same value and compares — if they match, the payload is authentic.
  • 2
    Never commit WEBHOOK_SECRET to version control. Add `.env` to `.gitignore` (already done from lesson 403) and keep the secret in your environment. Rotate it any time you suspect it has been exposed.

Common Mistakes to Avoid

  • ⚠️Adding WEBHOOK_SECRET before copying the .env from lesson 403 — the cp command would overwrite it. Copy first, then append with >>.
  • ⚠️Using echo with a single > instead of >> — `echo '...' > .env` would replace the entire file instead of appending. Use >> to add the new variable without losing GITHUB_TOKEN.
Was this step helpful?

All Steps (0 / 8 completed)