You: "The view does only three things: it verifies the HMAC signature, stores the event with a unique event_id and responds 200 immediately.
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 29 30 31 32 33 | # ====================================================================== # Q: CTO: "How do you process the payment provider's webhooks?" # # You: "The view does only three things: it verifies the HMAC signature, stores # the event with a unique event_id and responds 200 immediately. The processing # happens in a Celery task that is idempotent and changes the payment status # through a state machine. If the provider sends the same event twice, the # second one is ignored. A beat job resends any events that were left # unprocessed." # ====================================================================== @csrf_exempt def psp_webhook(request): if not verify_signature(request, settings.PSP_WEBHOOK_SECRET): return HttpResponse(status=400) event = json.loads(request.body) _, created = WebhookEvent.objects.get_or_create( event_id=event['id'], defaults={'payload': event}) if created: process_payment_event.delay(event['id']) return HttpResponse(status=200) @shared_task(acks_late=True) def process_payment_event(event_id): with transaction.atomic(): ev = WebhookEvent.objects.select_for_update().get(event_id=event_id) if ev.processed_at: return # already processed payment = Payment.objects.select_for_update().get( psp_reference=ev.payload['data']['reference']) payment.apply_event(ev.payload['type']) # allowed transitions only ev.processed_at = timezone.now() ev.save(update_fields=['processed_at']) |