Securely receiving webhooks: HMAC, dedup and async processing
(1) Verify the HMAC signature over the raw body, using compare_digest.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | # ====================================================================== # Q: Write an endpoint that securely receives webhooks from an external service. # # (1) Verify the HMAC signature over the raw body, using compare_digest. (2) # Check the timestamp for replay attacks, if the provider supplies one. (3) # Dedup with a unique event_id, because providers deliver at-least-once. (4) # Respond quickly with 200 and do the processing in Celery, otherwise the # provider times out and resends. (5) Don't rely on the order of arrival of # events. # ====================================================================== import hashlib, hmac @csrf_exempt @require_POST def billing_webhook(request): sig = request.headers.get('X-Signature', '') expected = hmac.new(settings.WEBHOOK_SECRET.encode(), request.body, hashlib.sha256).hexdigest() if not hmac.compare_digest(sig, expected): # constant-time return HttpResponse(status=401) event = json.loads(request.body) _, created = WebhookEvent.objects.get_or_create( event_id=event['id'], defaults={'payload': event}) # unique -> dedup if created: transaction.on_commit(lambda: process_webhook.delay(event['id'])) return HttpResponse(status=200) # fast 2xx |