Early access — use code HOOKSENSEWELCOME for 1 month free
11 min read

GitHub Webhooks: Complete Setup Guide

Set up GitHub webhooks from scratch. Covers event types, payload structure, security configuration, and real-world automation examples.

GitHubWebhooksGuide
O

Ozer

Developer & Founder of HookSense

GitHub webhooks let you build automated workflows that respond to repository events in real time. When someone pushes code, opens a pull request, creates an issue, or performs any of dozens of other actions, GitHub sends an HTTP POST request to your server with details about what happened. This is the foundation of CI/CD pipelines, chat notifications, deployment triggers, and countless other developer tools.

This guide walks you through setting up GitHub webhooks from scratch, covers the most important event types, shows you how to parse payloads, and shares real-world automation patterns.

Setting Up a GitHub Webhook

Repository-Level Webhooks

To create a webhook for a specific repository:

  1. Navigate to your repository on GitHub.
  2. Click Settings → Webhooks → Add webhook.
  3. Enter your Payload URL — the endpoint that will receive events.
  4. Set Content type to application/json (recommended over form-encoded).
  5. Enter a Secret — a random string used for HMAC signature verification.
  6. Choose which events to receive: "Just the push event," "Send me everything," or select individual events.
  7. Click Add webhook.

GitHub immediately sends a ping event to verify your endpoint is reachable. If your endpoint returns a 2xx status, the webhook shows a green checkmark.

Organization-Level Webhooks

Organization webhooks work the same way but receive events from all repositories in the organization. Navigate to Organization Settings → Webhooks to set them up. This is useful for company-wide CI/CD or audit logging.

Using HookSense During Setup

If you do not have a server ready yet, you can use HookSense to capture and inspect GitHub webhooks immediately:

  1. Create a free endpoint at hooksense.com.
  2. Use the HookSense URL as your Payload URL in GitHub settings.
  3. Trigger an event (e.g., push a commit) and see the payload appear in real time.
  4. When your server is ready, use the CLI to forward events: npx hooksense listen -p 3000

Key GitHub Webhook Events

GitHub supports over 40 event types. Here are the ones developers use most frequently:

push

Triggered when commits are pushed to a branch. This is the most common event and powers most CI/CD pipelines.

{
  "ref": "refs/heads/main",
  "before": "abc123...",
  "after": "def456...",
  "commits": [
    {
      "id": "def456...",
      "message": "Fix login bug",
      "author": { "name": "Ozer", "email": "[email protected]" },
      "added": ["src/auth.ts"],
      "modified": ["src/login.ts"],
      "removed": []
    }
  ],
  "repository": { "full_name": "ozers/myapp" },
  "pusher": { "name": "ozers" }
}

pull_request

Triggered when a pull request is opened, closed, merged, labeled, or updated. The action field tells you what happened:

{
  "action": "opened",
  "number": 42,
  "pull_request": {
    "title": "Add dark mode support",
    "user": { "login": "ozers" },
    "head": { "ref": "feature/dark-mode" },
    "base": { "ref": "main" },
    "merged": false
  }
}

issues

Triggered when an issue is opened, closed, labeled, assigned, or edited. Useful for project management integrations and chat notifications.

release

Triggered when a release is published, edited, or deleted. Common trigger for deployment pipelines and changelog generation.

workflow_run

Triggered when a GitHub Actions workflow is requested, in progress, or completed. Useful for chaining workflows or external monitoring.

star and fork

Triggered when someone stars or forks your repository. Useful for growth tracking and community notifications.

Payload Structure

Every GitHub webhook request includes these headers:

HeaderDescription
X-GitHub-EventThe event type (e.g., push, pull_request)
X-GitHub-DeliveryA unique GUID for this delivery
X-Hub-Signature-256HMAC-SHA256 signature for verification
Content-Typeapplication/json
User-AgentGitHub-Hookshot/...

The body contains the event-specific payload as JSON. Payloads can be large — a push event with many commits can be several KB.

Securing Your Endpoint

Always verify the X-Hub-Signature-256 header to ensure requests genuinely come from GitHub:

const crypto = require('crypto');

function verifyGitHubSignature(payload, signature, secret) {
  const expected = 'sha256=' +
    crypto.createHmac('sha256', secret)
      .update(payload)
      .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

app.post('/webhooks/github', express.raw({ type: '*/*' }), (req, res) => {
  const sig = req.headers['x-hub-signature-256'];

  if (!sig || !verifyGitHubSignature(req.body, sig, WEBHOOK_SECRET)) {
    return res.status(401).send('Unauthorized');
  }

  const event = req.headers['x-github-event'];
  const payload = JSON.parse(req.body);

  // Route by event type
  handleEvent(event, payload);
  res.status(200).send('OK');
});

Use crypto.timingSafeEqual() to prevent timing-based attacks. Never compare signatures with ===.

Real-World Automation Patterns

Auto-Deploy on Push to Main

if (event === 'push' && payload.ref === 'refs/heads/main') {
  exec('cd /app && git pull && npm run build && pm2 restart app');
}

Slack Notification on New PR

if (event === 'pull_request' && payload.action === 'opened') {
  await slack.post('#dev', {
    text: `New PR: ${payload.pull_request.title} by ${payload.pull_request.user.login}`,
    url: payload.pull_request.html_url
  });
}

Auto-Label Issues

if (event === 'issues' && payload.action === 'opened') {
  const title = payload.issue.title.toLowerCase();
  const labels = [];
  if (title.includes('bug')) labels.push('bug');
  if (title.includes('feature')) labels.push('enhancement');
  await github.issues.addLabels({
    owner, repo,
    issue_number: payload.issue.number,
    labels
  });
}

Trigger External CI on Pull Request

if (event === 'pull_request' && ['opened', 'synchronize'].includes(payload.action)) {
  await triggerCIPipeline({
    branch: payload.pull_request.head.ref,
    sha: payload.pull_request.head.sha,
    prNumber: payload.number
  });
}

Debugging GitHub Webhooks

GitHub provides a "Recent Deliveries" tab in webhook settings where you can see every delivery attempt, the response code, and resend events. However, this interface is limited — you cannot search, filter, or forward events locally.

For a better debugging experience, point your GitHub webhook at a HookSense endpoint. You get real-time inspection, full payload viewing with syntax highlighting, signature verification badges, and the ability to replay any event. Use the CLI to forward events to your local server while developing.

Common Gotchas

  • Payload size limits: GitHub truncates payloads larger than 25 MB. For push events with many commits, only the first 20 commits are included.
  • Timeouts: GitHub expects a response within 10 seconds. Use a queue for heavy processing.
  • Branch deletion: A push event with deleted: true has after set to all zeros. Do not try to fetch the commit.
  • Duplicate deliveries: GitHub may retry if your response is slow. Always handle idempotency.
  • IP allowlisting: GitHub publishes webhook IP ranges at api.github.com/meta under the hooks key.

Summary

GitHub webhooks are straightforward to set up but require careful attention to security and error handling. Always verify signatures, handle events idempotently, respond quickly, and use a tool like HookSense for development and debugging. With these practices in place, you can build reliable automations that respond to your repository events in real time.

Related

Try HookSense Free

Inspect, debug, and replay webhooks in real-time. No credit card required.

Get Started Free