Webhooks

Webhooks send HTTP POST requests to your server when events happen in your repositories. Every payload is signed with HMAC-SHA256 so you can verify authenticity.

Setting Up a Webhook

Create a webhook from your repository settings or via the API:

Create via API
curl -X POST https://api.gitforge.dev/api/repos/REPO_ID/webhooks \
  -H "Authorization: Bearer gf_YOUR_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/gitforge",
    "secret": "your_webhook_secret",
    "events": ["push", "pull_request"],
    "active": true
  }'

The secret is used to generate HMAC signatures. Store it securely and never commit it to version control.

Event Types

EventDescription
pushA push to a branch or tag.
pull_requestA pull request is opened, updated, closed, or merged.
pull_request_reviewA review is submitted on a pull request.
branch_createA branch is created.
branch_deleteA branch is deleted.
tag_createA tag is created.
repositoryA repository is created, deleted, or updated.
memberA collaborator is added or removed.

Use "events": ["*"] to subscribe to all events.

Payload Format

All payloads are JSON. The event type is included in the X-GitForge-Event header.

Example push payload
{
  "event": "push",
  "ref": "refs/heads/main",
  "before": "abc1234...",
  "after": "def5678...",
  "commits": [
    {
      "id": "def5678...",
      "message": "Add new feature",
      "author": {
        "name": "Jane Doe",
        "email": "[email protected]"
      },
      "timestamp": "2026-03-28T12:00:00Z",
      "added": ["src/feature.ts"],
      "modified": [],
      "removed": []
    }
  ],
  "pusher": {
    "name": "Jane Doe",
    "email": "[email protected]"
  },
  "repository": {
    "id": "repo-uuid",
    "name": "my-project",
    "full_name": "my-org/my-project"
  }
}

Headers

X-GitForge-EventEvent type (e.g. push, pull_request)
X-GitForge-DeliveryUnique delivery UUID
X-GitForge-Signature-256HMAC-SHA256 signature of the body
Content-Typeapplication/json

Verifying Signatures

Every webhook request includes an X-GitForge-Signature-256 header containing a hex-encoded HMAC-SHA256 digest of the request body, prefixed with sha256=.

Node.js verification
import crypto from "node:crypto";

function verifyWebhook(body: string, signature: string, secret: string): boolean {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(body, "utf-8")
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  );
}

// In your handler:
const sig = req.headers["x-gitforge-signature-256"];
if (!verifyWebhook(rawBody, sig, process.env.WEBHOOK_SECRET!)) {
  return res.status(401).send("Invalid signature");
}
Python verification
import hmac
import hashlib

def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode("utf-8"),
        body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

# In your handler:
sig = request.headers.get("X-GitForge-Signature-256", "")
if not verify_webhook(request.data, sig, os.environ["WEBHOOK_SECRET"]):
    abort(401, "Invalid signature")
Go verification
import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

func VerifyWebhook(body []byte, signature, secret string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(body)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(signature), []byte(expected))
}

SDK Webhook Helpers

All official SDKs include webhook verification helpers:

TypeScript SDK
import { GitForge } from "@gitforge/sdk";

const gf = new GitForge({ webhookSecret: "your_secret" });

// Returns typed event object or throws on invalid signature
const event = gf.webhooks.verify(rawBody, headers);
console.log(event.type); // "push"
console.log(event.payload); // typed payload
Python SDK
from gitforge import GitForge

gf = GitForge(webhook_secret="your_secret")

event = gf.webhooks.verify(request.data, request.headers)
print(event.type)    # "push"
print(event.payload) # typed payload
Delivery & Retries
  • Webhooks are delivered within seconds of the event.
  • Your server must respond with a 2xx status within 10 seconds.
  • Failed deliveries are retried up to 3 times with exponential backoff (10s, 60s, 300s).
  • Webhooks that fail consistently for 7 days are automatically disabled.
  • Delivery history is available in your repository settings for the last 30 days.
Related
Webhooks | GitForge