github app webhook secret mismatch install failed
For engineers installing a GitHub App with webhooks. Use when deliveries fail signature verification after install. Key triggers: secret mismatch between app settings and receiver, trailing whitespace, verifying against parsed JSON instead of raw bytes. Not for unreachable payload URLs or 500 handler errors.
Fix GitHub App install failing on webhook secret mismatch
TL;DR
GitHub signs every webhook delivery with the webhook secret, and the receiver rejected the signature: the secret in the GitHub App settings does not match what the receiver is verifying against. Copy the exact secret into both places - watching for trailing whitespace - then use GitHub's redeliver button to confirm 200s. Signature verification is byte-exact; one extra character breaks everything.
The error
Webhook delivery failed: signature verification error
Recent Deliveries tab shows failed ping/installation events; receiver logs "X-Hub-Signature-256 mismatch"Use this skill
Agent? Search Vectle for this error:
curl -s "https://vectle.com/api/v1/search?q=github%20app%20webhook%20secret%20mismatch%20install%20failed"Fix it
Step 1: Copy the secret exactly from the app settings
In the GitHub App settings / Webhook, reveal and copy the webhook secret value. Paste it into a plain-text check for leading or trailing whitespace.Expected: the exact string. Whitespace from copy-paste is the most common cause.
Step 2: Set the identical value on the receiver
Put the same string into the receiver's configuration (environment variable or settings store) and restart or reload the receiver so the new value loads.Expected: both sides hold byte-identical secrets. Update every environment the receiver runs in.
Step 3: Redeliver a ping and confirm 200
In the app's Settings / Advanced / Recent Deliveries, redeliver the ping event and check the response code.Expected: 200. If it still fails, the secrets still differ or the verification code has a bug; continue to step 4.
Step 4: Verify the HMAC computation is correct
Confirm the receiver computes HMAC-SHA256 over the raw request body bytes using the secret, and compares against the X-Hub-Signature-256 header with a constant-time compare.Expected: the computation matches GitHub's documented algorithm. Frameworks that parse JSON before verifying must use the raw bytes, not the re-serialized body.
Step 5: Complete the installation
Finish the app installation flow and confirm installation and installation_repositories events arrive with 200 responses.Expected: deliveries succeed end to end; the install is unblocked.
When this applies
- Webhook deliveries fail signature verification
- The failure starts right after setting or rotating the secret
- Ping events fail before any real events
When it doesn't
- Deliveries time out or get connection refused (payload URL unreachable)
- The receiver 500s after accepting the signature (handler bug)
- Events arrive but with wrong data (payload handling issue)
Compatibility
GitHub Apps webhooks, HMAC-SHA256 via X-Hub-Signature-256. Same mechanics for OAuth App webhooks.
Variant phrasings
github webhook 401 signature verification failed
A 401 on delivery is the receiver rejecting the signature. Fix the secret, not the URL.
x-hub-signature-256 mismatch
The header name tells you which algorithm the receiver expects; make sure both sides use SHA-256, not the legacy SHA-1 header.
Why it happens
Webhook signatures exist so the receiver can prove the payload came from GitHub untampered. Both sides must share the secret byte-for-byte: GitHub signs with it, the receiver verifies with it. Copy-paste whitespace, env-var quoting, or updating one side during rotation breaks the equality, and the check fails closed by design.
Edge cases
- Secret rotation needs ordering: receiver first, then GitHub, or deliveries fail during the window
- Verifying against parsed-then-reserialized JSON instead of raw bytes fails even with the right secret
- Some proxies strip or alter the signature header; check header passthrough
If it still fails
- Capture the exact timestamp, the username, and the full error from the system log before changing anything else.
- Reproduce with a single test user so you are not debugging a crowd.
- If it worked before, diff the config against the last known good, then open a vendor ticket with the timestamp and request id. Never send secrets or private keys.
Prevention
- Track credential and certificate expiry with alerts, not memory.
- Run a synthetic check per app daily so breakage pages you, not a user.
- Document mappings, URLs, and runbook steps where the next admin will find them.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_KTV8H8reJTWbTfYoc5M63w