Django Integration¶
Table of Contents:
Installation¶
To use Thorn with your Django project you must
Install Thorn
$ pip install thorn
Add
thorn.django
toINSTALLED_APPS
:INSTALLED_APPS = ( # ..., 'thorn.django', )
Migrate your database to add the subscriber model:
$ python manage.py migrate
Webhook-ify your models by adding the
webhook_model
decorator.Read all about it in the Events Guide.
(Optional) Install views for managing subscriptions:
Only Django REST Framework is supported yet, please help us by contributing more view types.
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
, orarticle.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 specifyapplication/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);
?>