GitHub Webhooks: Complete Guide with Event Examples

featured article thumbnail

GitHub webhooks are one of the most powerful tools for automating development workflows, enabling real-time notifications, and building integrations that respond to repository events. Whether you're building CI/CD pipelines, automating code reviews, managing issues, or monitoring security alerts, webhooks provide the event-driven foundation that keeps your team informed and your processes automated.

This comprehensive guide covers everything you need to know about GitHub webhooks—from basic setup to advanced automation patterns. We'll explore pull request workflows, issue management, CI/CD events, security alerts, and show you both custom implementation approaches and no-code alternatives using MagicBell workflows.

What Are GitHub Webhooks?

GitHub webhooks are HTTP callbacks that GitHub triggers when specific events occur in your repository. Instead of constantly polling GitHub's API to check for changes, webhooks push event data to your application in real time whenever something happens—like a pull request being opened, an issue being commented on, or a CI workflow completing.

Think of webhooks as GitHub's notification system for applications. When you configure a webhook, you're telling GitHub: "When X happens, send me the details at this URL." GitHub then delivers a POST request to your specified endpoint containing all relevant event data in JSON format.

Why Webhooks Matter

Webhooks enable event-driven architecture, where systems react to events as they happen rather than continuously checking for updates. This approach:

  • Reduces latency: Notifications arrive instantly when events occur
  • Saves resources: No need for constant polling or API rate limit consumption
  • Enables automation: Trigger workflows, notifications, and integrations automatically
  • Improves developer experience: Teams stay informed about repository activity in real time

GitHub supports 73+ webhook events covering everything from code changes to security alerts. This extensive event catalog makes webhooks incredibly versatile for building sophisticated automation workflows.

How GitHub Webhooks Work

Understanding the webhook flow helps you build more reliable integrations. Here's what happens when a GitHub event triggers a webhook:

The Webhook Lifecycle

  1. Event occurs: A developer opens a pull request, pushes code, or comments on an issue
  2. GitHub constructs payload: GitHub gathers all relevant event data into a JSON payload
  3. Signature generation: GitHub creates an HMAC signature using your webhook secret
  4. HTTP POST request: GitHub sends the payload to your configured webhook URL
  5. Your server receives: Your endpoint receives and validates the webhook
  6. Processing: Your application processes the event and takes action
  7. Response: Your server responds with a 2xx status code to acknowledge receipt

This entire process typically completes in milliseconds, enabling near-instant automation.

Webhook Payload Structure

Every GitHub webhook delivery includes these components:

Headers:

  • X-GitHub-Event: The event type (e.g., pull_request, issues, push)
  • X-GitHub-Delivery: Unique identifier for this webhook delivery
  • X-Hub-Signature-256: HMAC signature for payload verification
  • User-Agent: Always starts with GitHub-Hookshot/

Body: JSON payload containing:

  • action: What happened (e.g., opened, closed, synchronize)
  • Event-specific data (pull request details, issue information, commit data)
  • repository: Information about the repository where the event occurred
  • sender: The GitHub user who triggered the event

Understanding this structure is crucial for parsing events and building robust webhook handlers.

Setting Up GitHub Webhooks

You can configure webhooks at the repository or organization level through GitHub's web interface or API.

Repository Webhook Configuration

  1. Navigate to your repository on GitHub
  2. Go to SettingsWebhooksAdd webhook
  3. Configure the webhook:
    • Payload URL: Your endpoint that will receive webhook events
    • Content type: Select application/json
    • Secret: Generate a strong random string for signature verification
    • SSL verification: Leave enabled (always use HTTPS in production)
    • Events: Choose which events trigger this webhook

Event Selection Strategies

GitHub offers three options for event selection:

1. Just the push event (default)

  • Simplest option, only notified of code pushes
  • Good for deployment automation

2. Send me everything

  • Receive all 73+ event types
  • Useful during development to explore available events
  • Can generate high volume—filter events in your application

3. Let me select individual events

  • Recommended approach for production
  • Subscribe only to events you'll actually handle
  • Reduces unnecessary webhook deliveries

For most applications, selecting specific events is the best approach. You can always add more events later as your integration grows.

Webhook URL Requirements

Your webhook endpoint must:

  • Use HTTPS (not HTTP) for production webhooks
  • Respond within 10 seconds (GitHub's timeout)
  • Return a 2xx status code to indicate successful receipt
  • Be publicly accessible (GitHub can't reach localhost directly)

For local development, you'll need a tunneling tool to expose your local server—we'll cover this in the testing section.

Pull Request Webhook Events

Pull requests are the heart of collaborative development on GitHub. Automating PR workflows can dramatically improve code review efficiency and team communication.

Core Pull Request Events

GitHub provides several events for different stages of the PR lifecycle:

pull_request - The primary event with these actions:

  • opened: New PR created
  • closed: PR closed (check merged field to distinguish merged vs. closed)
  • reopened: Previously closed PR reopened
  • edited: PR title, description, or base branch changed
  • synchronize: New commits pushed to the PR branch
  • assigned: Reviewer or assignee added
  • labeled: Label added or removed
  • ready_for_review: Draft PR marked ready

pull_request_review - Code review events:

  • submitted: Review completed (approved, changes requested, or commented)
  • edited: Review comment edited
  • dismissed: Review dismissed

pull_request_review_comment - Inline code comments:

  • created: New comment on specific code lines
  • edited: Comment modified
  • deleted: Comment removed

Real-World PR Workflow Examples

Here's a practical example of handling PR events to automate reviewer notifications.

Scenario: When a PR is opened, notify the assigned reviewers via Slack and in-app notification.

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

  if (event === 'pull_request' && payload.action === 'opened') {
    const pr = payload.pull_request;

    // Extract PR details
    const prData = {
      title: pr.title,
      author: pr.user.login,
      url: pr.html_url,
      number: pr.number,
      repository: payload.repository.full_name
    };

    // Notify each requested reviewer
    for (const reviewer of pr.requested_reviewers || []) {
      await sendNotification({
        recipient: reviewer.login,
        title: `New PR: ${prData.title}`,
        message: `${prData.author} requests your review on PR #${prData.number}`,
        action_url: prData.url,
        category: 'code_review'
      });
    }

    console.log(`Notified ${pr.requested_reviewers?.length || 0} reviewers for PR #${pr.number}`);
  }

  res.status(200).send('Webhook received');
});

MagicBell Workflow Alternative

Instead of writing custom code, you can use MagicBell workflows to automate PR notifications without any backend changes:

{
  "key": "integration.github.pull_request.opened",
  "steps": [
    {
      "command": "broadcast",
      "input": {
        "title": "New PR: {{data.pull_request.title}}",
        "content": "{{data.pull_request.user.login}} requests your review",
        "action_url": "{{data.pull_request.html_url}}",
        "category": "code_review",
        "recipients": [
          {
            "external_id": "{{data.pull_request.requested_reviewers[0].login}}"
          }
        ]
      }
    }
  ]
}

This pull request workflow automatically executes when GitHub sends the pull_request.opened webhook to MagicBell. No server code required.

Advanced PR Automation Ideas

Merge conflict detection:
Monitor pull_request.synchronize events and check if mergeable is false. Notify the PR author that conflicts need resolution before merging can proceed.

Stale PR reminders:
Track when PRs remain in review status for too long. Use a scheduled job to query open PRs and send reminder notifications to reviewers after configurable time periods (e.g., 48 hours).

CI/CD integration:
Combine PR events with check run events to notify when tests pass or fail, enabling reviewers to know when a PR is actually ready for review.

Issue Webhook Events

GitHub Issues serve as the project management hub for many teams. Automating issue workflows keeps everyone aligned and ensures nothing falls through the cracks.

Core Issue Events

issues - The main event with these actions:

  • opened: New issue created
  • edited: Issue title or description changed
  • closed: Issue closed
  • reopened: Issue reopened
  • deleted: Issue deleted (available to org admins)
  • assigned: Assignee added
  • unassigned: Assignee removed
  • labeled: Label added
  • unlabeled: Label removed
  • locked: Issue locked (no more comments)
  • unlocked: Issue unlocked
  • transferred: Issue moved to another repository
  • pinned: Issue pinned to repository
  • unpinned: Issue unpinned

issue_comment - Comments on issues:

  • created: New comment added
  • edited: Comment edited
  • deleted: Comment deleted

These events also apply to pull requests (PRs are issues with code), so issue_comment fires for both issue comments and PR comments.

Practical Issue Automation

Scenario: When critical bugs are reported, immediately notify on-call engineers and create a Slack thread for coordination.

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

  if (event === 'issues' && payload.action === 'opened') {
    const issue = payload.issue;
    const labels = issue.labels.map(l => l.name);

    // Check if this is a critical bug
    if (labels.includes('bug') && labels.includes('critical')) {
      const issueData = {
        title: issue.title,
        reporter: issue.user.login,
        url: issue.html_url,
        number: issue.number,
        body: issue.body
      };

      // Alert on-call team
      await sendUrgentNotification({
        recipients: ['oncall-team'],
        title: `🚨 Critical Bug: ${issueData.title}`,
        message: `Reporter: ${issueData.reporter}\n\n${issueData.body.substring(0, 200)}...`,
        action_url: issueData.url,
        priority: 'urgent'
      });

      console.log(`Alerted on-call team for critical bug #${issue.number}`);
    }
  }

  res.status(200).send('Webhook received');
});

MagicBell Issue Workflow

The no-code approach using issue workflows:

{
  "key": "integration.github.issues.opened",
  "steps": [
    {
      "if": "{{data.issue.labels | map: 'name' | includes: 'bug'}}",
      "command": "broadcast",
      "input": {
        "title": "🐛 Bug Report: {{data.issue.title}}",
        "content": "{{data.issue.user.login}} reported a bug",
        "action_url": "{{data.issue.html_url}}",
        "category": "bug_report",
        "recipients": [
          {"external_id": "engineering-team"}
        ]
      }
    }
  ]
}

The if condition filters issues, only broadcasting when the bug label is present.

Issue Triage Automation

Auto-labeling based on content:
Parse issue body for keywords like "crash," "security," or "feature request" and automatically apply appropriate labels. This helps maintainers quickly categorize new issues.

Assignment routing:
When certain labels are applied, automatically assign issues to the appropriate team members or teams based on predefined rules.

Stale issue management:
Track issues that haven't been updated recently. After a configurable period (e.g., 30 days), add a "stale" label and post a comment asking if the issue is still relevant.

CI/CD and Workflow Events

GitHub Actions workflows and other CI/CD systems generate events that are crucial for keeping developers informed about build status, test results, and deployment progress.

Workflow Run Events

workflow_run - Triggered when GitHub Actions workflows execute:

  • completed: Workflow finished (check conclusion for success/failure)
  • requested: Workflow queued for execution
  • in_progress: Workflow actively running

workflow_job - Individual job-level events:

  • queued: Job added to queue
  • in_progress: Job started running
  • completed: Job finished

These events are especially valuable for monitoring CI/CD pipelines and notifying developers when their code passes or fails tests.

Check Run Events

check_run - Status checks from GitHub Apps and Actions:

  • created: New check initiated
  • completed: Check finished with conclusion (success, failure, cancelled)
  • rerequested: Check manually re-run

check_suite - Groups of checks:

  • completed: All checks in suite finished
  • requested: New check suite created

CI/CD Notification Example

Scenario: Notify developers when their commit fails CI, so they can fix issues immediately.

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

  if (event === 'workflow_run' && payload.action === 'completed') {
    const workflow = payload.workflow_run;

    // Only notify on failures
    if (workflow.conclusion === 'failure') {
      const commitAuthor = workflow.head_commit.author.name;
      const branch = workflow.head_branch;

      await sendNotification({
        recipient: commitAuthor,
        title: `❌ CI Failed: ${workflow.name}`,
        message: `Your commit on ${branch} failed CI checks`,
        action_url: workflow.html_url,
        priority: 'high',
        category: 'ci_failure'
      });

      console.log(`Notified ${commitAuthor} about CI failure on ${branch}`);
    }
  }

  res.status(200).send('Webhook received');
});

MagicBell CI/CD Workflow

Using workflow run events:

{
  "key": "integration.github.workflow_run.completed",
  "steps": [
    {
      "if": "{{data.workflow_run.conclusion == 'failure'}}",
      "command": "broadcast",
      "input": {
        "title": "CI Failed: {{data.workflow_run.name}}",
        "content": "Your commit on {{data.workflow_run.head_branch}} failed CI",
        "action_url": "{{data.workflow_run.html_url}}",
        "category": "ci_failure",
        "recipients": [
          {
            "external_id": "{{data.workflow_run.head_commit.author.name}}"
          }
        ]
      }
    }
  ]
}

Deployment Events

deployment - Deployments initiated:

  • created: New deployment started

deployment_status - Deployment progress:

  • created: Deployment status updated (pending, success, failure, error)

These events help teams track releases across environments (staging, production) and notify stakeholders when deployments complete.

Security and Alert Events

GitHub provides powerful security scanning features, and webhooks ensure your team responds quickly to security issues.

Security Event Types

dependabot_alert - Vulnerability alerts for dependencies:

  • created: New vulnerability discovered
  • dismissed: Alert dismissed by maintainer
  • fixed: Vulnerability patched
  • reintroduced: Previously fixed vulnerability reappeared

code_scanning_alert - Code security issues:

  • created: New code vulnerability found
  • closed_by_user: Alert manually closed
  • fixed: Issue resolved in code
  • reopened: Previously closed alert reopened

secret_scanning_alert - Exposed secrets detected:

  • created: Secret found in repository
  • resolved: Secret issue addressed

security_advisory - Published security advisories:

  • published: New advisory released
  • updated: Advisory information updated

Security Notification Example

Scenario: When Dependabot discovers a critical vulnerability, immediately alert the security team.

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

  if (event === 'dependabot_alert' && payload.action === 'created') {
    const alert = payload.alert;
    const vulnerability = alert.security_vulnerability;

    // Check severity
    if (vulnerability.severity === 'critical' || vulnerability.severity === 'high') {
      await sendNotification({
        recipients: ['security-team'],
        title: `🔒 ${vulnerability.severity.toUpperCase()} Vulnerability Detected`,
        message: `${vulnerability.package.name}: ${alert.security_advisory.summary}`,
        action_url: alert.html_url,
        priority: 'urgent',
        category: 'security_alert'
      });

      console.log(`Alerted security team about ${vulnerability.severity} vulnerability`);
    }
  }

  res.status(200).send('Webhook received');
});

Security Workflow with MagicBell

Automate Dependabot alert workflows:

{
  "key": "integration.github.dependabot_alert.created",
  "steps": [
    {
      "if": "{{data.alert.security_vulnerability.severity == 'critical'}}",
      "command": "broadcast",
      "input": {
        "title": "🔒 Critical Vulnerability: {{data.alert.security_vulnerability.package.name}}",
        "content": "{{data.alert.security_advisory.summary}}",
        "action_url": "{{data.alert.html_url}}",
        "category": "security_alert",
        "recipients": [
          {"external_id": "security-team"}
        ]
      }
    }
  ]
}

Repository and Code Events

These events cover fundamental repository activities like pushing code, creating branches, and publishing releases.

Push Events

The push event fires when commits are pushed to a repository. This is one of the most common webhook events.

Payload highlights:

  • commits: Array of commits pushed
  • ref: The branch reference (e.g., refs/heads/main)
  • before / after: Commit SHAs before and after push
  • forced: Whether this was a force push

Use cases:

  • Trigger deployments when code is pushed to specific branches
  • Run automated tests or code quality checks
  • Update external systems with latest code changes
  • Send notifications about significant commits (e.g., version bumps)

Release Events

release - Repository releases:

  • published: New release created
  • unpublished: Release removed
  • created: Draft release created
  • edited: Release details updated
  • deleted: Release deleted
  • prereleased: Pre-release published
  • released: Pre-release changed to full release

Release events are perfect for notifying users about new versions or triggering deployment pipelines.

MagicBell Release Notification

Announce new releases to your users with a release workflow:

{
  "key": "integration.github.release.published",
  "steps": [
    {
      "command": "broadcast",
      "input": {
        "title": "🎉 {{data.release.name}} Released",
        "content": "{{data.release.body}}",
        "action_url": "{{data.release.html_url}}",
        "category": "product_update",
        "recipients": [
          {"external_id": "all-users"}
        ]
      }
    }
  ]
}

Branch and Tag Events

create - Branch or tag created:

  • ref_type: Either branch or tag
  • ref: Name of branch or tag

delete - Branch or tag deleted:

  • ref_type: Either branch or tag
  • ref: Name deleted

branch_protection_rule - Branch protection changes:

  • created: New protection rule added
  • edited: Existing rule modified
  • deleted: Protection rule removed

Webhook Security Best Practices

Webhook endpoints are publicly accessible HTTP endpoints, making security critical. GitHub provides signature verification to ensure webhook authenticity.

HMAC Signature Verification

Every webhook delivery includes an X-Hub-Signature-256 header containing an HMAC signature. This signature proves the request came from GitHub and hasn't been tampered with.

Verification process:

  1. GitHub generates HMAC using your webhook secret and the payload
  2. GitHub includes signature in X-Hub-Signature-256 header
  3. Your server recomputes the signature using the same secret
  4. Compare signatures—if they match, the webhook is authentic

Node.js Signature Verification

const crypto = require('crypto');

function verifyGitHubSignature(req, secret) {
  const signature = req.headers['x-hub-signature-256'];
  if (!signature) {
    return false;
  }

  // Compute expected signature
  const hmac = crypto.createHmac('sha256', secret);
  const digest = 'sha256=' + hmac.update(JSON.stringify(req.body)).digest('hex');

  // Constant-time comparison to prevent timing attacks
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(digest)
  );
}

// Use in your webhook handler
app.post('/webhooks/github', (req, res) => {
  const secret = process.env.GITHUB_WEBHOOK_SECRET;

  if (!verifyGitHubSignature(req, secret)) {
    console.error('Invalid webhook signature');
    return res.status(401).send('Unauthorized');
  }

  // Signature valid, process webhook
  const event = req.headers['x-github-event'];
  // ... handle event
});

Critical security notes:

  • Always use crypto.timingSafeEqual() for comparison to prevent timing attacks
  • Never log or expose your webhook secret
  • Store secrets in environment variables, not code
  • Use HTTPS for all webhook URLs
  • Rotate webhook secrets periodically

Additional Security Measures

Rate limiting:
Implement rate limits on your webhook endpoint to prevent abuse. Even with signature verification, rate limiting protects against accidental webhook floods.

IP allowlisting (optional):
GitHub publishes webhook IP ranges you can allowlist. However, these ranges change, so signature verification is more reliable.

Payload validation:
After verifying the signature, validate the payload structure. Ensure required fields are present before processing.

Idempotency:
Handle duplicate webhook deliveries gracefully. GitHub may deliver the same webhook multiple times, so use the X-GitHub-Delivery header as an idempotency key.

Building a Custom Webhook Handler

Here's a production-ready Express.js webhook handler that demonstrates best practices:

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

const app = express();

// Raw body parsing for signature verification
app.use(express.json({
  verify: (req, res, buf) => {
    req.rawBody = buf.toString('utf8');
  }
}));

// Webhook secret from environment
const WEBHOOK_SECRET = process.env.GITHUB_WEBHOOK_SECRET;

// Signature verification middleware
function verifySignature(req, res, next) {
  const signature = req.headers['x-hub-signature-256'];

  if (!signature) {
    return res.status(401).send('Missing signature');
  }

  const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
  const digest = 'sha256=' + hmac.update(req.rawBody).digest('hex');

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

  next();
}

// Idempotency tracking (in-memory, use Redis/database in production)
const processedDeliveries = new Set();

// Main webhook endpoint
app.post('/webhooks/github', verifySignature, async (req, res) => {
  const deliveryId = req.headers['x-github-delivery'];
  const event = req.headers['x-github-event'];
  const payload = req.body;

  // Idempotency check
  if (processedDeliveries.has(deliveryId)) {
    console.log(`Duplicate delivery ${deliveryId}, ignoring`);
    return res.status(200).send('Already processed');
  }

  // Mark as processed
  processedDeliveries.add(deliveryId);

  // Acknowledge receipt immediately (respond within 10 seconds)
  res.status(200).send('Webhook received');

  // Process webhook asynchronously
  try {
    await processWebhook(event, payload);
    console.log(`Successfully processed ${event} event (${deliveryId})`);
  } catch (error) {
    console.error(`Error processing webhook ${deliveryId}:`, error);
    // Don't throw—we already responded to GitHub
  }
});

async function processWebhook(event, payload) {
  switch (event) {
    case 'pull_request':
      await handlePullRequest(payload);
      break;
    case 'issues':
      await handleIssue(payload);
      break;
    case 'workflow_run':
      await handleWorkflowRun(payload);
      break;
    case 'push':
      await handlePush(payload);
      break;
    default:
      console.log(`Unhandled event type: ${event}`);
  }
}

async function handlePullRequest(payload) {
  const action = payload.action;
  const pr = payload.pull_request;

  if (action === 'opened') {
    console.log(`New PR #${pr.number}: ${pr.title}`);
    // Send notifications, update tracking systems, etc.
  }
}

async function handleIssue(payload) {
  const action = payload.action;
  const issue = payload.issue;

  if (action === 'opened') {
    const labels = issue.labels.map(l => l.name);
    console.log(`New issue #${issue.number} with labels: ${labels.join(', ')}`);
    // Auto-label, assign, notify teams, etc.
  }
}

async function handleWorkflowRun(payload) {
  const workflow = payload.workflow_run;

  if (payload.action === 'completed' && workflow.conclusion === 'failure') {
    console.log(`Workflow ${workflow.name} failed on ${workflow.head_branch}`);
    // Notify developers of CI failures
  }
}

async function handlePush(payload) {
  const branch = payload.ref.replace('refs/heads/', '');
  const commits = payload.commits.length;

  console.log(`${commits} commit(s) pushed to ${branch}`);

  // Trigger deployments for specific branches
  if (branch === 'main' || branch === 'production') {
    console.log('Triggering deployment...');
    // Deploy to production/staging
  }
}

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`GitHub webhook handler listening on port ${PORT}`);
});

This handler demonstrates:

  • Signature verification middleware
  • Idempotency using delivery IDs
  • Immediate response to GitHub (sub-second)
  • Asynchronous event processing
  • Error handling that doesn't crash the server
  • Event routing to specialized handlers

Testing GitHub Webhooks Locally

Testing webhooks during development requires exposing your local server to the internet so GitHub can deliver events.

Tunneling Tools

Ngrok (popular choice):

ngrok http 3000

Ngrok provides a public URL that tunnels to localhost:3000. Use this URL as your webhook URL in GitHub settings.

Localtunnel (open source):

npm install -g localtunnel
lt --port 3000

Cloudflare Tunnel (free):

cloudflared tunnel --url http://localhost:3000

GitHub Webhook Delivery Inspection

GitHub provides excellent debugging tools in the webhook settings:

  1. Go to SettingsWebhooks
  2. Click your webhook
  3. Scroll to Recent Deliveries

For each delivery, you can view:

  • Full request headers and payload
  • Response status and body
  • Timing information
  • Redeliver the webhook with the same payload

Use redelivery to test your handler without triggering new GitHub events.

Testing with Webhook.site

Webhook.site provides instant webhook URLs for testing. Use it to inspect GitHub webhook payloads without writing any code:

  1. Visit webhook.site and copy your unique URL
  2. Add this URL as a GitHub webhook
  3. Trigger events in GitHub (open a PR, create an issue)
  4. View the full payload in webhook.site

This is invaluable for understanding event structure before writing handler code.

Production Best Practices

Running webhooks in production requires additional considerations beyond development environments.

Idempotency

GitHub may deliver the same webhook multiple times. Your handler must safely process duplicate deliveries without side effects.

Implementation strategies:

  • Store processed delivery IDs in Redis or your database
  • Check if delivery ID exists before processing
  • Set TTL on delivery IDs (e.g., 24 hours) to prevent unbounded growth
// Redis idempotency example
const redis = require('redis').createClient();

async function isProcessed(deliveryId) {
  const exists = await redis.exists(`webhook:${deliveryId}`);
  return exists === 1;
}

async function markProcessed(deliveryId) {
  // Store with 24-hour expiration
  await redis.setex(`webhook:${deliveryId}`, 86400, '1');
}

Error Handling and Retries

GitHub doesn't automatically retry failed webhooks beyond the initial delivery. If your handler errors, implement your own retry logic.

Best practices:

  • Acknowledge receipt immediately (return 2xx status)
  • Queue events for asynchronous processing
  • Implement exponential backoff for transient failures
  • Alert on-call engineers if processing repeatedly fails
// Queue-based processing example
const queue = require('bull');
const webhookQueue = new queue('github-webhooks');

app.post('/webhooks/github', verifySignature, async (req, res) => {
  // Add to queue instead of processing inline
  await webhookQueue.add({
    event: req.headers['x-github-event'],
    payload: req.body,
    deliveryId: req.headers['x-github-delivery']
  });

  res.status(200).send('Queued');
});

// Process queue jobs with retries
webhookQueue.process(async (job) => {
  const { event, payload, deliveryId } = job.data;

  if (await isProcessed(deliveryId)) {
    return; // Already processed
  }

  await processWebhook(event, payload);
  await markProcessed(deliveryId);
});

Monitoring and Alerting

Monitor webhook delivery success rates and processing latency:

  • Track webhook processing failures
  • Alert when error rates exceed thresholds
  • Monitor queue depth if using async processing
  • Log all webhook deliveries for debugging

GitHub also provides webhook delivery metrics in the repository settings, showing success/failure rates over time.

Rate Limits and Scaling

Popular repositories can generate high webhook volumes. Plan for scaling:

  • Use load balancers to distribute webhook traffic
  • Employ horizontal scaling for webhook handlers
  • Consider separate handlers for different event types
  • Implement backpressure mechanisms if your system can't keep up

MagicBell: No-Code Webhook Automation

Writing and maintaining custom webhook handlers requires ongoing development effort. MagicBell workflows provide a no-code alternative that handles common automation scenarios without custom code.

How MagicBell Workflows Work

  1. Configure your GitHub repository to send webhooks to MagicBell
  2. Create workflows that trigger on specific GitHub events
  3. Define notification logic with conditional branching and delays
  4. MagicBell handles webhook security, delivery, and notification sending

Setting Up MagicBell for GitHub

# 1. Save your GitHub webhook signing secret
magicbell integration save_github \
  --data '{"webhook_signing_secret":"your_secret_here"}'

# 2. Use the returned ID to build your webhook URL
# https://api.magicbell.com/v2/integrations/github/webhooks/incoming/{id}

Add this URL to your GitHub webhook configuration, and MagicBell will receive all GitHub events.

Workflow Definition Example

Create a workflow that triggers on GitHub events using the key pattern integration.github.{event_type}:

{
  "key": "integration.github.pull_request.opened",
  "steps": [
    {
      "command": "broadcast",
      "input": {
        "title": "PR Review Needed: {{data.pull_request.title}}",
        "content": "{{data.pull_request.user.login}} opened a pull request",
        "action_url": "{{data.pull_request.html_url}}",
        "category": "code_review",
        "recipients": [
          {
            "external_id": "{{data.pull_request.requested_reviewers[0].login}}"
          }
        ]
      }
    },
    {
      "command": "wait",
      "input": {
        "duration": 86400
      }
    },
    {
      "if": "{{data.pull_request.state == 'open'}}",
      "command": "broadcast",
      "input": {
        "title": "PR Still Pending: {{data.pull_request.title}}",
        "content": "This PR has been open for 24 hours without review",
        "action_url": "{{data.pull_request.html_url}}",
        "category": "reminder"
      }
    }
  ]
}

This workflow:

  1. Sends an immediate notification to the requested reviewer
  2. Waits 24 hours
  3. Sends a reminder if the PR is still open

All without writing any server code or managing infrastructure.

Available GitHub Workflows

MagicBell provides pre-built workflow examples for common GitHub automation scenarios:

Pull Request Workflows:

Issue Workflows:

CI/CD Workflows:

Release Workflows:

Security Workflows:

Browse the complete list of GitHub workflow examples to find templates for your use cases.

Troubleshooting Common Issues

Webhooks Not Delivering

Symptoms: GitHub shows failed deliveries or timeouts.

Solutions:

  • Verify your endpoint URL is publicly accessible
  • Check that your server responds within 10 seconds
  • Ensure you're returning 2xx status codes
  • Review GitHub's Recent Deliveries for specific error messages

Invalid Signature Errors

Symptoms: Your server rejects webhooks with signature verification failures.

Solutions:

  • Verify your webhook secret matches GitHub configuration
  • Ensure you're computing HMAC on the raw request body (before JSON parsing)
  • Check that your secret doesn't have trailing whitespace
  • Use constant-time comparison (crypto.timingSafeEqual)

Missing Event Data

Symptoms: Expected fields are undefined or missing from webhook payload.

Solutions:

  • Use GitHub's Recent Deliveries to inspect actual payload structure
  • Check GitHub's webhook documentation for event schemas
  • Remember that optional fields may be null or absent
  • Different actions within the same event type have different payloads

Webhook Floods

Symptoms: Receiving thousands of webhooks in short time periods.

Solutions:

  • Uncheck "Send me everything" and select specific events
  • Implement rate limiting on your endpoint
  • Use queue-based processing to handle bursts
  • Consider if you actually need real-time processing for all events

Conclusion

GitHub webhooks unlock powerful automation capabilities that transform how development teams work. From automated code reviews and CI/CD notifications to security alerts and release management, webhooks provide the real-time event stream that keeps modern development workflows moving.

This guide covered:

  • 73+ GitHub webhook events across pull requests, issues, CI/CD, security, and repository management
  • Custom webhook implementation with signature verification, idempotency, and production best practices
  • MagicBell workflows as a no-code alternative for common automation scenarios
  • Security best practices including HMAC verification and secret management
  • Testing strategies for local development and debugging
  • Production considerations like error handling, scaling, and monitoring

Whether you're building custom integrations or using MagicBell's no-code workflows, webhooks provide the foundation for event-driven automation that makes your team more productive and responsive.

For more webhook resources, check out:

Sources: