GitHub webhooks are the backbone of modern CI/CD pipelines. They push massive JSON payloads representing code pushes, PR merges, and issue updates. However, because GitHub's payloads can be extremely large, processing them synchronously can lead to HTTP timeouts and failed deliveries.
Unlike Stripe, GitHub prepends a sha256= string to the HMAC header. The most common reason GitHub validation fails in Node.js/Python is failing to strip this prefix before doing a timing-safe comparison.
The Fix: Strip and Compare
const receivedSig = req.headers['x-hub-signature-256'].replace(/^sha256=/, '');\nconst expectedSig = crypto.createHmac('sha256', secret).update(req.body).digest('hex');\ncrypto.timingSafeEqual(Buffer.from(receivedSig, 'hex'), Buffer.from(expectedSig, 'hex'));When you first configure a GitHub webhook, GitHub sends a ping event to verify the endpoint is alive. If your routing logic expects a push payload and crashes on the ping, GitHub will mark the webhook as failed.
Always check the X-GitHub-Event header. If it equals ping, return a fast 200 OK without processing the body.
Was this page helpful?
Your feedback helps us improve the docs.