Thorn: Easy Webhooks for Python

Ask Solem
Robinhood
Published in
3 min readMay 13, 2016

A webhook is a fancy name for an HTTP callback. Users and other services can subscribe to events happening in your system by registering a URL to be called whenever the event occurs.

Back in 2009 some hoped that webhooks would serve as a building block for the modern web. Webhooks are easy to use since they plug into existing frameworks seamlessly and are dead simple to understand. Anyone who builds a backend system or creates an API for third parties to consume can take advantage of webhooks.

APIs that support webhooks like GitHub’s and Stripe’s make asynchronous events trivial. With GitHub, one can register URLs to be called whenever a new change is committed to your repository, a new bugtracker issue is created, someone publishes a comment, and so on.

However, webhooks could also be used internally. Communication between internal systems is traditionally dominated by complicated message consumer daemons. Using webhooks is an elegant and REST friendly way to implement event driven systems, requiring only a web-server (and optimally a separate service to dispatch the HTTP callback requests). At Robinhood, we’re using webhooks with our new order execution system to perform real-time order cancellations in reaction to events such as a stock split.

Thorn

Thorn is a webhook framework for Python, focusing on flexibility and ease of use, both when getting started and when maintaining a production system. The goal is for webhooks to thrive on the web, by providing Python projects with an easy solution to implement them and keeping a repository of patterns evolved by the Python community. Thorn is:

  • Simple. Add webhook capabilities to your database models using a single decorator, including filtering for specific changes to the model.
  • Flexible. All Thorn components are pluggable, reusable and extendable.
  • Scalable. Thorn can perform millions of HTTP requests every second by taking advantage of Celery for asynchronous processing.

Example

This example adds four webhook events to the Article model of an imaginary blog engine:

Users can now subscribe to the four events individually, or all of them by subscribing to “article.*”, and will be notified every time an article is created, changed, removed or published:

$ curl -X POST \
> -H "Authorization: Bearer <secret login token>" \
> -H "Content-Type: application/json" \
> -d '{"event": "article.*", "url": "https://.com/h/article?u=1"}' \
> http://example.com/hooks/

The webhook consumer can be written in any language with web server support, here’s an example Django consumer endpoint reacting to articles being published:

ref is the URL of the article published

The API is expressive, so may require you to learn more about the arguments to understand it fully. Luckily it’s all described in the Events Guide for you to consult after reading the Quick start tutorial.

Extending Thorn

Thorn currently only supports Django, and an API for subscribing to events is only provided for Django REST Framework. However, extending Thorn is simple so you can contribute support for your favorite framework.

For dispatching web requests we recommend using Celery, but you can get started immediately by dispatching requests locally. Using Celery for dispatching requests will require a message transport like RabbitMQ or Redis.

You can also write custom dispatchers if you have an idea for efficient payload delivery, or just want to reuse a technology you already deploy in production.

Getting Started

You can install Thorn using pip:

$ pip install -U thorn

Next, over to the Django Integration guide to get started using Thorn in your Django projects. Thorn version 1.0 runs on Python 2.7, 3.4 and 3.5; PyPy and Jython.

Acknowledgments

Special thanks to Ty Wilkins for the logo and design, Tom Linford for helping me with this blog post, and my colleagues at Robinhood for their support and contributions to the project.

--

--

Ask Solem
Robinhood

Python and distributed systems, Father of Robin(8). Works at @robinhoodapp