Inquir Compute logoInquir Compute
Use case · GitHub

Process GitHub webhooks serverlessly for CI/CD and automation

GitHub webhooks fire on push, pull_request, release, issues, and more. Build a serverless processor that verifies HMAC-SHA256, routes events by type, and triggers async pipelines for CI/CD, repo indexing, and issue automation.

Last updated: 2026-04-20

Direct answer

Process GitHub webhooks serverlessly for CI/CD and automation. One function verifies the signature and routes by event type to dedicated pipeline jobs. Push events trigger indexing; pull_request events trigger review automation; release events trigger deployment pipelines.

When it fits

  • CI/CD triggers on push and PR events
  • Repo indexing, documentation sync, and search index updates on push
  • Automated issue labeling, PR review requests, and release deployments

Tradeoffs

  • Running repo indexing, vector embedding, or CI job dispatch synchronously before returning means any latency in those systems makes GitHub retry your endpoint.
  • Handling all event types in one handler creates a monolith that is hard to test, hard to deploy, and easy to break.

GitHub webhook challenges

  • HMAC-SHA256 with timing-safe comparison — standard but easy to get wrong
  • Dozens of event types in one endpoint — routing logic grows fast
  • Heavy work (indexing, CI triggers) inside the webhook window causes GitHub delivery timeouts

GitHub retries failed webhook deliveries up to 3 days. Without a fast ACK and idempotent event handling, you can end up triggering the same CI job or indexing the same commit multiple times.

Why inline GitHub event processing is fragile

Running repo indexing, vector embedding, or CI job dispatch synchronously before returning means any latency in those systems makes GitHub retry your endpoint.

Handling all event types in one handler creates a monolith that is hard to test, hard to deploy, and easy to break.

Verify, route, fan-out — the GitHub webhook pattern

One function verifies the signature and routes by event type to dedicated pipeline jobs. Push events trigger indexing; pull_request events trigger review automation; release events trigger deployment pipelines.

GitHub webhook handling features

HMAC-SHA256 timing-safe verification

Use crypto.timingSafeEqual—never string comparison—to prevent timing attacks on signature validation.

Event type routing

x-github-event header identifies the event. Route to different pipeline functions per event type.

Delivery ID deduplication

Use x-github-delivery header as idempotency key to skip already-processed deliveries on retry.

Async fan-out

One push event can trigger parallel pipelines: index docs, run linter, update search, notify Slack.

GitHub webhook flow

1

Verify HMAC-SHA256 timing-safely

Extract x-hub-signature-256, compute expected HMAC, compare with timingSafeEqual.

2

Route by event type, return 200

Switch on x-github-event header. Trigger appropriate pipeline. Return fast—before pipelines complete.

3

Process in pipelines

Each pipeline step runs independently with retries. Deduplication keys prevent double-processing on retry.

GitHub push and PR webhook handler

Verify HMAC, route by event type, trigger async pipelines—all before returning 200 to GitHub.

webhooks/github.mjs
import { createHmac, timingSafeEqual } from 'node:crypto';

export async function handler(event) {
  const body = event.body ?? '';
  const sigHeader = (event.headers['x-hub-signature-256'] ?? '').replace('sha256=', '');
  const expected = createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET).update(body).digest('hex');
  if (sigHeader.length !== expected.length ||
      !timingSafeEqual(Buffer.from(sigHeader, 'hex'), Buffer.from(expected, 'hex'))) {
    return { statusCode: 401, body: 'invalid signature' };
  }
  const eventType = event.headers['x-github-event'];
  const deliveryId = event.headers['x-github-delivery'];
  const payload = JSON.parse(body);
  const isNew = await db.webhookDeliveries.upsert(deliveryId);
  if (!isNew) return { statusCode: 200, body: 'duplicate' };
  if (eventType === 'push') {
    await global.durable.startNew('index-repo', undefined, { repo: payload.repository.full_name, sha: payload.after });
  } else if (eventType === 'pull_request' && payload.action === 'opened') {
    await global.durable.startNew('review-pr', undefined, { repo: payload.repository.full_name, pr: payload.number });
  } else if (eventType === 'release' && payload.action === 'published') {
    await global.durable.startNew('deploy-release', undefined, { repo: payload.repository.full_name, tag: payload.release.tag_name });
  }
  return { statusCode: 200, body: 'accepted' };
}

Use this for GitHub automation

When this works

  • CI/CD triggers on push and PR events
  • Repo indexing, documentation sync, and search index updates on push
  • Automated issue labeling, PR review requests, and release deployments

When to skip it

  • Simple GitHub Actions workflows that do not need external serverless processing

FAQ

How do I test locally?

Use gh webhook forward --repo owner/repo --events push --url http://localhost:PORT to forward real events to a local function.

What about pull_request review events?

GitHub sends many sub-actions (submitted, dismissed, edited). Always check payload.action to route correctly.

Inquir Compute logoInquir Compute

The simplest way to run AI agents and backend jobs without infrastructure.

Contact info@inquir.org

© 2025 Inquir Compute. All rights reserved.