Django Integration

Installation

To use Thorn with your Django project you must

  1. Install Thorn

    $ pip install thorn
    
  2. Add thorn.django to INSTALLED_APPS:

    INSTALLED_APPS = (
        # ...,
        'thorn.django',
    )
    
  3. Migrate your database to add the subscriber model:

    $ python manage.py migrate
    
  4. Webhook-ify your models by adding the webhook_model decorator.

    Read all about it in the Events Guide.

  5. (Optional) Install views for managing subscriptions:

    Only Django REST Framework is supported yet, please help us by contributing more view types.

  6. Specify the recommended HMAC signing method in your settings.py:

    THORN_HMAC_SIGNER = 'thorn.utils.hmac:sign'
    

Django Rest Framework Views

The library comes with a standard set of views you can add to your Django Rest Framework API, that enables your users to subscribe and unsubscribe from events.

The views all map to the Subscriber model.

To enable them add the following URL configuration to your urls.py:

url(r"^hooks/",
    include("thorn.django.rest_framework.urls", namespace="webhook"))

Supported operations

Note

All of these curl examples omit the important detail that you need to be logged in as a user of your API.

Subscribing to events

Adding a new subscription is as simple as posting to the /hooks/ endpoint, including the mandatory event and url arguments:

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

Returns the response:

{"id": "c91fe938-55fb-4190-a5ed-bd92f5ea8339",
 "url": "http:\/\/e.com\/h/article?u=1",
 "created_at": "2016-01-13T23:12:52.205785Z",
 "updated_at": "2016-01-13T23:12:52.205813Z",
 "user": 1,
 "hmac_secret": "C=JTX)v3~dQCl];[_h[{q{CScm]oglLoe&>ga:>R~jR$.x?t|kW!FH:s@|4bu:11",
 "hmac_digest": "sha256",
 "content_type": "application\/json",
 "subscription": "http://localhost/hooks/c91fe938-55fb-4190-a5ed-bd92f5ea8339",
 "event": "article.*"}

Parameters

  • event (mandatory)

    The type of event you want to receive notifications about. Events are composed of dot-separated words, so this argument can also be specified as a pattern matching words in the event name (e.g. article.*, *.created, or article.created).

  • url (mandatory)

    The URL destination where the event will be sent to, using a HTTP POST request.

  • content_type (optional)

    The content type argument specifies the MIME-type of the format required by your endpoint. The default is application/json, but you can also specify application/x-www-form-urlencoded..

  • hmac_digest (optional)

    Specify custom HMAC digest type, which must be one of: sha1, sha256, sha512.

    Default is sha256.

  • hmac_secret (optional)

    Specify custom HMAC secret key.

    This key can be used to verify the sender of webhook events received.

    A random key of 64 characters in length will be generated by default, and can be found in the response.

The only important part of the response data at this stage is the id, which is the unique identifier for this subscription, and the subscription url which you can use to unsubscribe later.

Listing subscriptions

Perform a GET request on the /hooks/ endpoint to retrieve a list of all the subscriptions owned by user:

$ curl -X GET                                       \
> -H "Content-Type: application/json"               \
> http://example.com/hooks/

Returns the response:

[
    {"id": "c91fe938-55fb-4190-a5ed-bd92f5ea8339",
     "url": "http:\/\/e.com\/h/article?u=1",
     "created_at": "2016-01-15T23:12:52.205785Z",
     "updated_at": "2016-01-15T23:12:52.205813Z",
     "user": 1,
     "content_type": "application\/json",
     "event": "article.*"}
]

Unsubscribing from events

Perform a DELETE request on the /hooks/<UUID> endpoint to unsubscribe from a subscription by unique identifier:

$ curl -X DELETE                                    \
> -H "Content-Type: application/json"               \
> http://example.com/hooks/c91fe938-55fb-4190-a5ed-bd92f5ea8339/

Example consumer endpoint

This is an example Django webhook receiver view, using the json content type:

from __future__ import absolute_import, unicode_literals

import hmac
import base64
import json
import hashlib

from django.http import HttpResponse
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt

ARTICLE_SECRET = 'C=JTX)v3~dQCl];[_h[{q{CScm]oglLoe&>ga:>R~jR$.x?t|kW!FH:s@|4bu:11'
ARTICLE_DIGEST_TYPE = 'sha256'

# also available at `thorn.utils.hmac.verify`
def verify(hmac_header, digest_method, secret, message):
    digestmod = getattr(hashlib, digest_method)
    signed = base64.b64encode(
        hmac.new(secret, message, digestmod).digest(),
    ).strip()
    return hmac.compare_digest(signed, hmac_header)

@require_POST()
@csrf_exempt()
def handle_article_changed(request):
    digest = request.META.get('HTTP_HOOK_HMAC')
    body = request.body
    if verify(digest, ARTICLE_DIGEST_TYPE, ARTICLE_SECRET, body):
        payload = json.loads(body)
        print('Article changed: {0[ref]}'.format(payload)
        return HttpResponse(status=200)

Using the csrf_exempt() is important here, as by default Django will refuse POST requests that do not specify the CSRF protection token.

Verify HMAC Ruby

This example is derived from Shopify’s great examples found here: https://help.shopify.com/api/tutorials/webhooks#verify-webhook

require 'rubygems'
require 'base64'
require 'openssl'
require 'sinatra'

ARTICLE_SECRET = 'C=JTX)v3~dQCl];[_h[{q{CScm]oglLoe&>ga:>R~jR$.x?t|kW!FH:s@|4bu:11'

helpers do

    def verify_webhook(secret, data, hmac_header):
        digest = OpenSSL::Digest::Digest.new('sha256')
        calculated_hmac = Base64.encode64(OpenSSL:HMAC.digest(
            digest, secret, data)).strip
        return calculated_hmac == hmac_header
    end
end

post '/' do
    request.body.rewind
    data = request.body.read
    if verify_webhook(ARTICLE_SECRET, env["HTTP_HOOK_HMAC"])
        # deserialize data' using json and process webhook
    end
end

Verify HMAC PHP

This example is derived from Shopify’s great examples found here: https://help.shopify.com/api/tutorials/webhooks#verify-webhook

<?php

define('ARTICLE_SECRET', 'C=JTX)v3~dQCl];[_h[{q{CScm]oglLoe&>ga:>R~jR$.x?t|kW!FH:s@|4bu:11')

function verify_webhook($data, $hmac_header)
{
    $calculated_hmac = base64_encode(hash_hmac('sha256', $data,
        ARTICLE_SECRET, true));
    return ($hmac_header == $calculated_hmac);
}

$hmac_header = $_SERVER['HTTP_HOOK_HMAC'];
$data = file_get_contents('php://input');
$verified = verify_webhook($data, $hmac_header);

?>