The Github Webhooks API is powerful and flexible, making it simple to integrate services with your source repository. Lately I've been tinkering with it a bit, but all the examples Github has are in Ruby. So I put together a simple demo server in Python 3. Though simple (it's completely self contained and only needs Python 3 to run), it's complete, covering even webhook security by verifying the signature created with the API's secret token.
Here it is:
import argparse
import hashlib
import hmac
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import pprint
import os
import sys
# It's not recommended to store the key within the code. Following
# http://12factor.net/config, we'll store this in the environment.
# Note that the key must be a bytes object.
HOOK_SECRET_KEY = os.environb[b'HOOK_SECRET_KEY']
class GithubHookHandler(BaseHTTPRequestHandler):
"""Base class for webhook handlers.
Subclass it and implement 'handle_payload'.
"""
def _validate_signature(self, data):
sha_name, signature = self.headers['X-Hub-Signature'].split('=')
if sha_name != 'sha1':
return False
# HMAC requires its key to be bytes, but data is strings.
mac = hmac.new(HOOK_SECRET_KEY, msg=data, digestmod=hashlib.sha1)
return hmac.compare_digest(mac.hexdigest(), signature)
def do_POST(self):
data_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(data_length)
if not self._validate_signature(post_data):
self.send_response(401)
return
payload = json.loads(post_data.decode('utf-8'))
self.handle_payload(payload)
self.send_response(200)
class MyHandler(GithubHookHandler):
def handle_payload(self, json_payload):
"""Simple handler that pretty-prints the payload."""
print('JSON payload')
pprint.pprint(json_payload)
if __name__ == '__main__':
argparser = argparse.ArgumentParser(description='Github hook handler')
argparser.add_argument('port', type=int, help='TCP port to listen on')
args = argparser.parse_args()
server = HTTPServer(('', args.port), MyHandler)
server.serve_forever()
Just run it at some port on your server and point the webhook you create to it. Currently it just runs on the server's root path (e.g. http://myserver.com:1234), but should be trivial to modify to any path.
By the way, I found ngrok to be invaluable for testing this. It creates a tunnel from your localhost's port to a unique URL you can set as the webhook destination on Github. This makes it possible to quickly iterate and test the server on your local machine.