Skip to main content

Deduplicate webhook deliveries by provider event ID

2026-11-11 16:36:27.093227+00:00
Deduplicate webhook deliveries by provider event ID

A provider may deliver the same webhook repeatedly until it receives an acknowledgement. A durable unique event record lets the application accept redelivery without repeating the business update.

Repeated deliveries converge on a unique provider-account and event-ID record before processing.

Enforce the event identity in the database

This toy Django model scopes an event ID to its provider account. Real providers define the appropriate identity scope.

from django.db import models, transaction

class InboundEvent(models.Model):
    provider_account = models.CharField(max_length=100)
    event_id = models.CharField(max_length=200)
    status = models.CharField(max_length=20, default="pending")

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["provider_account", "event_id"],
                name="inbound_account_event_unique",
            )
        ]

def accept_event(account, event_id):
    with transaction.atomic():
        event, created = InboundEvent.objects.get_or_create(
            provider_account=account, event_id=event_id
        )
    return event.pk, created

Verify the request signature and validate the event schema before this service runs. In a complete ingestion service, store the necessary payload or a durable reference in the same transaction; an ID with no retrievable event content is insufficient for later processing.

The created flag distinguishes a first accepted event from a repeated delivery. The uniqueness constraint is what makes the identity hold when two requests arrive together. A prior "does it exist?" query without that constraint would leave a race.

Separate acceptance from processing

Return the provider's required acknowledgement only after the event is durably recorded. A worker can then claim pending events and apply the business operation. A crash after acceptance should leave recoverable work rather than an acknowledged event held only in process memory.

The worker needs its own duplicate-safe state transition. Accepting an event once does not prevent a queue from delivering its processing task twice. If a processing failure is transient, retain the event and attempt information so a reconciler can revisit it.

Test two concurrent deliveries on the intended database backend. Confirm there is one event row and that both HTTP requests receive the appropriate response. Then simulate a worker failure and verify the accepted event remains available for retry.

Choose retention from the provider's replay behavior and the application's audit needs. Deleting deduplication records too soon can make an old event look new again. Retaining payloads forever can create unnecessary sensitive-data storage.

The event-driven publishing context in Postzyo is related. Django documents constraints and transactions.