Erasure

Blog

Written by the Erasure product and engineering team

Part of Data deletion

Webhook Retries and Idempotency for Deletion Workflows

When a deletion job depends on webhooks, retries are guaranteed and duplicates are certain. An idempotency key and a signature check are what keep the system safe.

The moment your deletion workflow touches a webhook, two facts take over: the delivery will fail sometimes, and the retry will arrive more than once. If your receiver is not idempotent, the second delivery is not a duplicate—it is a second deletion event hitting a system that already acted. This is how you build a receiver that survives retries.

Retries are not a bug

Webhooks are delivered over HTTP with no transaction. The sender cannot know whether the receiver processed the event or crashed after storing it, so it retries. That means your receiver must be able to process the same event twice with the same result. That property has a name: idempotency.

The standard mechanism is an idempotency key. Every event carries a unique ID, and the receiver records which IDs it has already processed:

-- Track processed events so retries are no-ops.
CREATE TABLE webhook_events_processed (
  event_id      TEXT PRIMARY KEY,
  processed_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

On each delivery, check the ID first. If it exists, return a success response without doing the work again:

INSERT INTO webhook_events_processed (event_id)
VALUES ($1)
ON CONFLICT (event_id) DO NOTHING
RETURNING processed_at;

If the insert returns a row, this is the first time—do the work. If it returns nothing, it is a retry—acknowledge and stop. The uniqueness constraint makes the race safe even if two deliveries arrive at once.

Verify the signature before you trust the payload

Any webhook endpoint on the public internet will receive forged payloads. Verify the signature before doing anything else.

For HMAC-signed webhooks the pattern is consistent: compute the HMAC over the raw body with the shared secret and compare against the header, using a constant-time comparison so timing does not leak the secret:

import crypto from "node:crypto";

function validSignature(rawBody: string, signature: string, secret: string) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  );
}

Two rules that matter: hash the raw body, not a re-parsed version, or the signature will not match; and never log the signing secret or put it in client-side code.

The receiver ordering that keeps you safe

Do the checks in the right order:

  1. Verify the signature. Reject anything that does not match.
  2. Check the event ID against the processed table. Return success if already handled.
  3. Process the work (delete, notify, enqueue).
  4. Record the event ID in the same transaction as the work, if possible, so a crash between "did the work" and "recorded the ID" still leads to a safe retry.

The worst failure is doing the work, failing to record the ID, then crashing—because the retry runs the deletion twice. Doing the work and recording the ID in one transaction closes that gap.

A webhook is not a delete

A webhook tells another system "this user asked to be deleted." It does not delete anything itself. If the receiving system is down, retries are limited, and the event is dropped, the deletion silently never happens.

For anything that must actually be erased, prefer a direct delete path (a database connector or an API call your job controls) and use the webhook only to notify systems you cannot reach inbound. Treat the webhook as a notification with a best-effort delivery, not as the source of truth for erasure.

What durable deletion looks like

The robust shape is a job queue, not a pile of HTTP calls. A job is persisted, claimed by a worker, retried with backoff on failure, and its outcome recorded. If the process dies, the job survives and resumes. That is the difference between "we fired a webhook" and "we ran a deletion and can prove it."

This is the model Erasure uses: durable deletion jobs run by a worker, with retries and honest outcomes, and a signed webhook system for notifying systems it cannot reach directly. The consent side also delivers signed consent.updated webhooks with an HMAC signature you verify on the raw body.

The data deletion hub has the broader workflow, and the deletion requests guide covers the full loop from intake to evidence.

About this post

Written by the Erasure product and engineering team

Published 6 August 2026

Part of Data deletion

This article is grounded in Erasure's product documentation and explains engineering and operational implications. Where it discusses regulation, it is not legal advice. See our editorial policy.

← All posts · Docs