Key Concepts

Key Concepts

Reference material that applies across all tokenized payment flows.


Idempotency

All write endpoints that create or modify payment state require an idempotency key in the X-ShopBack-Idempotent-Id header.

X-ShopBack-Idempotent-Id: 8a3f9c21-4b17-4e2d-9a31-dc702f81e934

Rules:

  • Use a UUID generated fresh for each logical operation attempt.
  • If a request times out or returns a 5xx, retry with the same X-ShopBack-Idempotent-Id. ShopBack will return the original result without creating a duplicate.
  • If you intentionally want a new pre-auth or charge (e.g. a retry after a decline), generate a new X-ShopBack-Idempotent-Id.

Endpoints that require this header:

EndpointNotes
POST /pre-authsPer logical pre-auth attempt
POST /chargePer logical charge attempt
⚠️

Don't reuse keys across different logical operations

A key collision between two different intended operations returns the first result, silently. Use a UUID library to generate keys — don't construct them manually.


Payment token lifecycle

StateDescription
LINKEDActive. Can be used for pre-auth and immediate charge.
UNLINKEDRevoked via POST /tokens/unlink or by ShopBack. Cannot be used for payment.

A token does not expire on a fixed date under normal circumstances. Treat it as valid until you receive a payment-token.invalid error, at which point prompt the user to relink.


Cashback

ShopBack users accumulate cashback that can be applied as a discount on payments. By default (useCashback: true), ShopBack applies available cashback balance to reduce the net charge amount.

To check available balance before charging:

POST /tokenized-payment/v1/tokens/cashback-balance
Authorization: Bearer <merchant_jwt>
Content-Type: application/json

{
  "paymentToken": "pt_sg_7829_a1b2c3d4e5f6..."
}

Response

{
  "cashbackBalance": 5.00,
  "currency": "SGD"
}

To disable cashback for a specific charge: set "useCashback": false in the pre-auth capture or immediate charge request.


Webhooks

ShopBack delivers an asynchronous order outcome notification to your callbackUrl after a capture or immediate charge completes PSP processing.

Webhook payload

{
  "order_uuid": "61cf9737-5cc3-421a-84c1-ba554c0674ba",
  "order_status": "SUCCESS",
  "order_context_token": "c9393a19-00b0-48cd-9a9e-fdcf0b3cdbb5",
  "cart_id": null,
  "webhook_url": "https://your-app.com/shopback/webhook"
}
FieldDescription
order_uuidShopBack order identifier. Matches orderUuid in the capture/charge response.
order_statusSUCCESS or FAILED.
order_context_tokenOpaque token for ShopBack internal correlation.
cart_idReserved. Always null for tokenized payments.
webhook_urlEchoes the callbackUrl you supplied.

Implementation checklist:

  • Respond with HTTP 200 as fast as possible. Do your processing asynchronously.
  • Verify order_uuid matches a known order in your system before acting on it.
  • Handle duplicate deliveries idempotently — the same webhook may be delivered more than once.
  • Do not block payment confirmation to your user on webhook delivery. Use the synchronous API response as the source of truth.

Error codes

All error responses follow this shape:

{
  "statusCode": 401,
  "error": "payment-token.invalid",
  "message": "Payment token not found or belongs to a different merchant"
}

Common error codes

CodeHTTPMeaningAction
payment-token.invalid401Token not found, unlinked, or from a different merchantPrompt user to relink
bad-request400Missing required field, invalid amount, or currency mismatchFix request before retrying
link-session.not-found404linkToken not found or from a different merchantDo not retry; start a new link session
auth-code.expired400Auth code TTL (60s) elapsedStart a new link session
auth-code.invalid400Code doesn't match stored value (or already used)Do not retry; start a new link session
pre-auth.not-found404Pre-auth ID not found or from a different merchantVerify the ID
pre-auth.already-captured409Capture already initiatedDo not retry capture
pre-auth.already-voided409Pre-auth was voidedCreate a new pre-auth if needed
pre-auth.declined409PSP declined authorizationInform user; offer alternative payment
pre-auth.expired409Pre-auth TTL elapsedCreate a new pre-auth
unauthorized401Missing or invalid merchant JWTRe-authenticate

Retry strategy

ScenarioRetry?Notes
Network timeout or 5xxYesUse the same X-ShopBack-Idempotent-Id
400 bad-requestOnly after fixingRetrying an unchanged request won't change the outcome
401 payment-token.invalidNo — relink firstToken is gone; prompt user to reconnect
409 conflict errorsNoThese reflect terminal state transitions
Auth code exchangeNo — relinkAuth code is single-use

API reference

EndpointReference page
POST /link-sessions/linkInitiate link session
GET /link-sessions/link/{linkToken}Get link session status
POST /link-sessions/tokenExchange auth code for payment token
POST /tokens/unlinkRevoke a payment token
POST /tokens/cashback-balanceGet cashback balance
POST /pre-authsCreate pre-authorization
GET /pre-auths/{id}Get pre-authorization
POST /pre-auths/{id}/captureCapture pre-authorization
POST /pre-auths/{id}/voidVoid pre-authorization
POST /chargeImmediate charge