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
- Generate a unique
noncefor the charge; store it in your database POST /payments/realtime(orPOST /payments): include thenoncein the request body- On timeout or error:
POST /payments/nonceto check whether the nonce was already processed - If found: surface the existing result; do not charge again
- 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-1inv-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-resultsevent 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
noncefield onPOST /refundsworks identically; usePOST /refunds/nonceto check whether a refund request was already processed before retrying. See Refunds.
What to do next
- Create Realtime Payment: full realtime payment reference
- Check Payment Nonce: nonce lookup endpoint reference
- Refunds: the same idempotency pattern applied to refunds
- Vault a Payment Source: handle token expiry on retry
Got feedback on this feature?
Send an email to [email protected] or book in a chat with our API team.
Updated about 10 hours ago
