1
0
mirror of synced 2025-12-19 18:10:59 -05:00
Files
docs/content/webhooks/using-webhooks/validating-webhook-deliveries.md
Jess Hosman 0cf27c110d Update "Securing webhooks" (#42622)
Co-authored-by: Sarah Edwards <skedwards88@github.com>
2023-09-27 21:58:11 +00:00

206 lines
8.3 KiB
Markdown

---
title: Validating webhook deliveries
intro: 'You can use a webhook secret to verify that a webhook delivery is from {% data variables.product.company_short %}.'
redirect_from:
- /webhooks/securing
- /developers/webhooks-and-events/securing-your-webhooks
- /developers/webhooks-and-events/webhooks/securing-your-webhooks
- /webhooks-and-events/webhooks/securing-your-webhooks
- /webhooks/webhooks/securing-your-webhooks
- /webhooks/securing-your-webhooks
- /webhooks/using-webhooks/securing-your-webhooks
versions:
fpt: '*'
ghes: '*'
ghae: '*'
ghec: '*'
topics:
- Webhooks
---
## About webhook deliveries
Once your server is configured to receive payloads, it will listen for any delivery that's sent to the endpoint you configured. For security reasons, you should only process deliveries from {% data variables.product.prodname_dotcom %}.
To ensure your server only processes deliveries from {% data variables.product.prodname_dotcom %}, you need to:
1. Create a secret token for a webhook.
1. Store the token securely on your server.
1. Validate incoming webhook payloads against the token, to verify they are coming from {% data variables.product.prodname_dotcom %}.
## Creating a secret token
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.
- _To create a new webhook with a secret token_, see "[AUTOTITLE](/webhooks/using-webhooks/creating-webhooks)."
- _To add a secret token to an existing webhook_, edit the webhook's settings. Under "Secret", type a string to use as a `secret` key. For more information, see "[AUTOTITLE](/webhooks/using-webhooks/editing-webhooks)."
## Securely storing the secret token
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 "[AUTOTITLE](/rest/overview/keeping-your-api-credentials-secure#use-authentication-credentials-securely-in-your-code)."
## Validating webhook deliveries
{% data variables.product.product_name %} 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 "[AUTOTITLE](/webhooks/webhook-events-and-payloads#delivery-headers)."
In your code that handles webhook deliveries, you should calculate a hash using your secret token. Then, compare the hash that {% data variables.product.company_short %} 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](#examples)."
There are a few important things to keep in mind when validating webhook payloads:
- {% data variables.product.product_name %} uses an HMAC hex digest to compute the hash.
- The hash signature always starts with `sha256=`.
- The hash signature is generated using your webhook's secret token and the payload contents.
- If your language and server implementation specifies a character encoding, ensure that you handle the payload as UTF-8. Webhook payloads can contain unicode characters.
- Never use a plain `==` operator. Instead consider using a method like [`secure_compare`][secure_compare] or [`crypto.timingSafeEqual`][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.
### Testing the webhook payload validation
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:
- signature: `757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17`
- X-Hub-Signature-256: `sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17`
### Examples
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 example
For example, you can define the following `verify_signature` function:
``` ruby
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:
``` ruby
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:
```python
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:
```javascript
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:
```javascript copy
import * as crypto from "crypto";
const WEBHOOK_SECRET: string = process.env.WEBHOOK_SECRET;
const verify_signature = (req: Request) => {
const signature = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest("hex");
let trusted = Buffer.from(`sha256=${signature}`, 'ascii');
let untrusted = Buffer.from(req.headers.get("x-hub-signature-256"), 'ascii');
return crypto.timingSafeEqual(trusted, untrusted);
};
const handleWebhook = (req: Request, res: Response) => {
if (!verify_signature(req)) {
res.status(401).send("Unauthorized");
return;
}
// The rest of your logic here
};
```
[secure_compare]: https://www.rubydoc.info/gems/rack/Rack%2FUtils:secure_compare
[timingSafeEqual]: https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b
## Further reading
- "[AUTOTITLE](/webhooks/using-webhooks/handling-webhook-deliveries)"
- "[AUTOTITLE](/webhooks/using-webhooks/best-practices-for-using-webhooks)"