Duplicates and idempotency
By the end of this track you can receive and trust a result.
0/3 finished
Step 1
The idea
The same callback can arrive twice, and processing it twice credits the order twice. Make the receipt unique in your database so the second copy changes nothing. Idempotency means doing a thing twice has the same effect as doing it once.
Step 2
How it works
Why duplicates happen
Daraja occasionally sends a callback twice, some products retry (C2B and Bill Manager), and your status job may confirm a payment the callback also confirms. Any of these can bring the same result to your code twice.
Let the database say no
A UNIQUE constraint is stronger than an "already seen?" check in code, because two copies arriving at the same moment can both pass the check. With the constraint, the second insert fails and you ignore it.
CREATE TABLE payments ( id BIGSERIAL PRIMARY KEY, order_id TEXT NOT NULL, checkout_id TEXT UNIQUE, receipt TEXT UNIQUE, amount BIGINT NOT NULL, status TEXT NOT NULL DEFAULT 'accepted');Only move forward
When a result arrives, update the row only if it isn't final yet. A second copy then changes nothing, and a late "failed" can't overwrite a "confirmed".
UPDATE payments SET status = 'confirmed', receipt = :receipt WHERE checkout_id = :checkout_id AND status = 'accepted';Step 3
Try it
POST the same sample callback to your receiver twice, or reason it through with a small table.
Step 4
Common mistakes
- Checking for duplicates in code without a database constraint.
- Updating a payment that is already confirmed or failed.
- Sending the customer a second receipt email for a duplicate callback.
Go deeper
Which column gets a UNIQUE constraint?
Answer in your own words first.
Show the answerHide the answer
The M-Pesa receipt, and the CheckoutRequestID.