Documentation Menu

GitHub Webhook Integration

GitHub uses HMAC-SHA256 signatures in the X-Hub-Signature-256 header with a sha256= prefix you must strip before comparing.

Setting Up a GitHub Webhook

  1. 1Go to GitHub Repository → Settings → Webhooks → Add webhook
  2. 2Set Payload URL to your Hookmetry endpoint URL
  3. 3Content type: application/json
  4. 4Enter a Secret — generate one with: openssl rand -hex 32
  5. 5Select events — "Send me everything" or choose specific events
  6. 6In Hookmetry: create endpoint → Validation Type: GitHub → paste the same secret

Verify on Your Server (Node.js)

Strip the sha256= prefix from the header, then hash the raw Buffer — not parsed JSON.

const express = require('express');
const crypto  = require('crypto');
const app     = express();

app.post('/webhooks/github', express.raw({ type: 'application/json' }), (req, res) => {
  const sigHeader  = req.headers['x-hub-signature-256'];  // 'sha256=abc123...'
  const eventType  = req.headers['x-github-event'];       // 'push', 'pull_request', etc.

  if (!sigHeader) return res.status(400).send('Missing X-Hub-Signature-256');

  // Strip prefix and compare using timing-safe function
  const expected = 'sha256=' + crypto
    .createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET)
    .update(req.body)   // raw Buffer — must NOT parse JSON first
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(sigHeader), Buffer.from(expected))) {
    return res.status(401).send('Invalid signature');
  }

  const payload = JSON.parse(req.body);  // safe to parse now

  switch (eventType) {
    case 'ping':
      // Always handle ping — GitHub sends this when webhook is first created
      console.log('Webhook configured! Zen:', payload.zen);
      break;
    case 'push':
      const branch = payload.ref.replace('refs/heads/', '');
      console.log(`${payload.commits.length} commits pushed to ${branch}`);
      break;
    case 'pull_request':
      console.log(`PR #${payload.number} ${payload.action}: ${payload.pull_request.title}`);
      break;
    case 'release':
      console.log(`Release ${payload.release.tag_name} ${payload.action}`);
      break;
    default:
      console.log('Unhandled event:', eventType);
  }

  res.status(200).json({ received: true });
});

Common GitHub Event Types

X-GitHub-EventTrigger
pingSent when webhook is first created — must return 200
pushCommits pushed to any branch or tag
pull_requestPR opened, closed, merged, labeled, etc.
issuesIssue opened, closed, assigned, labeled
releaseRelease created, published, or edited
workflow_runGitHub Actions workflow completed

Testing Tip — Handle the Ping Event

GitHub sends a ping event immediately when you create a webhook. If your handler doesn't return 200 for ping, GitHub marks the webhook as failed and may auto-disable it. Always add a ping case to your switch statement.

Was this page helpful?

Your feedback helps us improve the docs.