← All writing

A unique database key is the foundation of duplicate webhook handling

MySQL 8.4 / InnoDBSources checked 2026-09-12

A preliminary SELECT cannot prevent two concurrent inserts.

A provider sends the same event twice. Two workers both check whether the event exists, both see nothing, and both process it. The initial SELECT is not a concurrency guarantee.

Enforce event identity in the database

CREATE TABLE webhook_receipts (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    provider VARCHAR(40) NOT NULL,
    event_id VARCHAR(191) NOT NULL,
    received_at TIMESTAMP NOT NULL,
    UNIQUE KEY provider_event (provider, event_id)
);

For a multi-account integration, include the provider account or tenant in the unique identity when event IDs are not globally unique. Choose a case-sensitive collation if the provider treats differently cased identifiers as distinct.

Keep receipt and effect consistent

For an effect entirely within one database, insert the receipt and apply the business change in one transaction. A rollback should remove both. If a competing transaction already inserted the same event identity, treat that specific duplicate as a replay.

Do not suppress every database error as a duplicate. A connection failure or missing column is an operational failure and should remain visible.

External effects need more state

Sending email or calling another service is outside the database transaction. Persist a durable pending action and use the downstream provider's idempotency mechanism when available. A single receipt row cannot make two independent systems commit together.

Test repeated delivery sequentially, then concurrently, and then with a failure halfway through processing. Verify the business record count, not only the webhook HTTP response.

Event identity is a business contract. A timestamp rounded to a second or a hash of a changing payload may not represent the same event consistently. MySQL's CREATE TABLE reference documents unique constraints; your provider's event specification determines the correct key.