can1357/oh-my-pi · warning · HTTPException

invalid signature

Error message

invalid signature

What it means

POST /webhook/github verifies the X-Hub-Signature-256 HMAC-SHA256 header against GITHUB_WEBHOOK_SECRET using github_events.verify_signature; a mismatch raises HTTPException 401 'invalid signature' so forged or misconfigured deliveries never enter the queue.

Source

Thrown at python/robomp/src/server.py:342

            raise HTTPException(503, "not initialized")
        return {"status": "ready"}

    @app.post("/webhook/github")
    async def webhook(
        request: Request,
        x_github_event: str = Header(..., alias="X-GitHub-Event"),
        x_github_delivery: str = Header(..., alias="X-GitHub-Delivery"),
        x_hub_signature_256: str | None = Header(None, alias="X-Hub-Signature-256"),
    ) -> JSONResponse:
        bag = request.app.state.bag
        cfg: Settings = bag["settings"]
        body = await request.body()
        if not github_events.verify_signature(
            cfg.github_webhook_secret.get_secret_value(),
            body,
            x_hub_signature_256,
        ):
            raise HTTPException(status.HTTP_401_UNAUTHORIZED, "invalid signature")
        try:
            payload = await request.json()
        except Exception as exc:
            raise HTTPException(status.HTTP_400_BAD_REQUEST, f"invalid json: {exc}") from exc

        db: Database = bag["db"]
        issue_cache: _IssueBrowseCache = bag["issue_browse_cache"]
        await issue_cache.apply_webhook(
            event_type=x_github_event,
            payload=payload,
            allowlist=cfg.repo_allowlist,
        )
        # Keep the local search index fresh from every delivery that carries an
        # issue/PR object — including ones the router will skip.
        if x_github_event in ("issues", "issue_comment") or x_github_event.startswith("pull_request"):
            repo_full = str((payload.get("repository") or {}).get("full_name") or "")
            if repo_full and repo_full in cfg.repo_allowlist:
                try:

View on GitHub (pinned to 9690622007)

Solutions

  1. Confirm GITHUB_WEBHOOK_SECRET in robomp exactly matches the webhook secret configured in GitHub settings (no quotes/whitespace).
  2. After changing the secret, restart the orchestrator so Settings reload.
  3. Ensure any reverse proxy forwards the raw body unmodified and passes through X-Hub-Signature-256.
  4. Verify with a redelivery from the GitHub webhook 'Recent Deliveries' pane after fixing.

Example fix

// before (.env)
GITHUB_WEBHOOK_SECRET="my secret "  # stray quotes/trailing space
// after (.env)
GITHUB_WEBHOOK_SECRET=my-secret
Defensive patterns

Strategy: validation

Validate before calling

import hmac, hashlib
sig = 'sha256=' + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, headers['X-Hub-Signature-256']):
    raise ValueError('local signature check failed — secret or body mismatch')

Try / catch

try:
    resp = httpx.post(url, content=body, headers=headers)
except httpx.HTTPStatusError as e:
    if e.response.status_code == 401:
        ...  # re-check GITHUB_WEBHOOK_SECRET on both sides before retrying

Prevention

When it happens

Trigger: Webhook delivery whose HMAC does not match the configured secret — wrong/rotated GITHUB_WEBHOOK_SECRET on the robomp side, secret not updated in the GitHub repo/app webhook settings, a proxy re-signing or mangling the raw body, or replaying a captured request.

Common situations: Secret updated in GitHub but not in robomp's .env (or vice versa); trailing whitespace/quotes in the secret value; an intermediary (nginx, ngrok, tunnel) altering the payload before hashing; pointing two environments at one webhook.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/1626f07ae76cf534. Report an issue: GitHub.