How to Test a Webhook Handler in CI
A webhook handler test must cover both a valid signature and a tampered one, so CI catches a broken verification path.
Load a fixture payload, compute the expected signature with the test secret, and assert the handler accepts it. Add a second case with a wrong signature that must be rejected.
Steps
- Save a real payload as a fixture JSON file.
- Compute the signature over the exact fixture bytes with the test secret.
- Assert 2xx for the valid case and 401 for a tampered signature.
Test
handler.test.js
const crypto = require('crypto');
const { handler } = require('./handler');
const secret = 'test-secret';
const body = JSON.stringify({ action: 'opened' });
const sig = 'sha256=' + crypto.createHmac('sha256', secret).update(body).digest('hex');
test('accepts a valid signature', async () => {
const res = await handler(body, { 'x-hub-signature-256': sig });
expect(res.status).toBe(200);
});
test('rejects a tampered signature', async () => {
const res = await handler(body, { 'x-hub-signature-256': 'sha256=deadbeef' });
expect(res.status).toBe(401);
});In CI
.github/workflows/ci.yml
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm testGotchas
- Sign the exact bytes the handler will read; a trailing newline changes the digest.
- Do not commit the production secret; use a dedicated test secret in the fixture.
Related guides
How to Verify a Webhook Signature With HMAC SHA-256Verify an incoming GitHub webhook by computing an HMAC SHA-256 over the raw body with your secret and compari…
How to Mock and Replay Webhooks for TestsMock and replay webhooks in tests by saving a real delivery payload and its headers to a fixture, then feedin…