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-Signatureheader? - Why is my Gitea webhook signature verification failing?
- How do I handle
push,pull_request,issues,issue_comment,create,deleteorreleaseevents from Gitea? - Why do Gitea webhooks sent as
application/x-www-form-urlencodedfail 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).
| Event | Description |
|---|---|
push | Commits pushed to a branch or tag |
pull_request | Pull request opened, closed, reopened, edited, deleted |
issues | Issue opened, closed, reopened, edited, deleted |
issue_comment | Comment on an issue |
create | Branch or tag created |
delete | Branch or tag deleted |
release | Release 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
| Header | Description |
|---|---|
X-Gitea-Signature | Hex HMAC-SHA256 of the JSON payload, the raw body for POST + JSON (empty if no secret) |
X-Hub-Signature-256 | Same digest as sha256=<hex> (GitHub-compatible) |
X-Gitea-Event | Event name (push, issues, ...) |
X-Gitea-Event-Type | Specific trigger, e.g. issue_assign |
X-Gitea-Delivery | Unique 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
- references/overview.md - Gitea webhook concepts and events
- references/setup.md - Configuration guide
- references/verification.md - Signature verification details