Simple Flask Webhook


Since this is my first technical blog post, I though I'd start with something relatively simple. Also, please bare with me as I get to grips with the formatting of this confounded contraption!
If you have any feedback, then please feel free to send me a mail at

In this post, I'm going to give a quick guide on how to get a fairly simple, yet incredibly useful, webhook setup with Flask.

Source Code

You can find the complete source code here:

What is a Webhook?

Very basically; a webhook is a HTTP web service that listens on a server and will respond and/or perform a task when they are called by some event. Read more.

What is Flask?

Flask is a micro web framework written in Python. Read more.


Since you're here, I'm going to assume you have Python installed. Python 2 is soon(ish...) to be depreciated, so I'm going to be using Python 3 here (and in the future).

Virtual Environments

In my opinion it's always good practice to use virtual environments. I currently use Ubuntu, so the following examples to setup will be with aptitude, but I'm sure it's mostly translatable to whichever package manager your linux distribution uses.

If you don't have virtual environments setup on your system, you should install the virtualenv package with:

$ sudo apt-get install virtualenv

We'll get into making the virtual environment in a little bit... For now, you just need it installed.

Let's Get Started

Ok, with the introduction out of the way, let's get started...


I like to start all new applications in a clean folder of their own... So, that's where we'll start. Make a new directory and let's call is simple_flask_webhook, then change directory into that folder;

$ mkdir simple_flask_webhook
$ cd simple_flask_webhook

Once in here, we will setup our virtual environment for our webhook;

$ virtualenv --python=python3 venv

Activate the virtual environment (i.e. set it as your working installation of Python);

$ source venv/bin/activate

You should now have a (venv) infront of your bash prompt (if you don't have any shell fanciness!).

Python Packages

With the virtual environment created and activated we need to install Flask;

(venv)$ pip install flask

If all went well and flask installed, your output of pip freeze should look something like this:

(venv)$ pip freeze

Note: Don't worry about the version numbers, by the time you read this it might be a bit old!

The Webhook

Right, now let's get to the good stuff!

Create a new file,;

(venv)$ touch

Open it up with your favourite editor, and we'll start writing our app...

from flask import Flask, request, abort

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    if request.method == 'POST':
        return '', 200

if __name__ == '__main__':

Hah! I told you it was simple! The webhook will print out any json you send to it. You can test it out with curl.
i.e. curl -H "Content-Type: application/json" -X POST -d '{"data": "This is some test data"}'

Adding a Little Validation

Well, that's ok; but perhaps a little more security wouldn't go a miss... Let's implement a simple token validation;

import os
from datetime import datetime, timedelta
from flask import Flask, request, abort, jsonify

def temp_token():
    import binascii
    temp_token = binascii.hexlify(os.urandom(24))
    return temp_token.decode('utf-8')


app = Flask(__name__)

authorised_clients = {}

@app.route('/webhook', methods=['GET', 'POST'])
def webhook():
    if request.method == 'GET':
        verify_token = request.args.get('verify_token')
        if verify_token == WEBHOOK_VERIFY_TOKEN:
            authorised_clients[request.remote_addr] =
            return jsonify({'status':'success'}), 200
            return jsonify({'status':'bad token'}), 401

    elif request.method == 'POST':
        client = request.remote_addr
        if client in authorised_clients:
            if - authorised_clients.get(client) > timedelta(hours=CLIENT_AUTH_TIMEOUT):
                return jsonify({'status':'authorisation timeout'}), 401
                return jsonify({'status':'success'}), 200
            return jsonify({'status':'not authorised'}), 401


if __name__ == '__main__':
        print('WEBHOOK_VERIFY_TOKEN has not been set in the environment.\nGenerating random token...')
        token = temp_token()
        print('Token: %s' % token)
        WEBHOOK_VERIFY_TOKEN = token

Wooft, well; that's a bit meatier. So, what's going on here...
When you start the app, it will check for an Environment Variable, called WEBHOOK_VERIFY_TOKEN, if there isn't one set it will automatically generate a token for you.
We initialise a dictionary of authorised_clients, which stores IP addresses (key) and the time they validated (value).
In order for an IP to become validated, they need to sent a GET request with a parameter verify_token equal to our webhooks token.
i.e. curl
Once an IP is registered the webhook will accept POSTs from it, the same as previously. It's not exactly Fort Knox, but it'll suffice for now.

Further Improvements

This post is only supposed to give a brief introduction to writing webhooks and there are many improvements you could make which are probably outwith the scope of this post. However, here are a few suggestions to get you started!

Persistent Authorised Clients

Since we only store the autorised IP addresses in a dictionary, they're lost whenever the application closes. You could store these into a file format, or even a database. Flask-SQLAlchemy makes simplifies using databases with Flask!

Added Authentication

If you want to add further authentication, I'd recommend checking out Flask-HTTPAuth.


If you want to enable HTTPS (without using a full blown http service like nginx), pass your SSL certificates to Flask by modifying the run line to;, ssl_key_file)) where the variables are paths to your certificates.
LetsEncrypt is a really quick and easy way to get setup with some certs!

End Notes

I hope you find this useful, and if you have any comments or have built on the examples and would like to share please feel free to drop me a line.

About Me

Well, since this is nearly all setup I figured I should start (well, excluding that formatting test) with a relatively short and sweet introduction.

I'm James; I currently work for a large company where, without getting too long-winded, I look after a lot of data, produce data reports, run simulations and write software to help the team I belong to produce reports/simulations in a more efficient manner.

I've always grown up around technology. My dad worked for a networking company back when I was a kid in the 90's, and was always brining home bits of old computers, modems, weird storage things etc. I'd often go scavenging in the loft, where inevitably the cast-offs were stowed away for... well, "the future" as every male is accustomed to.

I think my interest in programming began not long after getting the internet. I'm not sure how, but I stumbled into SDF, a free UNIX shell account service from the 80's, where I tumbled down a rabbit hole of this magical new world of UNIX, telnet, SSH, IRC channels... All of it intrigued me from a ripe young age.

Still being young I quite predictably loved video games, and inside this tavern of curiosity that was SDF there was a MUD. I was instantly hooked. I started off playing with clients to automate some of my inputs, then moved into looking at the game's codebase. I ended up changing bits then wrote a few snippets, and hence began my adventures into the world of programming...

Test Post

Header 1

Header 2

Header 3

Header 4
Header 5
Header 6

in-line code

This should have Python syntax highlighting
def do_something(thing=None):
    if not thing:
        print("There isn't a thing!")
        print("We got the thing!")
  1. Item One
  2. Item Two
    • Subitem One
    • Subitem Two
  3. Item Three