You can use a webhook secret to verify that a webhook delivery is from GitHub.
About validating webhook deliveriesOnce your server is configured to receive payloads, it will listen for any delivery that's sent to the endpoint you configured. To ensure that your server only processes webhook deliveries that were sent by GitHub and to ensure that the delivery was not tampered with, you should validate the webhook signature before processing the delivery further. This will help you avoid spending server time to process deliveries that are not from GitHub and will help avoid man-in-the-middle attacks.
To do this, you need to:
You can create a new webhook with a secret token, or you can add a secret token to an existing webhook. When creating a secret token, you should choose a random string of text with high entropy.
secret
key. For more information, see Editing webhooks.After creating a secret token, you should store it in a secure location that your server can access. Never hardcode a token into an application or push a token to any repository. For more information about how to use authentication credentials securely in your code, see Keeping your API credentials secure.
Validating webhook deliveriesGitHub will use your secret token to create a hash signature that's sent to you with each payload. The hash signature will appear in each delivery as the value of the X-Hub-Signature-256
header. For more information, see Webhook events and payloads.
In your code that handles webhook deliveries, you should calculate a hash using your secret token. Then, compare the hash that GitHub sent with the expected hash that you calculated, and ensure that they match. For examples showing how to validate the hashes in various programming languages, see Examples.
There are a few important things to keep in mind when validating webhook payloads:
sha256=
.==
operator. Instead consider using a method like secure_compare
or crypto.timingSafeEqual
, which performs a "constant time" string comparison to help mitigate certain timing attacks against regular equality operators, or regular loops in JIT-optimized languages.You can use the following secret
and payload
values to verify that your implementation is correct:
secret
: It's a Secret to Everybody
payload
: Hello, World!
If your implementation is correct, the signatures that you generate should match the following signature values:
757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
You can use your programming language of choice to implement HMAC verification in your code. Following are some examples showing how an implementation might look in various programming languages.
Ruby exampleFor example, you can define the following verify_signature
function:
def verify_signature(payload_body)
signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)
return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_HUB_SIGNATURE_256'])
end
Then you can call it when you receive a webhook payload:
post '/payload' do
request.body.rewind
payload_body = request.body.read
verify_signature(payload_body)
push = JSON.parse(payload_body)
"I got some JSON: #{push.inspect}"
end
Python example
For example, you can define the following verify_signature
function and call it when you receive a webhook payload:
import hashlib
import hmac
def verify_signature(payload_body, secret_token, signature_header):
"""Verify that the payload was sent from GitHub by validating SHA256.
Raise and return 403 if not authorized.
Args:
payload_body: original request body to verify (request.body())
secret_token: GitHub app webhook token (WEBHOOK_SECRET)
signature_header: header received from GitHub (x-hub-signature-256)
"""
if not signature_header:
raise HTTPException(status_code=403, detail="x-hub-signature-256 header is missing!")
hash_object = hmac.new(secret_token.encode('utf-8'), msg=payload_body, digestmod=hashlib.sha256)
expected_signature = "sha256=" + hash_object.hexdigest()
if not hmac.compare_digest(expected_signature, signature_header):
raise HTTPException(status_code=403, detail="Request signatures didn't match!")
JavaScript example
For example, you can define the following verifySignature
function and call it in any JavaScript environment when you receive a webhook payload:
let encoder = new TextEncoder();
async function verifySignature(secret, header, payload) {
let parts = header.split("=");
let sigHex = parts[1];
let algorithm = { name: "HMAC", hash: { name: 'SHA-256' } };
let keyBytes = encoder.encode(secret);
let extractable = false;
let key = await crypto.subtle.importKey(
"raw",
keyBytes,
algorithm,
extractable,
[ "sign", "verify" ],
);
let sigBytes = hexToBytes(sigHex);
let dataBytes = encoder.encode(payload);
let equal = await crypto.subtle.verify(
algorithm.name,
key,
sigBytes,
dataBytes,
);
return equal;
}
function hexToBytes(hex) {
let len = hex.length / 2;
let bytes = new Uint8Array(len);
let index = 0;
for (let i = 0; i < hex.length; i += 2) {
let c = hex.slice(i, i + 2);
let b = parseInt(c, 16);
bytes[index] = b;
index += 1;
}
return bytes;
}
TypeScript example
For example, you can define the following verify_signature
function and call it when you receive a webhook payload:
import { Webhooks } from "@octokit/webhooks"; const webhooks = new Webhooks({ secret: process.env.WEBHOOK_SECRET, }); const handleWebhook = async (req, res) => { const signature = req.headers["x-hub-signature-256"]; const body = await req.text(); if (!(await webhooks.verify(body, signature))) { res.status(401).send("Unauthorized"); return; } // The rest of your logic here };
import { Webhooks } from "@octokit/webhooks";
const webhooks = new Webhooks({
secret: process.env.WEBHOOK_SECRET,
});
const handleWebhook = async (req, res) => {
const signature = req.headers["x-hub-signature-256"];
const body = await req.text();
if (!(await webhooks.verify(body, signature))) {
res.status(401).send("Unauthorized");
return;
}
};
Troubleshooting
If you are sure that the payload is from GitHub but the signature verification fails:
X-Hub-Signature-256
header will not be present if you have not configured a secret for your webhook. For more information about configuring a secret for your webhook, see Editing webhooks.X-Hub-Signature-256
header, which uses the HMAC-SHA256 algorithm. The X-Hub-Signature
header uses the HMAC-SHA1 algorithm and is only included for legacy purposes.X-Hub-Signature-256
header, you should use the HMAC-SHA256 algorithm.RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4