Pre-Authorization
Pre-authorization places a hold on a user's linked payment method without immediately capturing the funds. When your service completes and the final amount is known, you capture (settle) the hold. If the service doesn't proceed, you void it to release the funds.
Prerequisite: a paymentToken obtained from Account Linking.
Flow overview
sequenceDiagram
autonumber
participant ME as Merchant
participant SB as ShopBack API
ME->>SB: POST /tokenized-payment/v1/pre-auths
Note over ME,SB: paymentToken, amount, currency, merchantRef
Note over ME,SB: Header — X-ShopBack-Idempotent-Id: <uuid>
SB-->>ME: 201 { id, status: AUTHORIZED, amount, currency }
Note over ME,SB: Service runs — final amount determined
ME->>SB: POST /tokenized-payment/v1/pre-auths/{id}/capture
Note over ME,SB: { callbackUrl }
SB-->>ME: 201 { orderUuid, status }
SB-)ME: POST callbackUrl (async webhook)
Note over ME,SB: { order_uuid, order_status: "SUCCESS" }
Pre-auth status lifecycle
AUTHORIZED ────────────────────────────────────┐
│ │
▼ ▼
CAPTURE_INITIATED → CAPTURED VOIDED
│
▼
DECLINED (authorization failed at PSP)
EXPIRED (pre-auth not acted on within TTL)
Poll GET /tokenized-payment/v1/pre-auths/{id} after any write operation to confirm the transition.
Step-by-step
1. Create a pre-authorization
Request
POST /tokenized-payment/v1/pre-auths
Authorization: Bearer <merchant_jwt>
Content-Type: application/json
X-ShopBack-Idempotent-Id: 8a3f9c21-4b17-4e2d-9a31-dc702f81e934
{
"paymentToken": "pt_sg_7829_a1b2c3d4e5f6...",
"merchantUserId": "usr_7829",
"amount": 25.00,
"currency": "SGD",
"merchantRef": "trip-4821",
"merchantMetadata": {
"pickupZone": "CBD",
"vehicleClass": "standard"
}
}| Field | Required | Description |
|---|---|---|
paymentToken | Yes | Token from account linking, scoped to this user and your channel. |
merchantUserId | Yes | Must match the merchantUserId used during account linking. |
amount | Yes | Hold amount in major units (e.g. 25.00 for SGD 25.00). Must be > 0. |
currency | Yes | 3-letter ISO 4217 code. Must match your merchant channel's configured currency. |
merchantRef | Yes | Your stable, unique reference for this transaction (e.g. trip ID, booking ID). Used for idempotency — same merchantRef returns the same pre-auth within the idempotency window. |
merchantMetadata | No | Arbitrary JSON object. Max 5 keys, 200 characters per value. |
X-ShopBack-Idempotent-Id | Yes (header) | UUID unique to this logical pre-auth attempt. See Idempotency. |
Response
{
"id": "9d2e1a87-3c4f-4b12-a891-cc702f81e123",
"merchantRef": "trip-4821",
"status": "AUTHORIZED",
"amount": 25.00,
"currency": "SGD",
"orderUuid": null,
"orderStatus": null,
"merchantMetadata": {
"pickupZone": "CBD",
"vehicleClass": "standard"
},
"createdAt": "2026-06-24T09:00:00.000Z",
"updatedAt": "2026-06-24T09:00:00.000Z"
}A status of AUTHORIZED means funds are held. If you receive DECLINED, the hold was not placed — do not proceed with the service.
2. Poll for status (if needed)
If your create request times out or you need to confirm the hold before starting the service, poll:
GET /tokenized-payment/v1/pre-auths/{id}
Authorization: Bearer <merchant_jwt>Poll until status is AUTHORIZED or a terminal state (DECLINED, EXPIRED).
3. Capture the pre-authorization
Once your service completes and the final amount is known, capture the hold to settle funds and create a ShopBack order.
Capture amountThe current API captures the full pre-auth amount. If your final amount differs, contact ShopBack about partial capture support.
Request
POST /tokenized-payment/v1/pre-auths/9d2e1a87-.../capture
Authorization: Bearer <merchant_jwt>
Content-Type: application/json
{
"callbackUrl": "https://your-app.com/shopback/webhook",
"useCashback": true
}| Field | Required | Description |
|---|---|---|
callbackUrl | Recommended | HTTPS URL that ShopBack POSTs the order outcome to once the PSP confirms. |
useCashback | No | Apply the user's available ShopBack cashback to this payment. Defaults to true. |
Response
{
"uuid": "9d2e1a87-3c4f-4b12-a891-cc702f81e123",
"orderUuid": "61cf9737-5cc3-421a-84c1-ba554c0674ba",
"status": "APPROVED",
"orderType": "ONLINE",
"merchantRef": "trip-4821",
"merchantOrderId": "SB-20260624-001",
"consumerEmail": "u***@example.com",
"failureReason": null,
"createdAt": "2026-06-24T09:45:00.000Z"
}4. Receive the webhook
If you provided a callbackUrl, ShopBack POSTs the final order outcome asynchronously once the PSP confirms:
{
"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"
}
Treat the synchronous capture response as the source of truthThe webhook is best-effort. Always use the capture response
statusto determine payment outcome for your user. Use the webhook for reconciliation and downstream processing.
Voiding a pre-authorization
If the service doesn't proceed and you want to release the held funds before they expire:
Request
POST /tokenized-payment/v1/pre-auths/9d2e1a87-.../void
Authorization: Bearer <merchant_jwt>
Content-Type: application/json
{
"reason": "User cancelled before service started"
}Response
{
"uuid": "9d2e1a87-3c4f-4b12-a891-cc702f81e123",
"status": "VOIDED"
}
Void only works onAUTHORIZEDpre-authsYou cannot void a pre-auth that has already been captured, declined, or expired.
Error responses
| HTTP status | Error code | Meaning |
|---|---|---|
400 | bad-request | Missing required field, amount ≤ 0, or currency mismatch |
401 | payment-token.invalid | Token not found, unlinked, or belongs to a different merchant |
404 | pre-auth.not-found | Pre-auth ID not found or belongs to a different merchant |
409 | pre-auth.already-captured | Capture already initiated — do not retry |
409 | pre-auth.already-voided | Pre-auth was voided |
409 | pre-auth.declined | Authorization declined by PSP |
409 | pre-auth.expired | Pre-auth TTL elapsed without capture or void |
Next step
- Immediate Charge — if you need one-shot payments
- Key Concepts — idempotency, cashback, retries