All documentation
Messages

Webhooks: Duplicate Protection and Idempotency

Make retries safe by ensuring each webhook delivery performs business work only once.

Python

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

Webhooks: Duplicate Protection and Idempotency
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.