Mintcash
Concepts

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

Rendering diagram…

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

EndpointIdempotency fieldRequired?
POST /paymentsexternalIdYes
POST /subscriptionsexternalIdYes
POST /payments/{id}/refundexternalId (per refund)Yes

What counts as a duplicate

Any request with an externalId we've already seen is treated as a replay: we return the original resource and never touch the provider again. The rest of the payload is not compared — a request that reuses an externalId with a different amount or customer still gets the original resource back, silently. Make sure an externalId maps to exactly one intent in your system; never recycle one for a different charge.

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.