Skip to content
← Back to blog
·2 min read·

Idempotency Is the Integration Feature You Actually Need

The integration bug that costs money is rarely a parsing error. It is the same operation running twice. Idempotency keys, retry budgets, and what to store.

Idempotency Is the Integration Feature You Actually Need

The integration bug that costs real money is almost never a parsing error. It is the same invoice going out twice because a webhook arrived twice and nobody planned for it.

Every delivery guarantee is at-least-once

Payment providers, CRMs, message queues and your own retry loop all promise delivery, and what they actually mean is that they will keep trying until you say yes. If your acknowledgement is lost on the way back, you get the message again. That is not a bug in their system, it is the only guarantee a network can offer, and the correct response is to make receiving something twice harmless.

An idempotency key is just a receipt

Give every incoming operation a key from the sender: the webhook's event id, the payment intent id, or a hash of the payload if the sender gives you nothing better. Before doing the work, write that key to a table with a unique constraint, in the same transaction as the work itself. If the insert fails on the constraint, you have already done it, so return the stored result instead of doing it again.

The important part is the same transaction. A key written before the work means a crash halfway leaves you thinking you are done. A key written after the work means a crash halfway leaves you doing it twice. Both happen in practice.

Make the outbound side safe too

The same applies when you are the caller. If you post a charge and the connection drops, you do not know whether it happened. Send your own idempotency key with the request, and most serious APIs will return the original result rather than charging again. Where an API does not support one, you need a way to ask it what it did, which usually means searching by your own reference before retrying.

Retries need a budget

Retry immediately and you hammer a service that is already struggling. Retry forever and a poisoned message blocks the queue behind it. Back off exponentially with a cap, give up after a bounded number of attempts, and put what you gave up on somewhere a human can see it. A dead letter queue nobody looks at is the same as no dead letter queue.

Log the decision, not just the event

When something has gone wrong in production, the question is always "did we process this, and what did we decide". Store the key, the outcome and the time. It turns an argument with a vendor into a query. It also means that when a client asks why a customer was billed twice in March, you can answer in a minute rather than a day.

None of this is clever. It is the difference between an integration you trust and one you check by hand every morning.

#integrations#APIs#webhooks#idempotency#reliability