Idempotent Payment Submission with Nonce

Safely retry failed payment requests without risk of double-charging.

Persona: E-commerce checkout, server-to-server integrator, any integration where network unreliability or server retries could cause duplicate charges.

Scenario: Your server submits a realtime credit card charge. The HTTP request times out before a response arrives. Before retrying, you need to know whether the original charge already went through, to avoid double-charging the customer. The nonce field is your safeguard.


Endpoint sequence

  1. Generate a unique nonce for the charge; store it in your database
  2. POST /payments/realtime (or POST /payments): include the nonce in the request body
  3. On timeout or error: POST /payments/nonce to check whether the nonce was already processed
  4. If found: surface the existing result; do not charge again
  5. If not found: retry with the same nonce value

Step 1: Generate and store a nonce

Create a unique string for this specific charge before making the API call. Store it alongside the order in your database so you can reference it if the request fails.

const nonce = `order-${orderId}-${Date.now()}`;
await db.orders.saveNonce(orderId, nonce);

A good nonce includes identifiers that make it naturally unique per intended charge:

  • order-ORD-9876-attempt-1
  • inv-INV-456-2026-08-01

Step 2: Submit the payment with the nonce

Include the nonce field in your payment request.

Realtime credit card charge:

POST https://api.getpinch.com.au/test/payments/realtime
Authorization: ******
pinch-version: 2020.1
Content-Type: application/json

{
  "payerId": "pyr_XXXXXXXX",
  "amount": 9900,
  "description": "Invoice INV-456",
  "creditCardToken": "{card_token}",
  "nonce": "order-ORD-9876-attempt-1"
}

Scheduled payment:

POST https://api.getpinch.com.au/test/payments
Authorization: ******
pinch-version: 2020.1
Content-Type: application/json

{
  "payerId": "pyr_XXXXXXXX",
  "sourceId": "src_XXXXXXXX",
  "amount": 9900,
  "description": "Invoice INV-456",
  "transactionDate": "2026-08-01",
  "nonce": "order-ORD-9876-attempt-1"
}

Step 3: Check the nonce after a timeout or error

If the request times out (no response received) or returns a 5xx error, do not immediately retry. First check whether the nonce was already processed:

POST https://api.getpinch.com.au/test/payments/nonce
Authorization: ******
pinch-version: 2020.1
Content-Type: application/json

{
  "nonce": "order-ORD-9876-attempt-1"
}

Nonce found: the original request was processed. The response returns the existing payment record:

{
  "id": "pmt_XXXXXXXX",
  "status": "pending",
  "amount": 9900,
  "nonce": "order-ORD-9876-attempt-1"
}

Surface this result to your customer. Do not submit another charge.

Nonce not found: the original request did not complete. It is safe to retry.

Step 4: Retry with the same nonce

If the nonce lookup returns no result, retry the original payment request with the exact same nonce value:

POST https://api.getpinch.com.au/test/payments/realtime
Authorization: ******
pinch-version: 2020.1
Content-Type: application/json

{
  "payerId": "pyr_XXXXXXXX",
  "amount": 9900,
  "description": "Invoice INV-456",
  "creditCardToken": "{card_token}",
  "nonce": "order-ORD-9876-attempt-1"
}

Pinch deduplicates on the nonce: if the first attempt somehow went through between your nonce check and this retry, Pinch will return the existing payment instead of creating a duplicate.


Key caveats

  • Nonces must be globally unique per intended charge. Reusing the same nonce for a genuinely new transaction (e.g. a second order from the same customer) will return the result of the first transaction rather than creating a new payment.
  • Always generate the nonce before the API call. If you generate it after a failure, you lose the ability to check whether the original request went through.
  • A successful nonce check confirms submission, not collection. For realtime credit card payments, the nonce check confirms the payment was created. For direct debit scheduled payments, the payment still needs to clear through the banking network; await the bank-results event before treating funds as collected.
  • Credit card tokens are single-use. If a realtime payment request fails and the CaptureJS token has expired (tokens are short-lived (see Vault a Payment Source), you cannot retry using the same token. You will need to re-collect the card details from the customer.
  • Same pattern applies to refunds. The nonce field on POST /refunds works identically; use POST /refunds/nonce to check whether a refund request was already processed before retrying. See Refunds.

What to do next

📘

Got feedback on this feature?

Send an email to [email protected] or book in a chat with our API team.


Did this page help you?