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-dc702f81e934Rules:
- 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:
| Endpoint | Notes |
|---|---|
POST /pre-auths | Per logical pre-auth attempt |
POST /charge | Per logical charge attempt |
Don't reuse keys across different logical operationsA 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
| State | Description |
|---|---|
| LINKED | Active. Can be used for pre-auth and immediate charge. |
| UNLINKED | Revoked 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"
}| Field | Description |
|---|---|
order_uuid | ShopBack order identifier. Matches orderUuid in the capture/charge response. |
order_status | SUCCESS or FAILED. |
order_context_token | Opaque token for ShopBack internal correlation. |
cart_id | Reserved. Always null for tokenized payments. |
webhook_url | Echoes the callbackUrl you supplied. |
Implementation checklist:
- Respond with HTTP
200as fast as possible. Do your processing asynchronously. - Verify
order_uuidmatches 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
| Code | HTTP | Meaning | Action |
|---|---|---|---|
payment-token.invalid | 401 | Token not found, unlinked, or from a different merchant | Prompt user to relink |
bad-request | 400 | Missing required field, invalid amount, or currency mismatch | Fix request before retrying |
link-session.not-found | 404 | linkToken not found or from a different merchant | Do not retry; start a new link session |
auth-code.expired | 400 | Auth code TTL (60s) elapsed | Start a new link session |
auth-code.invalid | 400 | Code doesn't match stored value (or already used) | Do not retry; start a new link session |
pre-auth.not-found | 404 | Pre-auth ID not found or from a different merchant | Verify the ID |
pre-auth.already-captured | 409 | Capture already initiated | Do not retry capture |
pre-auth.already-voided | 409 | Pre-auth was voided | Create a new pre-auth if needed |
pre-auth.declined | 409 | PSP declined authorization | Inform user; offer alternative payment |
pre-auth.expired | 409 | Pre-auth TTL elapsed | Create a new pre-auth |
unauthorized | 401 | Missing or invalid merchant JWT | Re-authenticate |
Retry strategy
| Scenario | Retry? | Notes |
|---|---|---|
| Network timeout or 5xx | Yes | Use the same X-ShopBack-Idempotent-Id |
400 bad-request | Only after fixing | Retrying an unchanged request won't change the outcome |
401 payment-token.invalid | No — relink first | Token is gone; prompt user to reconnect |
409 conflict errors | No | These reflect terminal state transitions |
| Auth code exchange | No — relink | Auth code is single-use |
API reference
| Endpoint | Reference page |
|---|---|
POST /link-sessions/link | Initiate link session |
GET /link-sessions/link/{linkToken} | Get link session status |
POST /link-sessions/token | Exchange auth code for payment token |
POST /tokens/unlink | Revoke a payment token |
POST /tokens/cashback-balance | Get cashback balance |
POST /pre-auths | Create pre-authorization |
GET /pre-auths/{id} | Get pre-authorization |
POST /pre-auths/{id}/capture | Capture pre-authorization |
POST /pre-auths/{id}/void | Void pre-authorization |
POST /charge | Immediate charge |