HookDeploy
GitHub Intermediate

Testing GitHub Webhooks with HookDeploy

GitHub Apps vs OAuth Apps, webhook event types, payload verification, and testing delivery with HookDeploy and ngrok.

HookDeploy Team · May 22, 2026

GitHub webhooks notify your application about repository events — pushes, pull requests, issues, releases, and dozens more. This guide covers how to set them up, verify payloads, and debug delivery failures.

GitHub Apps vs OAuth Apps vs personal tokens

MethodWebhook supportBest for
GitHub AppYes — app-level webhooks + per-repo subscriptionsProduction integrations, fine-grained permissions
OAuth AppLimited — no webhooks on the OAuth app itselfUser authorization flows, not event delivery
Repository webhookYes — configured per repo in SettingsQuick testing, repo-specific automation
Organization webhookYes — all repos in the orgOrg-wide CI/CD triggers

For most development work, start with a repository webhook pointing at HookDeploy. Graduate to a GitHub App when you need multi-repo subscriptions and installation tokens.

Common GitHub webhook events

EventPayload contains
pushCommits, ref, pusher, repository
pull_requestPR object, action (opened, closed, synchronize)
issuesIssue object, action (opened, edited, closed)
releaseRelease object, action (published, created)
workflow_runCI workflow status changes
pingSent when you first create the webhook — verify delivery

Subscribe only to events you handle. GitHub sends a ping event immediately after webhook creation — if you do not see it in HookDeploy, the URL is wrong.

Set up HookDeploy as your GitHub webhook

  1. Create an endpoint in HookDeploy
  2. Copy https://hookdeploy.dev/h/{slug}
  3. In your GitHub repo: Settings → Webhooks → Add webhook
  4. Payload URL: your HookDeploy URL
  5. Content type: application/json
  6. Secret: generate a random string — save it for verification
  7. Select events (start with push and pull_request)
  8. Click Add webhook

GitHub sends a ping event. Check HookDeploy — you should see it within seconds.

Verify GitHub signatures

GitHub signs payloads with HMAC-SHA256 using your webhook secret. The signature is in the X-Hub-Signature-256 header:

const crypto = require('crypto');

function verifyGitHubWebhook(rawBody, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your handler:
const sig = request.headers['x-hub-signature-256'];
if (!verifyGitHubWebhook(request.rawBody, sig, process.env.GITHUB_WEBHOOK_SECRET)) {
  return response.status(401).send('Invalid signature');
}

Testing with ngrok vs HookDeploy

HookDeploy alone — best when your local server is not running. Capture the webhook, inspect the payload, replay later.

ngrok alone — best when your server is running and you want live traffic in your debugger.

Both together — capture in HookDeploy with forward URL set to your ngrok tunnel. You get history plus live processing.

# Terminal 1: start ngrok
ngrok http 3000

# Terminal 2: your app
npm run dev

Set HookDeploy forward URL to https://abc123.ngrok.io/webhooks/github. GitHub → HookDeploy → ngrok → localhost.

Trigger test events

Push a commit to trigger push:

git commit --allow-empty -m "test webhook"
git push

Open or close a PR to trigger pull_request. Each action generates a separate delivery with action in the payload.

Common GitHub delivery failures

Red delivery icon in GitHub Settings

GitHub shows delivery status per webhook. Click Recent Deliveries to see response codes.

ResponseMeaning
200Success
404URL wrong or HookDeploy endpoint deleted
422HookDeploy endpoint is paused
429HookDeploy plan limit reached
TimeoutGitHub waited 10 seconds — HookDeploy capture should never timeout

Webhook not firing

  • Event type not subscribed — check selected events in webhook settings
  • Branch filters — org webhooks can filter by branch
  • GitHub App not installed on the repo

Duplicate deliveries

  • GitHub retries on non-2xx for up to 3 attempts
  • Use X-GitHub-Delivery header as an idempotency key

SSL errors

  • HookDeploy uses valid TLS — SSL errors usually mean a typo in the URL

Inspect payloads in HookDeploy

Open a captured push event. Key fields:

{
  "ref": "refs/heads/main",
  "commits": [{ "id": "abc123", "message": "fix bug" }],
  "repository": { "full_name": "org/repo" },
  "pusher": { "name": "developer" }
}

For pull_request:

{
  "action": "opened",
  "number": 42,
  "pull_request": {
    "title": "Add feature",
    "head": { "ref": "feature-branch" },
    "base": { "ref": "main" }
  }
}

Test with curl

curl -X POST "https://hookdeploy.dev/h/YOUR_SLUG" \
  -H "Content-Type: application/json" \
  -H "X-GitHub-Event: push" \
  -H "X-GitHub-Delivery: test-delivery-id" \
  -d '{
    "ref": "refs/heads/main",
    "commits": [],
    "repository": { "full_name": "test/repo" }
  }'

Related guides