Overview
Webhooks use at-least-once delivery. PulseGrid normally sends once, but it retries temporary failures. Networks can also lose the receiver's response after the receiver has already completed the work.
Therefore, the same delivery may reach the receiver more than once.
PulseGrid keeps the same delivery_id for every retry. The receiving backend must place a unique database constraint on that value or record it transactionally before performing irreversible work.
Idempotency prevents duplicate orders, payments, SMS messages, emails and workflow starts.
Setup
Recommended processing order:
1. Verify the signature.
2. Begin a database transaction.
3. Attempt to create a ProcessedWebhook row using delivery_id as a unique key.
4. If that key already exists, return HTTP 200 without repeating the work.
5. Perform the required database work.
6. Commit the transaction.
7. Return HTTP 200.
For slow external actions, store the accepted event and queue the work locally before returning 200.
Code example
from django.db import IntegrityError, transaction
def process_once(event):
delivery_id = event["delivery_id"]
try:
with transaction.atomic():
ProcessedWebhook.objects.create(delivery_id=delivery_id)
if event["event"] == "order.created":
create_order_from_event(event)
except IntegrityError:
# This delivery was already accepted and processed.
return "duplicate"
return "processed"
Notes
Do not use the event name alone as a duplicate key. Thousands of legitimate events may share the name order.created.
Use delivery_id to deduplicate deliveries. You may additionally store id, the original event ID, for tracing an event across multiple destinations.