How to create a GitHub webhook
A GitHub webhook is an HTTP POST that GitHub sends to a URL you register whenever something happens in a repository, organization, or GitHub App. You create one under Settings > Webhooks > Add webhook, set a payload URL and a secret, and choose which events fire it.
What GitHub sends
Every delivery is a POST carrying the event as JSON, with three headers that matter. X-GitHub-Event names the event (push, pull_request, and so on), X-GitHub-Delivery is a unique GUID you can deduplicate on, and X-Hub-Signature-256 is an HMAC SHA-256 of the raw request body keyed with your secret, formatted as sha256= followed by the hex digest. The older X-Hub-Signature header uses SHA-1 and should be ignored.
GitHub allows 10 seconds for a response and does not retry repository webhooks automatically. A delivery that times out or returns a non-2xx status stays failed until you redeliver it by hand from the Recent Deliveries tab, so the handler should answer quickly and do its real work in the background.
Step 1: Set up a web server for receiving webhooks
To test webhooks locally, use tools like Ngrok or Svix Play to create a public URL that can forward GitHub webhook requests to your local development server.
- Install Ngrok or use an alternative like SvixPlay to create a public-facing endpoint.
- Start Ngrok to forward requests to your localhost. For example:
This will generate a public URL (e.g.,ngrok http 3000
https://abcd1234.ngrok.io) that you’ll use as the webhook URL in GitHub.
Step 2: Create a webhook in GitHub
-
Navigate to Your GitHub Repository: Go to your repository settings by selecting Settings > Webhooks > Add webhook.
-
Configure the Webhook:
- Payload URL: Enter your public URL (e.g.,
https://abcd1234.ngrok.io/github-webhook). - Content type: Set it to
application/json. - Secret: Enter a secret key (recommended). This helps verify that the requests you receive are genuinely from GitHub.
- Events: Choose the events to trigger the webhook (e.g.,
push,pull request), or select “Send me everything” to capture all repository events.
- Payload URL: Enter your public URL (e.g.,
-
Save the Webhook: Click Add webhook to save your configuration. GitHub will immediately attempt a ping to your endpoint to verify the setup.
Step 3: Set up code to handle webhook events
Create a server script to process webhook events and respond to GitHub.
Sample code in Node.js
Here's an example using Node.js and Express to listen to incoming webhook events. Note that the route parses the body as a raw Buffer rather than JSON: the signature covers the exact bytes GitHub sent, and re-serializing the parsed object will produce a different string and a failed check.
const express = require("express");
const crypto = require("crypto");
const app = express();
const GITHUB_SECRET = process.env.GITHUB_SECRET || "your_secret_key";
// Middleware to verify GitHub signature
function verifyGitHubSignature(req, res, next) {
const signature = req.headers["x-hub-signature-256"] || "";
const hash = `sha256=${crypto
.createHmac("sha256", GITHUB_SECRET)
.update(req.body)
.digest("hex")}`;
const a = Buffer.from(signature);
const b = Buffer.from(hash);
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.status(401).send("Invalid signature");
}
next();
}
app.post(
"/github-webhook",
express.raw({ type: "application/json" }),
verifyGitHubSignature,
(req, res) => {
const event = req.headers["x-github-event"];
const deliveryId = req.headers["x-github-delivery"];
const payload = JSON.parse(req.body.toString("utf8"));
console.log(`Received GitHub event: ${event} (${deliveryId})`);
// Handle the event types you need
if (event === "push") {
console.log("Push event received:", payload);
} else if (event === "pull_request") {
console.log("Pull request event received:", payload);
}
res.sendStatus(200);
}
);
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
-
Install Dependencies:
npm install express -
Run the Server:
node server.js -
Test the Webhook: Trigger events in your GitHub repository (e.g., create a new pull request or push to a branch) to verify that your server receives and logs the events.
Step 4: Verify webhook requests
Reject any request whose X-Hub-Signature-256 header does not match an HMAC SHA-256 of the raw body computed with your secret, and compare the two values in constant time so the check does not leak information about the correct digest. An endpoint that skips this accepts a POST from anyone who guesses the URL. GitHub webhook security covers the rest of the hardening, including IP allowlists and secret rotation.
Step 5: Review webhook delivery history
After creating the webhook, you can see its delivery history in GitHub:
- Go to Settings > Webhooks in your GitHub repository.
- Select your webhook to view recent delivery logs, including status, response times, and detailed payload information.
Each entry shows the full request and response, and the Redeliver button replays it. Because GitHub does not retry on its own, this tab is the recovery path when your receiver was down: see what happens when a webhook fails for how other providers handle the same situation.
Need to receive webhooks reliably?
Svix Ingest gives you one endpoint for webhooks from any provider, with signature verification and durability built in, so a redeploy or a traffic spike never drops an event.
Start receiving webhooks with Svix Ingest or inspect webhooks with Svix Play