Django Integration — Thorn 1.3.0 documentation

# Django Integration

Table of Contents:

- Installation
- Django Rest Framework Views
- Example consumer endpoint
- Verify HMAC Ruby
- Verify HMAC PHP

## 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 itsdangerous import URLSafeSerializer

    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'

    def verify_webhook(secret, hmac_header, digest_method, 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')
        digest_type = request.META.get('')
        body = request.body
        if verify_webhook(ARTICLE_SECRET, digest, ARTICLE_DIGEST_TYPE, 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);

    ?>