1
0 Comments

Why we added idempotency keys to Adal

One of the less obvious problems with webhooks is that successful processing and successful delivery confirmation are not the same thing.

A webhook provider may send a request, your service may process it successfully, and then the HTTP response may be lost because of a timeout or network issue.

From the sender’s perspective, the delivery failed.

From your service’s perspective, the business action already happened.

The sender retries.

Now your system may create another order, send another email, create another CRM record, trigger another deployment, or charge another payment.

That is why webhook consumers should not assume that a webhook will arrive exactly once.

They should assume that the same logical event may be delivered multiple times.

The tempting but unreliable approach

A common idea is to detect duplicates by comparing request bodies.

For example:

  • save the webhook payload;

  • calculate a hash;

  • reject the next request with the same contents.

The problem is that identical payloads do not necessarily represent the same event.

Two invoices can have the same amount and currency. Two users can submit the same form. Two separate events can contain identical JSON.

The payload is data. It is not a reliable event identity.

The better approach: an explicit key

The usual solution is an idempotency key.

Instead of guessing whether two requests are the same, the sender provides a unique identifier for one logical operation.

The receiver stores that key and treats any later request with the same key as a repeat.

A simple implementation can look like this:

INSERT INTO processed_webhooks (idempotency_key, processed_at) VALUES ($1, NOW()) ON CONFLICT (idempotency_key) DO NOTHING;

If the insert succeeds, the webhook is new.

If the key already exists, the service can safely return success without repeating the side effect.

The important detail is that this needs to be atomic. A “check first, then insert” flow can still create duplicates when two deliveries arrive at the same time.

Why this belongs in Adal

Not every webhook provider gives you a useful event ID or idempotency key.

Some include an identifier inside the payload. Some expose it in headers. Some provide no stable identity at all.

So we added an optional idempotency header to Adal.

When enabled for a destination, Adal adds:

X-Adal-Idempotency: <unique-key>

The receiving service can use that value to recognize repeated deliveries safely.

The setting is enabled per destination, not globally.

That was deliberate.

Some destinations need the original request to remain untouched. Others are internal services where an idempotency key is exactly what prevents duplicate work. We did not want to force one behavior on every integration.

Retry and replay are different operations

The key behavior is intentionally explicit:

Automatic retry of the same request → same idempotency key Manual retry of the same delivery → same idempotency key Replay of an older webhook → new idempotency key

A retry is another attempt to deliver the same logical request.

A replay is a deliberate creation of a new request based on an older webhook.

Even if the payload is identical, replaying it is a new action. It should not be silently ignored by the receiver as if it were only another failed delivery attempt.

That distinction makes retries safe without making replay useless.

A small feature with a large effect

Idempotency keys are often discussed in the context of payment APIs, but they matter anywhere a webhook causes a side effect:

  • creating orders;

  • provisioning infrastructure;

  • sending emails;

  • triggering background jobs;

  • synchronizing CRM records;

  • creating tickets;

  • calling external APIs;

  • processing queue-like workflows.

The more expensive or irreversible the action is, the less acceptable duplicate execution becomes.

Reliable webhook delivery is not only about retrying when something fails.

It is also about making retries safe for the systems that receive them.

That is the goal behind X-Adal-Idempotency in Adal.

Adal is a webhook delivery and observability platform built around predictable delivery behavior, retries, replays, and clear request history.

Read the full article: https://adal.cloud/blog/2026-07-06-why-webhooks-need-idempotent-processing

posted toAvatar for product Adal
Adal