GitHub with Hookwatch Webhook Tester
This inbox is set up for GitHub: paste its URL as the Payload URL of a repository or organization webhook and the ping GitHub sends on creation appears here, followed by every push or pull request delivery. The Signature tab recomputes the sha256= HMAC of the raw body with your webhook secret, so you can tell a wrong secret from a body your server changed.
- Copy the inbox URL at the top of the tool. It ends in /github, so GitHub deliveries are easy to find in the list.
- In the repository open Settings → Webhooks → Add webhook. Paste the URL as Payload URL, choose application/json and enter a Secret you will also give your server.
- Save the webhook. GitHub immediately sends a ping event with X-GitHub-Event: ping; push a commit, or press Send a signed test GitHub event here, to see a push delivery.
- Select the request. The Signature tab reads X-Hub-Signature-256 and shows Valid or Invalid once you paste the secret.
- If it is Invalid, compare the expected and received digests and open the exact signed bytes. A Valid result here with a failure on your server means your server is not hashing the raw body.
What to know
GitHub signs each delivery with HMAC-SHA256 over the raw request body, using the secret you typed when you created the webhook, and sends the hex digest as X-Hub-Signature-256: sha256=<digest>. The older X-Hub-Signature header carries an HMAC-SHA1 digest and only exists for backwards compatibility; new code should verify the SHA-256 header. Nothing else is signed: not the headers, not the URL, so a request with the right body and secret validates wherever it is replayed from.
Most failures come from hashing something other than the exact bytes GitHub sent. A JSON body parser that runs first, re-serialising with different spacing or key order, changes the digest. So does the form content type: with application/x-www-form-urlencoded GitHub sends payload=<url-encoded JSON>, and the signature covers that whole form string, not the decoded JSON inside it. Compare with a constant-time function such as crypto.timingSafeEqual in Node or hmac.compare_digest in Python, and include the sha256= prefix on both sides or strip it from both.
Every delivery carries X-GitHub-Event (push, pull_request, ping…), X-GitHub-Delivery (a GUID per delivery) and X-GitHub-Hook-ID. GitHub expects a 2XX response within 10 seconds and marks slower responses as failed. It does not retry failed deliveries on its own: open the webhook's Recent Deliveries to see the response and press Redeliver, which repeats the delivery with the same X-GitHub-Delivery GUID. Use that GUID to skip deliveries you have already processed.
The ping event is the quickest way to test a secret, because it arrives the moment you save the webhook and is signed like any other event. Its body includes zen, hook_id and the hook configuration, so you can confirm the content type and events you selected. Push payloads list commits under commits[] with added, modified and removed files; a JSONPath filter such as $.ref == 'refs/heads/main' here shows only pushes to main.
Updated · Cosmovex
Questions
Where do I find the GitHub webhook secret?
GitHub never shows it again after you save the webhook. It is the value you typed in the Secret field when you created it. If you no longer have it, edit the webhook, enter a new secret, save, and update your server with the same value.
Why does my server say the payload signature check failed?
Either the secret differs or the body was changed before hashing. If the delivery shows Valid here with your secret, the secret is right: make your handler compute the HMAC over the raw request bytes before any JSON or form parsing, and keep the content type the same as the webhook setting.
Should I verify X-Hub-Signature or X-Hub-Signature-256?
X-Hub-Signature-256. It is HMAC-SHA256 of the body and is the header GitHub recommends. X-Hub-Signature uses SHA-1 and is kept only for older integrations. The tool checks the SHA-256 header when it is present and falls back to the SHA-1 one.
Does GitHub retry a webhook that failed?
Not automatically. A delivery that gets no 2XX response within 10 seconds is marked failed, and you can redeliver it from Settings → Webhooks → Recent Deliveries. The redelivery keeps the same X-GitHub-Delivery GUID.
Do I need a tunnel like ngrok to test GitHub webhooks?
Not to see what GitHub sends: this URL is public, so GitHub can reach it directly. To run the delivery against code on your laptop, use Copy as curl (free) on a stored request and run it in a terminal, or with Pro use Replay to send it to your local server from the browser.
The free plan covers everything on this page. Hookwatch Webhook Tester Pro ($5/mo, billed monthly) is described on the Hookwatch Webhook Tester page.