Integrations 8 min read

GitHub Webhook Integration: Automating Your CI/CD Pipeline

Learn how to use GitHub webhooks to trigger builds, run tests, and deploy code automatically. A practical guide with real-world examples for teams of any size.

H

HookWatch Team

February 8, 2026

GitHub webhooks are one of the most powerful tools for automating your development workflow. Every push, pull request, issue, and release can trigger actions in your infrastructure—without polling the GitHub API.

Why GitHub Webhooks?

GitHub webhooks let your systems react instantly to repository events:

  • CI/CD triggers: Start builds and deployments on every push
  • Code review automation: Assign reviewers, run linters, post status checks
  • Issue tracking: Sync GitHub issues with your project management tool
  • Release management: Publish packages, update changelogs, notify stakeholders

Setting Up GitHub Webhooks

Via Repository Settings

  1. Go to your repository → Settings → Webhooks
  2. Click "Add webhook"
  3. Enter your payload URL
  4. Select content type (JSON recommended)
  5. Choose which events to listen for
  6. Click "Add webhook"

Via GitHub API

Bash
curl -X POST https://api.github.com/repos/owner/repo/hooks \
  -H "Authorization: token ghp_xxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "web",
    "active": true,
    "events": ["push", "pull_request"],
    "config": {
      "url": "https://hook.hookwatch.dev/wh/your-endpoint",
      "content_type": "json",
      "secret": "your-webhook-secret"
    }
  }'

Essential Events for CI/CD

Push Events

Trigger builds on every code push:

Javascript
app.post('/webhooks/github', async (req, res) => {
  const event = req.headers['x-github-event'];
  const payload = req.body;

  if (event === 'push') {
    const branch = payload.ref.replace('refs/heads/', '');
    const commits = payload.commits;

    console.log(`Push to ${branch}: ${commits.length} commits`);

    if (branch === 'main') {
      await triggerProductionDeploy(payload);
    } else if (branch === 'staging') {
      await triggerStagingDeploy(payload);
    } else {
      await triggerCIBuild(payload);
    }
  }

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

Pull Request Events

Run checks and post status updates:

Javascript
async function handlePullRequest(payload) {
  const { action, pull_request, repository } = payload;

  switch (action) {
    case 'opened':
    case 'synchronize':
      // Run tests on new/updated PRs
      await runTestSuite(pull_request.head.sha);

      // Post pending status
      await updateCommitStatus(
        repository.full_name,
        pull_request.head.sha,
        'pending',
        'Tests are running...'
      );
      break;

    case 'closed':
      if (pull_request.merged) {
        // Trigger deployment after merge
        await triggerDeploy(pull_request.base.ref);
      }
      // Clean up preview environments
      await destroyPreviewEnv(pull_request.number);
      break;
  }
}

Release Events

Automate package publishing:

Javascript
async function handleRelease(payload) {
  if (payload.action !== 'published') return;

  const { tag_name, prerelease } = payload.release;

  if (prerelease) {
    await publishToStaging(tag_name);
  } else {
    await publishToProduction(tag_name);
    await notifySlack(`Released ${tag_name}`);
    await updateChangelog(payload.release);
  }
}

Verifying GitHub Webhooks

GitHub signs every payload with your webhook secret using HMAC-SHA256:

Javascript
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', (req, res) => {
  const signature = req.headers['x-hub-signature-256'];

  if (!verifyGitHubSignature(req.rawBody, signature, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }

  // Process webhook...
});

Building a Deployment Pipeline

Here's a complete pipeline triggered by GitHub webhooks:

Code
[GitHub Push] → [HookWatch] → [Your Server]
                                    │
                              ┌─────┴──────┐
                              │ Run Tests   │
                              └─────┬──────┘
                                    │
                        ┌───────────┴───────────┐
                        │                       │
                   Tests Pass            Tests Fail
                        │                       │
                  Deploy App              Notify Team
                        │                       │
                  Update Status          Post PR Comment
Javascript
async function deployPipeline(payload) {
  const sha = payload.after;
  const repo = payload.repository.full_name;

  // Step 1: Run tests
  await updateStatus(repo, sha, 'pending', 'Running tests...');
  const testResult = await runTests(sha);

  if (!testResult.success) {
    await updateStatus(repo, sha, 'failure', 'Tests failed');
    await notifyTeam(testResult.errors);
    return;
  }

  // Step 2: Build
  await updateStatus(repo, sha, 'pending', 'Building...');
  const buildResult = await buildApp(sha);

  if (!buildResult.success) {
    await updateStatus(repo, sha, 'failure', 'Build failed');
    return;
  }

  // Step 3: Deploy
  await updateStatus(repo, sha, 'pending', 'Deploying...');
  await deploy(buildResult.artifact);
  await updateStatus(repo, sha, 'success', 'Deployed!');
}

Common Pitfalls

Webhook Timeouts

GitHub expects a response within 10 seconds. For long-running CI tasks, acknowledge immediately and process asynchronously:

Javascript
app.post('/webhooks/github', async (req, res) => {
  // Acknowledge immediately
  res.status(200).json({ received: true });

  // Process in background
  processGitHubWebhook(req.body).catch(console.error);
});

Missing Events During Downtime

If your server is down during a deployment, you'll miss webhooks. Use HookWatch to buffer and retry automatically.

Branch Filtering

Don't trigger deployments for every branch:

Javascript
const deployableBranches = ['main', 'staging', 'production'];

if (!deployableBranches.includes(branch)) {
  console.log(`Skipping non-deployable branch: ${branch}`);
  return;
}

Using HookWatch with GitHub

Route your GitHub webhooks through HookWatch for reliability:

  1. Create a HookWatch endpoint for your repository
  2. Set the HookWatch URL as your GitHub webhook URL
  3. Configure HookWatch to forward to your CI/CD server
  4. Get automatic retries, logging, and alerting

Never miss a deployment trigger again—even during server maintenance.

Tags: githubci-cdautomationwebhooksdevops

Share this article

Ready to try HookWatch?

Start monitoring your webhooks in minutes. No credit card required.

Start Free Today