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

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.