Idempotency & concurrency
How the Universal Wallet keeps deductions race-free and retries safe.
The wallet is designed so that retries and concurrent calls can never overcharge or double-credit a user.
Atomic deductions
POST /io/wallet/deduct performs the balance check and the debit as a single atomic operation in the ledger. Two requests racing to spend the last credit cannot both succeed — one is debited and the other returns allowed: false. You never need to read-then-write the balance yourself; doing so would introduce a race the atomic deduct already avoids.
Idempotent deductions
Pass a stable idempotency_key whenever the same logical charge might be retried — a network blip, a queue redelivery, or a user double-click.
curl -X POST "https://api.misar.io/io/wallet/deduct" \
-H "x-wallet-service-key: $WALLET_SERVICE_KEY" \
-H "Content-Type: application/json" \
-d '{
"user_id": "usr_123",
"feature": "blog.article.generate",
"idempotency_key": "article:my-post-slug"
}'Re-sending the same idempotency_key returns the original result and does not charge again.
Pick a key tied to the unit of work
Good keys are deterministic from the work itself — an article slug, a job id, a message id. Avoid random keys generated per attempt; a fresh key on each retry defeats the protection.
Idempotent top-ups
Credit grants are keyed on the Stripe checkout session id. Stripe may deliver checkout.session.completed more than once; the webhook credits a given session exactly once. Redelivery is a no-op.
Best practices
Deduct before the work
Charge first, then run the billable feature only if allowed is true.
Use a deterministic idempotency key
Derive it from the unit of work so all retries collapse to one charge.
Never read-modify-write the balance
Rely on the atomic deduct — do not fetch the balance, subtract, and write it back.