Webhook GitHub¶
Overview¶
This recipe documents the GitHub webhook receiver in examples/http/webhook_github/.
It combines signature verification,
JSON validation,
event-type dispatch,
and structured response payloads.
The endpoint is anonymous by design,
but it is protected by HMAC-SHA256 verification using the X-Hub-Signature-256 header
and a shared secret from GITHUB_WEBHOOK_SECRET.
It handles push,
pull_request,
and issues events with dedicated handlers,
then falls back for unknown events.
When to Use¶
- You need to receive GitHub events securely in Azure Functions.
- You want a clean event-dispatch pattern for multiple webhook event types.
- You need a reference for rejecting invalid signatures and malformed JSON.
Architecture¶
+-----------------+ POST /api/github/webhook +-------------------------------+
| GitHub Webhooks | ---------------------------> | github_webhook(req) |
+--------+--------+ +---------------+---------------+
| |
| X-Hub-Signature-256 | verify with secret
v v
HMAC SHA256 signature check +-----------------------------+
| _is_signature_valid(...) |
+---------------+-------------+
|
v
+-----------------------------+
| Event dispatch by header |
| push / pull_request /issues |
+---------------+-------------+
|
v
+-----------------------------+
| JSON response to caller |
+-----------------------------+
Prerequisites¶
- Python 3.10+
- Azure Functions Core Tools v4
azure-functionspackage fromrequirements.txtGITHUB_WEBHOOK_SECRETconfigured in environment/local settings- Ability to compute test signatures for local webhook replay
Project Structure¶
examples/http/webhook_github/
├── function_app.py
├── host.json
├── local.settings.json.example
├── requirements.txt
└── README.md
Implementation¶
The function uses helper methods for consistency:
_json_responsestandardizes JSON serialization and content type._is_signature_validverifies request authenticity._handle_push,_handle_pull_request, and_handle_issuesmap event payloads to outputs.
Signature verification from function_app.py:
def _is_signature_valid(signature_header: str | None, payload: bytes, secret: str) -> bool:
if not signature_header or not signature_header.startswith("sha256="):
return False
expected = (
"sha256="
+ hmac.new(
key=secret.encode("utf-8"),
msg=payload,
digestmod=hashlib.sha256,
).hexdigest()
)
return hmac.compare_digest(expected, signature_header)
Main route and validation sequence:
@app.route(route="github/webhook", methods=["POST"], auth_level=func.AuthLevel.ANONYMOUS)
def github_webhook(req: func.HttpRequest) -> func.HttpResponse:
secret = os.environ.get("GITHUB_WEBHOOK_SECRET", "")
if not secret:
return _json_response({"error": "Server is not configured with GITHUB_WEBHOOK_SECRET."}, status_code=500)
signature = req.headers.get("X-Hub-Signature-256")
raw_body = req.get_body()
if not _is_signature_valid(signature, raw_body, secret):
return _json_response({"error": "Invalid or missing webhook signature."}, status_code=401)
Event dispatch behavior:
event_type = req.headers.get("X-GitHub-Event", "")
if event_type == "push":
return _json_response(_handle_push(payload), status_code=200)
if event_type == "pull_request":
return _json_response(_handle_pull_request(payload), status_code=200)
if event_type == "issues":
return _json_response(_handle_issues(payload), status_code=200)
Status code intent:
500when the server is misconfigured (missing secret).401when signature is invalid or absent.400when JSON is malformed or not an object.200when event is accepted and processed, including unhandled event types with a generic message.
Run Locally¶
Expected Output¶
POST /api/github/webhook with valid signature and X-GitHub-Event: push
-> 200 OK
{
"event": "push",
"repository": "octo/repo",
"ref": "refs/heads/main",
"commits": 1,
"message": "Processed push with 1 commit(s)."
}
POST with invalid signature -> 401
{"error":"Invalid or missing webhook signature."}
Production Considerations¶
- Scaling: Webhooks can burst during high repo activity; keep handlers fast and offload heavy work to queues.
- Retries: GitHub retries failed deliveries, so return non-2xx only for true failures and ensure deterministic handling.
- Idempotency: Use
X-GitHub-Deliveryor payload identifiers to deduplicate repeated deliveries. - Observability: Log event type, delivery ID, repository, and outcome with structured fields for triage.
- Security: Rotate webhook secrets, enforce TLS end to end, and never trust payloads before signature validation.