Skip to content

Instantly share code, notes, and snippets.

@gka
Created January 24, 2013 20:43
Show Gist options
  • Star 62 You must be signed in to star a gist
  • Fork 22 You must be signed in to fork a gist
  • Save gka/4627519 to your computer and use it in GitHub Desktop.
Save gka/4627519 to your computer and use it in GitHub Desktop.
PHP Endpoint for Github Webhook URLs

PHP Endpoint for Github Webhook URLs

If you love deploying websites using Github, but for some reason want to use your own server, this script might be exactly what you need.

  1. Put github.php somewhere on your PHP-enabled web server, and make it accessible for the outside world. Let's say for now the script lives on http://example.com/github.php

  2. Somewhere on your server you need to have an update script that pulls your site from Github. If you're lucky, git is available on your server and you just need to run cd myrepo; git pull. If not, you might as well download the entire repository as zip, unpack it, etc. Make sure that it is executable. Let's say the script lives on /path/to/update/script.sh.

  3. Put config.json next to github.php and update it according to your needs.

    If you want email notification (yes, you want!), enter your email address to email.to. The emails will also be sent to the email of the Github user who pushed to the repository. To help yourself recognizing where these strange commit emails are comming from, you should set email.from to something meaningful like github-push-notification@example.com.

    You can use it for several repositories or branches at the same time by adding more entries to the endpoints list. For each endpoint you need to set endpoint.repo to "username/reponame". You can configure endpoints for different branches, for instance if you store your website in gh-pages branch or use different branches for development/production etc.

    Set endpoint.run to the path of your update script, e.g. /path/to/update/script.sh.

    For clarity, describe what happened after the update script has been executed under endpoint.action. Usually that's something like "Your website XY has been updated.". It will be used as subject in notification emails. This is especially helpful if you have multiple endpoints.

    The email will also contain the output of your update script and all the messages of the pushed commits.

  4. On the settings page of your Github repository, go to Service Hooks > WebHook URLs and enter the public url of your github.php, e.g. http://example.com/github.php. On the same page you see a list of IP addresses Github sends the requests from. Make sure they're the same as defined below.

  5. If you don't want everybody to see your config.json, either prevent access using .htaccess or the like, or move it to a secure location on your server. If you move it, make sure the PHP script knows where to find it.

And that's it.

{
"email": {
"from": "github-hook@example.com",
"to": "admin@example.com"
},
"endpoints": [
{
"repo": "user/repository",
"branch": "master",
"action": "something has been changed on the server",
"run": "/path/to/update_script.sh"
}
]
}
<?php
/*
* Endpoint for Github Webhook URLs
*
* see: https://help.github.com/articles/post-receive-hooks
*
*/
// script errors will be send to this email:
$error_mail = "admin@example.com";
function run() {
global $rawInput;
// read config.json
$config_filename = 'config.json';
if (!file_exists($config_filename)) {
throw new Exception("Can't find ".$config_filename);
}
$config = json_decode(file_get_contents($config_filename), true);
$postBody = $_POST['payload'];
$payload = json_decode($postBody);
if (isset($config['email'])) {
$headers = 'From: '.$config['email']['from']."\r\n";
$headers .= 'CC: ' . $payload->pusher->email . "\r\n";
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-Type: text/html; charset=ISO-8859-1\r\n";
}
// check if the request comes from github server
$github_ips = array('207.97.227.253', '50.57.128.197', '108.171.174.178', '50.57.231.61');
if (in_array($_SERVER['REMOTE_ADDR'], $github_ips)) {
foreach ($config['endpoints'] as $endpoint) {
// check if the push came from the right repository and branch
if ($payload->repository->url == 'https://github.com/' . $endpoint['repo']
&& $payload->ref == 'refs/heads/' . $endpoint['branch']) {
// execute update script, and record its output
ob_start();
passthru($endpoint['run']);
$output = ob_end_contents();
// prepare and send the notification email
if (isset($config['email'])) {
// send mail to someone, and the github user who pushed the commit
$body = '<p>The Github user <a href="https://github.com/'
. $payload->pusher->name .'">@' . $payload->pusher->name . '</a>'
. ' has pushed to ' . $payload->repository->url
. ' and consequently, ' . $endpoint['action']
. '.</p>';
$body .= '<p>Here\'s a brief list of what has been changed:</p>';
$body .= '<ul>';
foreach ($payload->commits as $commit) {
$body .= '<li>'.$commit->message.'<br />';
$body .= '<small style="color:#999">added: <b>'.count($commit->added)
.'</b> &nbsp; modified: <b>'.count($commit->modified)
.'</b> &nbsp; removed: <b>'.count($commit->removed)
.'</b> &nbsp; <a href="' . $commit->url
. '">read more</a></small></li>';
}
$body .= '</ul>';
$body .= '<p>What follows is the output of the script:</p><pre>';
$body .= $output. '</pre>';
$body .= '<p>Cheers, <br/>Github Webhook Endpoint</p>';
mail($config['email']['to'], $endpoint['action'], $body, $headers);
}
return true;
}
}
} else {
throw new Exception("This does not appear to be a valid requests from Github.\n");
}
}
try {
if (!isset($_POST['payload'])) {
echo "Works fine.";
} else {
run();
}
} catch ( Exception $e ) {
$msg = $e->getMessage();
mail($error_mail, $msg, ''.$e);
}
@dustincurrie
Copy link

An alternative to validating IPs is to hash the payload with a secret key given to GitHub.

See: https://developer.github.com/webhooks/securing/

@juancarlos-rodriguez
Copy link

Sorry I'm confused.
Which is the update script? is it the github.php?
Regards,

@alexivkin
Copy link

Here is how to check for the signature - if( 'sha1=' . hash_hmac( 'sha1', $postBody, $endpoint['secret'], false ) === $_SERVER[ 'HTTP_X_HUB_SIGNATURE' ]){ // ideally you want to use a method like secure_compare, not ===

Make sure $postBody = file_get_contents( 'php://input' ); and $ContentType === 'application/json'

@Swolebrain
Copy link

Baller as fuck

@jbelien
Copy link

jbelien commented Mar 18, 2018

Based on your awesome Gist, I created a Zend Expressive application for GitHub Webhooks : https://github.com/jbelien/php-github-webhook

  • Check signature ;
  • Replies to ping and push events (can be easily extended to other events - I'll take care of that soon) ;

@designosis
Copy link

I'd be interested in seeing your update_script.sh.
Is it simply cd /path/to/project/folder/; git pull master origin?

@abdu997
Copy link

abdu997 commented Feb 1, 2021

How do you deal with authentication for private repos?

@akosblazsik
Copy link

Depending on your server configuration, you may be required to specify charset strictly in lowercase letters (iso-8859-1 in line 29).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment