Agent skill

Gitea Webhooks Skill

Receive and verify Gitea webhooks. Use when setting up Gitea webhook handlers, debugging X-Gitea-Signature verification, or handling self-hosted Git events like push, pull_request, issues, issue_comment, create, delete, or release.

Install this skill

npx skills add hookdeck/webhook-skills --skill gitea-webhooks


When to Use This Skill

  • How do I receive Gitea webhooks?
  • How do I verify the X-Gitea-Signature header?
  • Why is my Gitea webhook signature verification failing?
  • How do I handle push, pull_request, issues, issue_comment, create, delete or release events from Gitea?
  • Why do Gitea webhooks sent as application/x-www-form-urlencoded fail verification?

Verification (core)

Gitea signs the JSON payload with HMAC-SHA256 keyed on the webhook Secret. That payload is the raw request body only for POST + application/json, so configure the webhook that way. X-Gitea-Signature is the bare lowercase hex digest (no sha256= prefix). If no secret is configured, the header is still sent but empty, so reject an empty signature. Gitea also sends the GitHub-style X-Hub-Signature-256 (sha256=<hex>) with the same digest; this skill verifies X-Gitea-Signature.

Node:

const crypto = require('crypto');

function verify(rawBody, signature, secret) {
  if (!signature || !secret) return false; // empty header = no secret configured
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  try {
    return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
  } catch {
    return false; // length mismatch
  }
}

Python:

import hmac, hashlib

def verify(raw_body: bytes, signature: str, secret: str) -> bool:
    if not signature or not secret:
        return False
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(signature.encode(), expected.encode())

Use POST with application/json, read the raw bytes, and parse only after verifying. Gitea can also send form-encoded deliveries (payload=<url-encoded JSON>) or GET deliveries (payload in the query string), but the signature still covers the JSON payload rather than the bytes you receive, so those never verify against the request body.

For complete handlers with route wiring, event dispatch, and tests, see:

Common Event Types

The event name is in the X-Gitea-Event header (not the body).

EventDescription
pushCommits pushed to a branch or tag
pull_requestPull request opened, closed, reopened, edited, deleted
issuesIssue opened, closed, reopened, edited, deleted
issue_commentComment on an issue
createBranch or tag created
deleteBranch or tag deleted
releaseRelease published, updated, deleted

Other events include fork, wiki, repository, package, status, workflow_run and workflow_job. X-Gitea-Event-Type carries the more specific trigger (e.g. issue_assign).

Full event reference: Gitea webhooks

Important Headers

HeaderDescription
X-Gitea-SignatureHex HMAC-SHA256 of the JSON payload, the raw body for POST + JSON (empty if no secret)
X-Hub-Signature-256Same digest as sha256=<hex> (GitHub-compatible)
X-Gitea-EventEvent name (push, issues, ...)
X-Gitea-Event-TypeSpecific trigger, e.g. issue_assign
X-Gitea-DeliveryUnique delivery UUID for this attempt

Environment Variables

GITEA_WEBHOOK_SECRET=your_webhook_secret   # The "Secret" field of the Gitea webhook

Local Development

# Start tunnel (no account needed)
npx hookdeck-cli listen 3000 gitea --path /webhooks/gitea

Reference Materials


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 5, 2026

View on GitHub →