Idempotency
How MintCash makes safe retries safe. Use externalId on every charge so duplicate requests never cost your customer twice.
Network calls fail. When they do, you retry. With charge endpoints that's terrifying — a retry that "succeeded last time but timed out" can double-charge a customer.
MintCash solves this with idempotency keys. Every charge endpoint takes an externalId — your own unique identifier for that order or invoice. If we receive the same externalId twice, we return the original resource and never re-charge the card.
How it works
The first request creates the payment and stores everything we'd need to return on a replay — the payment row, the provider's response, the redirectUrl. The second request, with the same externalId, never touches the provider. You get the same response back.
Where to use it
| Endpoint | Idempotency field | Required? |
|---|---|---|
POST /payments | externalId | Yes |
POST /subscriptions | externalId | Yes |
POST /payments/{id}/refund | externalId (per refund) | Yes |
externalId is scoped to your merchant — different merchants can use the same string without conflict, and we only look at it within your account.
What counts as a duplicate
Two requests are treated as duplicates if they have the same externalId and the same payload shape. If the payload differs (different amount, different currency, different customer), the second request is rejected with already_exists — not silently replayed against a different intent.
Good externalIds
Pick something that's stable, unique, and meaningful in your system:
- Your order ID:
order_2026_05_22_001 - Your invoice ID:
inv_8347 - A UUID generated when you start the checkout flow
Avoid using anything you might re-emit for a different purpose. customer_id is wrong (one per customer, infinite charges per customer). Timestamps alone are wrong (two parallel checkouts could collide).
Combine with webhook deduplication
Idempotency keys protect the request side. On the webhook side, every event has an eventId that's stable across retries — use it as a dedupe key on your handler:
async function handleWebhook(event: WebhookEvent) {
const exists = await db.processedEvents.exists(event.eventId);
if (exists) return; // already handled
await db.transaction(async (tx) => {
await fulfillOrder(event.data);
await tx.processedEvents.insert(event.eventId);
});
}Two layers, one promise
externalId on requests means we never double-charge. eventId on webhooks
means you never double-fulfil. Together, your integration is idempotent
end-to-end.