Skip to content

GitHub webhooks

GitHub webhook failures are usually the wrong signature header, a secret mismatch, or a mutated body. There is also one structural limit worth knowing before you plan a production setup.

Create a catchbin endpoint with the GitHub provider. Add its URL as a webhook on a repository (Settings → Webhooks) or an organization, set the content type to application/json, and set a secret. Paste the same secret into the catchbin endpoint. catchbin verifies each delivery and stores the secret encrypted.

GitHub sends an HMAC-SHA256 digest of the raw request body, hex-encoded, prefixed with sha256=:

X-Hub-Signature-256: sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17

There is no timestamp and no built-in replay protection. GitHub also sends a legacy X-Hub-Signature (HMAC-SHA1) for backward compatibility — verify against X-Hub-Signature-256, not the sha1 header.

Symptom Cause Fix
Verifying X-Hub-Signature (no -256) Using the deprecated sha1 header Verify X-Hub-Signature-256
Fails on every event immediately Secret mismatch Match the webhook secret exactly
Byte length differs from what GitHub sent Body mutated Verify the raw bytes
Can only configure one webhook URL GitHub App single-URL limit Fan out in your backend, or use repo/org webhooks

Symptom. Verification fails, and your code reads X-Hub-Signature.

Why. X-Hub-Signature is HMAC-SHA1, kept only for old integrations. New code should use X-Hub-Signature-256. A verifier that hashes with SHA256 but reads the SHA1 header will never match.

Fix. Read X-Hub-Signature-256 and compare against a SHA256 HMAC of the raw body. catchbin’s GitHub strategy uses the -256 header.

Symptom. Total failure from the first event.

Why. The secret configured on the GitHub webhook differs from the one catchbin holds — a trailing space, a copy from the wrong environment, or a webhook created without a secret at all.

Fix. Re-enter the same secret on both sides. If the GitHub webhook has no secret, GitHub sends no signature header and verification cannot succeed — add one.

Symptom. Nothing ever verified on a new endpoint, secret confirmed.

Why. Same as every provider: the bytes GitHub signed differ from the bytes your verifier hashed, because a proxy or parser rewrote the body.

Fix. Hash the raw request body before any JSON parsing. catchbin surfaces the received body byte count so you can spot a mismatch against what GitHub sent.

GitHub can redeliver a webhook from its own UI, which re-runs the whole delivery. catchbin replay is different: it re-sends a captured event to a target you choose, so you can point it at localhost or staging without touching GitHub. For a target that verifies signatures, use --resign with that target’s secret.