Securely receiving webhooks: HMAC, dedup and async processing

Backend Live Coding November 13, 2026 python

(1) Verify the HMAC signature over the raw body, using compare_digest.

python
 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