HookTestHookTest
← Back to Blog

GitHub Webhooks: Setup Guide & Common Patterns

GitHub webhooks let your server react to repository events in real time — pushes, pull requests, issues, releases, and more. Instead of polling the GitHub API for changes, your server gets notified the moment something happens.

Setting Up a GitHub Webhook

Go to your repository > Settings > Webhooks > Add webhook. You need three things:

  • Payload URL. The endpoint that will receive POST requests from GitHub. This must be publicly accessible — GitHub cannot reach localhost.
  • Content type. Choose application/json unless you have a reason to use form encoding. JSON is easier to parse and debug.
  • Secret. A shared secret string used to sign payloads. Always set this. Without it, anyone who discovers your endpoint URL can send fake events.

Choosing the Right Events

GitHub offers over 30 event types. Subscribing to "everything" is wasteful and creates unnecessary load. Pick only the events you need:

  • push — triggered on every push to any branch. The most common trigger for CI/CD pipelines and auto-deployments.
  • pull_request — fires on PR open, close, merge, synchronize (new commits pushed), and label changes. Useful for running tests, posting review comments, or triggering preview deployments.
  • release — fires when a release is published. Good for triggering production deployments or publishing packages.
  • issues — fires on issue open/close/edit. Useful for Slack notifications or project management integrations.

Verifying the Payload Signature

GitHub signs every webhook payload with your secret using HMAC-SHA256. The signature is sent in the X-Hub-Signature-256 header. Always verify it:

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)
  );
}

Use timingSafeEqual to prevent timing attacks. A naive string comparison leaks information about which bytes matched.

Common Patterns

Auto-deploy on push to main. Listen for push events, check that ref equals refs/heads/main, then trigger your deployment script. This is how most simple CI/CD pipelines work before you adopt GitHub Actions.

PR preview environments. On pull_request.opened and pull_request.synchronize, deploy a preview build and post the URL as a PR comment using the GitHub API.

Slack notifications. Forward selected events (releases, issue assignments, failed checks) to a Slack incoming webhook. Filter by action type so you do not flood the channel.

Debugging GitHub Webhooks

GitHub shows recent deliveries under Settings > Webhooks > Recent Deliveries. You can see the request body, response, and status code. But this only works after deployment.

During development, you need a public URL before your server is deployed. HookTest gives you one instantly — point your GitHub webhook at a HookTest bin URL to see exactly what GitHub sends. You can inspect headers, payload structure, and event types before writing any handler code. Once you understand the payload shape, building the handler is straightforward.