---
updatedAt: 2026-06-24T04:32:44.000Z
agentTools:
  projectIndex: https://docs.shopback.com/llms.txt
---

# Account Linking

# Account Linking

Account linking is the one-time flow that authorises your app to charge a ShopBack user. At the end of this flow you receive a `paymentToken` — a long-lived token scoped to a specific user and your merchant channel. Store it server-side and reuse it for all future payments for that user.

> 📘 **One token per user**
>
> If the same user links again, ShopBack returns the same `paymentToken`. You don't need to update your records.

***

## Flow overview

```mermaid
sequenceDiagram
    autonumber
    participant App as Merchant App
    participant BE as Merchant Backend
    participant SB as ShopBack API

    App->>BE: User taps "Link ShopBack account"
    BE->>SB: POST /tokenized-payment/v1/link-sessions/link
    SB-->>BE: 200 { linkToken, redirectUrl, appToken, expiresAt }
    BE-->>App: redirectUrl + appToken
    App->>SB: Open redirectUrl in in-app browser
    Note over App,SB: Header — X-ShopBack-App-Token: appToken
    Note over SB: User reviews and approves on ShopBack consent page
    SB-->>App: Redirect to callbackUrl?state=<state>&code=<authCode>
    App->>BE: Forward authCode
    BE->>SB: POST /tokenized-payment/v1/link-sessions/token
    SB-->>BE: 200 { paymentToken }
    Note over BE: Store paymentToken against merchantUserId
```

***

## Step-by-step

### 1. Initiate a link session

Your backend calls `POST /tokenized-payment/v1/link-sessions/link` to create a new link session.

**Request**

```http
POST /tokenized-payment/v1/link-sessions/link
Authorization: Bearer <merchant_jwt>
Content-Type: application/json

{
  "callbackUrl": "https://your-app.com/shopback/callback",
  "state": "csrf-nonce-abc123",
  "merchantUserId": "usr_7829",
  "userHint": {
    "phone": "+6591xxxx67"
  }
}
```

| Field            | Required                        | Description                                                                                                                 |
| ---------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `callbackUrl`    | Yes                             | HTTPS URL ShopBack redirects to after consent. Must be on the allowlist for your merchant channel.                          |
| `state`          | Yes                             | A CSRF nonce you generate. ShopBack echoes it back in the redirect so you can verify the response originates from ShopBack. |
| `merchantUserId` | Yes                             | Your opaque, stable identifier for this user. Must match the `merchantUserId` you'll use in payment requests.               |
| `userHint.phone` | One of `phone`/`email` required | A pre-redacted hint displayed on the ShopBack consent page to confirm identity.                                             |
| `userHint.email` | One of `phone`/`email` required | As above.                                                                                                                   |

**Response**

```json
{
  "linkToken": "550e8400-e29b-41d4-a716-446655440000",
  "redirectUrl": "https://pay.shopback.com/link",
  "appToken": "eyJhbGciOiJIUzI1NiJ9...",
  "expiresAt": "2026-06-24T10:20:00.000Z"
}
```

| Field         | Description                                                                               |
| ------------- | ----------------------------------------------------------------------------------------- |
| `linkToken`   | Identifies this session. Use it to poll session status if needed.                         |
| `redirectUrl` | The ShopBack consent page URL. Open this in your in-app browser.                          |
| `appToken`    | A short-lived JWT. Pass as `X-ShopBack-App-Token` header when opening the in-app browser. |
| `expiresAt`   | Session TTL — 20 minutes from creation. Start the flow before this time.                  |

***

### 2. Open the consent page in your in-app browser

Open `redirectUrl` inside your app using an in-app browser (iOS: `SFSafariViewController`, Android: Custom Tabs). Pass `appToken` as a custom HTTP header:

```
X-ShopBack-App-Token: <appToken>
```

> ⚠️ **Do not open in a standard WebView**
>
> ShopBack's consent page requires cookies and secure storage that standard WebViews may block. Use `SFSafariViewController` or Chrome Custom Tabs.

ShopBack will display the user's linked payment method and prompt them to authorise your app.

***

### 3. Handle the redirect back to your app

Once the user approves (or declines), ShopBack redirects the in-app browser to your `callbackUrl`:

```
https://your-app.com/shopback/callback
  ?state=csrf-nonce-abc123
  &code=a1b2c3d4e5f6
```

In your app:

1. Verify `state` matches what you sent in step 1 (CSRF check).
2. Extract the `code` (auth code). It expires in **60 seconds** — swap it immediately.
3. Close the in-app browser and pass `code` to your backend.

> ⚠️ **Auth code is single-use and expires in 60 seconds**
>
> Do not store or display it. Send it straight to your backend for the token swap.

***

### 4. Exchange the auth code for a payment token

Your backend calls `POST /tokenized-payment/v1/link-sessions/token` with the one-time code.

**Request**

```http
POST /tokenized-payment/v1/link-sessions/token
Authorization: Bearer <merchant_jwt>
Content-Type: application/json

{
  "code": "a1b2c3d4e5f6"
}
```

**Response**

```json
{
  "paymentToken": "pt_sg_7829_a1b2c3d4e5f6..."
}
```

Store `paymentToken` server-side mapped to your `merchantUserId`. This token is:

* **Long-lived** — no expiry under normal circumstances.
* **Reusable** — use it for every subsequent pre-auth or immediate charge for this user.
* **Revocable** — call `POST /tokenized-payment/v1/tokens/unlink` to revoke it if the user disconnects.

***

## Unlinking

To revoke a user's payment token (e.g. if they request disconnection):

```http
POST /tokenized-payment/v1/tokens/unlink
Authorization: Bearer <merchant_jwt>
Content-Type: application/json

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

> 📘 **Unlink is blocked if a pre-auth is open**
>
> If the user has an `AUTHORIZED` pre-auth, the unlink request returns `400`. Capture or void the pre-auth first.

***

## Next step

With your `paymentToken`, proceed to:

* [Pre-Authorization](./pre-auth-requests) — for hold-then-capture payments
* [Immediate Charge](./immediate-charge-1)  — for one-shot fixed-price payments