Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
GitHub Webhook Handler
<?php
/**
* GitHub webhook handler template.
*
* @see https://docs.github.com/webhooks/
* @author Miloslav Hůla (https://github.com/milo)
*/
$hookSecret = 's.e.c.r.e.t'; # set NULL to disable check
set_error_handler(function($severity, $message, $file, $line) {
throw new \ErrorException($message, 0, $severity, $file, $line);
});
set_exception_handler(function($e) {
header('HTTP/1.1 500 Internal Server Error');
echo "Error on line {$e->getLine()}: " . htmlSpecialChars($e->getMessage());
die();
});
$rawPost = null;
if ($hookSecret !== null) {
if (!isset($_SERVER['HTTP_X_HUB_SIGNATURE'])) {
throw new \Exception("HTTP header 'X-Hub-Signature' is missing.");
} elseif (!extension_loaded('hash')) {
throw new \Exception("Missing 'hash' extension to check the secret code validity.");
}
list($algo, $hash) = explode('=', $_SERVER['HTTP_X_HUB_SIGNATURE'], 2) + array('', '');
if (!in_array($algo, hash_algos(), true)) {
throw new \Exception("Hash algorithm '$algo' is not supported.");
}
$rawPost = file_get_contents('php://input');
if (!hash_equals($hash, hash_hmac($algo, $rawPost, $hookSecret))) {
throw new \Exception('Hook secret does not match.');
}
};
if (!isset($_SERVER['CONTENT_TYPE'])) {
throw new \Exception("Missing HTTP 'Content-Type' header.");
} elseif (!isset($_SERVER['HTTP_X_GITHUB_EVENT'])) {
throw new \Exception("Missing HTTP 'X-Github-Event' header.");
}
switch ($_SERVER['CONTENT_TYPE']) {
case 'application/json':
$json = $rawPost ?: file_get_contents('php://input');
break;
case 'application/x-www-form-urlencoded':
$json = $_POST['payload'];
break;
default:
throw new \Exception("Unsupported content type: $_SERVER[CONTENT_TYPE]");
}
# Payload structure depends on triggered event
# https://developer.github.com/v3/activity/events/types/
$payload = json_decode($json);
switch (strtolower($_SERVER['HTTP_X_GITHUB_EVENT'])) {
case 'ping':
echo 'pong';
break;
// case 'push':
// break;
// case 'create':
// break;
default:
header('HTTP/1.0 404 Not Found');
echo "Event:$_SERVER[HTTP_X_GITHUB_EVENT] Payload:\n";
print_r($payload); # For debug only. Can be found in GitHub hook log.
die();
}
@dereckson

This comment has been minimized.

Copy link

@dereckson dereckson commented Nov 21, 2015

Use the hash_equals method instead of == in PHP 5.6+ to compare strings. That will prevent theorical timing attacks to guess the secret.

@madchops1

This comment has been minimized.

Copy link

@madchops1 madchops1 commented Mar 24, 2016

This is awesome dude!

@aapis

This comment has been minimized.

Copy link

@aapis aapis commented Apr 19, 2016

Awesome. 👍

@victorsmirnov

This comment has been minimized.

Copy link

@victorsmirnov victorsmirnov commented Apr 29, 2016

Why do you use $_SERVER['HTTP_CONTENT_TYPE'] instead of $_SERVER['CONTENT_TYPE']? In my setup (PHP 5.6.4 and Apache 2) only the last is available but the first is not defined even when the header exists.

Thank you!

@lhermann

This comment has been minimized.

Copy link

@lhermann lhermann commented Aug 11, 2016

Also had to change $_SERVER['HTTP_CONTENT_TYPE'] to $_SERVER['CONTENT_TYPE'] like @victorsmirnov, otherwise perfect.
Thank you!

@piperone

This comment has been minimized.

Copy link

@piperone piperone commented Jun 13, 2017

Cool. Thanks!

@mnolte

This comment has been minimized.

Copy link

@mnolte mnolte commented Sep 23, 2017

Nice one! Thanks

@u01jmg3

This comment has been minimized.

Copy link

@u01jmg3 u01jmg3 commented Oct 19, 2017

Cheers! For me I had to change HTTP_CONTENT_TYPE to CONTENT_TYPE and I used hash_equals() as per the suggestion of @dereckson.

@yulinzhihou

This comment has been minimized.

Copy link

@yulinzhihou yulinzhihou commented Jun 25, 2019

nice good job

@Aleksandr-yask

This comment has been minimized.

Copy link

@Aleksandr-yask Aleksandr-yask commented Jan 11, 2020

I had an error on HTTP_CONTENT_TYPE, the thing is that the request came to me with CONTENT_TYPE
I changed everything $ _SERVER ['HTTP_CONTENT_TYPE'] to $ _SERVER ['CONTENT_TYPE'] and everything is ok.
Thanks!

@gabrielrodrigodesbesell

This comment has been minimized.

Copy link

@gabrielrodrigodesbesell gabrielrodrigodesbesell commented Aug 4, 2020

Awesome! Tku

@milo

This comment has been minimized.

Copy link
Owner Author

@milo milo commented Aug 25, 2020

Thanks for feedback!
I cannot recall, why I used HTTP_CONTENT_TYPE. So I updated gist to use CONTENT_TYPE and hash_equals(). Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.