# How to Set Up and Authenticate GitHub Webhooks Using HMAC-SHA256 in Claude-Code-Telegram

> Securely authenticate GitHub webhooks using HMAC-SHA256. Learn to set up your `GITHUB_WEBHOOK_SECRET` and verify the `X-Hub-Signature-256` header in your FastAPI server.

- Repository: [Richard A/claude-code-telegram](https://github.com/richardatct/claude-code-telegram)
- Tags: how-to-guide
- Published: 2026-02-19

---

**Configure the `GITHUB_WEBHOOK_SECRET` environment variable and verify the `X-Hub-Signature-256` header in your FastAPI server to securely authenticate incoming GitHub webhooks.**

The Claude-Code-Telegram repository provides a FastAPI-based webhook server that ingests GitHub events securely using HMAC-SHA256 signature verification. This implementation ensures that only payloads signed with your shared secret are processed, preventing unauthorized requests from triggering your automation pipeline.

## Configure the GitHub Webhook Secret

The server reads the shared secret from the **`GITHUB_WEBHOOK_SECRET`** environment variable, which is exposed through the Pydantic `Settings` model in [`src/config/settings.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/config/settings.py). This secret must match the value configured in your GitHub repository settings.

Add the secret to your environment file:

```bash

# .env

GITHUB_WEBHOOK_SECRET=your_generated_secret_here

```

The application accesses this value via `Settings.github_webhook_secret` at runtime. Keep this value confidential and never commit it to version control.

## Register the Webhook on GitHub

Navigate to your repository’s **Settings → Webhooks** and click **Add webhook**:

1. **Payload URL**: `https://<your-host>/webhooks/github`
2. **Content type**: Select **application/json**
3. **Secret**: Paste the identical value stored in `GITHUB_WEBHOOK_SECRET`
4. **Events**: Choose **Just the push event** (or select specific events based on your automation needs)
5. Click **Add webhook**

GitHub will now sign every payload using HMAC-SHA256 with your secret before transmitting it to your server.

## Verify Signatures Using HMAC-SHA256

The verification flow occurs in the FastAPI endpoint defined in [`src/api/server.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/api/server.py).

### The Endpoint Handler

When GitHub delivers a webhook, it sends a `POST` request to `/webhooks/{provider}`. The handler extracts the **`X-Hub-Signature-256`** header and the raw request body, then delegates verification to the authentication module.

### Signature Validation Logic

In [`src/api/auth.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/api/auth.py), the `verify_github_signature()` function reproduces the HMAC digest and performs a constant-time comparison:

```python

# src/api/auth.py (lines 34-41)

expected_signature = (
    "sha256="
    + hmac.new(secret.encode("utf-8"), payload_body, hashlib.sha256).hexdigest()
)
return hmac.compare_digest(expected_signature, signature_header)

```

The function returns `True` only if the computed signature matches the header value exactly. The use of `hmac.compare_digest()` prevents timing attacks during the comparison.

If verification fails, the server returns **401 Unauthorized** and logs a warning message. If successful, the payload proceeds to processing.

## Process Verified Events

After successful authentication, the server:

- Parses the JSON payload (with fallback to raw body on parsing errors)
- Checks the SQLite `webhook_events` table in [`src/storage/database.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/storage/database.py) to detect and skip duplicate deliveries
- Publishes a `WebhookEvent` to the internal `EventBus` defined in [`src/events/bus.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/events/bus.py) for downstream handling by Claude agents

The deduplication mechanism ensures that retried webhook deliveries do not trigger duplicate automation runs.

## Test the Webhook Integration

Start the API server using `run_api_server()` from [`src/api/server.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/api/server.py), which launches Uvicorn on the port specified by `settings.api_server_port`.

Test your configuration locally using `curl` to generate a valid signature:

```bash
payload='{"test":"data"}'
secret=$GITHUB_WEBHOOK_SECRET
sig=$(echo -n "$payload" | openssl dgst -sha256 -hmac "$secret" -binary | xxd -p)

curl -X POST "http://localhost:8080/webhooks/github" \
  -H "Content-Type: application/json" \
  -H "X-Hub-Signature-256: sha256=$sig" \
  -d "$payload"

```

A correctly signed request returns `{"status":"accepted"}` with HTTP 200. Requests with missing or invalid signatures return **401 Unauthorized**.

## Summary

- Store your GitHub webhook secret in the **`GITHUB_WEBHOOK_SECRET`** environment variable to configure `Settings.github_webhook_secret` in [`src/config/settings.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/config/settings.py).
- Register the webhook URL **`/webhooks/github`** in your GitHub repository settings with content type **application/json**.
- The FastAPI endpoint in **[`src/api/server.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/api/server.py)** validates the **`X-Hub-Signature-256`** header using **`verify_github_signature()`** from **[`src/api/auth.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/api/auth.py)**.
- Invalid signatures trigger an immediate **401 Unauthorized** response without further processing.
- Verified events are deduplicated via SQLite and published to the **`EventBus`** for asynchronous handling by your automation agents.

## Frequently Asked Questions

### Where is the GitHub webhook secret configured in the codebase?

The secret is defined in the environment variable `GITHUB_WEBHOOK_SECRET` and loaded through the `Settings` class in [`src/config/settings.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/config/settings.py) as `github_webhook_secret`. The application reads this value at startup to use during signature verification.

### Which HTTP header contains the HMAC-SHA256 signature from GitHub?

GitHub sends the signature in the **`X-Hub-Signature-256`** header with the format `sha256=<hex_digest>`. The FastAPI endpoint extracts this value as `x_hub_signature_256` and passes it to `verify_github_signature()` along with the raw request body and configured secret.

### How does the server prevent processing duplicate webhooks?

The server stores a deduplication record in the SQLite `webhook_events` table via [`src/storage/database.py`](https://github.com/RichardAtCT/claude-code-telegram/blob/main/src/storage/database.py) immediately after successful verification. Subsequent deliveries with identical identifiers are detected and ignored, ensuring idempotent processing even when GitHub retries failed deliveries.

### What happens if signature verification fails?

When `verify_github_signature()` returns `False` or the header is missing, the endpoint returns **401 Unauthorized** and logs a security warning. The request body is not parsed, no database records are created, and no events are published to the `EventBus`, protecting your automation from spoofed or tampered payloads.