# Token Sync (beta)

## Overview <a href="#overview" id="overview"></a>

Token Sync is a VGS capability that allows merchants to create and manage PSP tokens from a single VGS card object. Instead of treating each PSP token as a separate integration artifact, merchants can use VGS as the canonical system for card storage, PSP-token provisioning, and token lifecycle management across providers such as Braintree, Adyen, and Stripe.

With Token Sync, a merchant can decide which PSP tokens should be available immediately, which should be created asynchronously in the background, and which should be issued on demand later. This gives merchants a flexible way to support primary payment routing, backup PSP coverage, failover strategies, and future token refresh flows without requiring the browser or merchant backend to recollect sensitive card data for each provider.

#### API Specifications

[Token Sync Draft OpenAPI Specifications](https://openapi.gitbook.com/o/hcowjO8ckQ1K5fm3NPZH/spec/token-sync.yaml)

## PSP Token Issuance Options <a href="#psp-token-issuance-options" id="psp-token-issuance-options"></a>

Merchants can choose between three PSP-token issuance patterns depending on which providers they need immediately and which providers they only need as backup or failover.

### 1. Synchronous issuance on `POST /cards` <a href="#id-1-synchronous-issuance-on-post-cards" id="id-1-synchronous-issuance-on-post-cards"></a>

In the first model, VGS issues one or more PSP tokens during the initial `POST /cards` request and returns them in the response.

This is the best fit when the merchant needs a specific PSP token immediately for the primary payment flow, for example:

* Braintree token required right away for the first authorization
* Adyen token required immediately for the primary region

In this mode, the merchant can treat the synchronously returned PSP token as the primary token it expects to use first.

### 2. Asynchronous Issuance After `POST /cards` <a href="#id-2-asynchronous-issuance-after-post-cards" id="id-2-asynchronous-issuance-after-post-cards"></a>

In the second model, `POST /cards` creates the card object immediately, but one or more PSP tokens are provisioned asynchronously after the card has already been created.

This is the best fit when the merchant wants:

* a fast card-creation response
* backup or failover PSP tokens created in the background
* secondary providers prepared without blocking the checkout or enrollment flow

For example, a merchant may want:

* Braintree returned synchronously as the primary PSP token
* Adyen and Stripe issued asynchronously as backup PSP tokens

In this mode, the merchant can retrieve the updated card later with `GET /cards/{card_id}` or receive a webhook when PSP token state changes.

### 3. Manual Issuance with `POST /cards/{card_id}/psp-tokens` <a href="#id-3-manual-issuance-with-post-cardscard_idpsp-tokens" id="id-3-manual-issuance-with-post-cardscard_idpsp-tokens"></a>

In the third model, the merchant explicitly triggers PSP token creation or refresh by calling `POST /cards/{card_id}/psp-tokens`.

This is the best fit when the merchant wants:

* precise control over when secondary PSP tokens are created
* a delayed provisioning step after user onboarding is complete
* targeted creation or refresh of only a subset of PSPs

For example, a merchant may:

* create the card first
* issue Braintree synchronously
* wait to issue Stripe until the card is actually needed for failover
* refresh Adyen and Stripe later if the card data changes

This endpoint can be used for all configured PSPs by default, or for a selected provider subset when the merchant wants manual control over which PSP tokens are created or refreshed.

### Choosing Primary and Backup PSP Tokens <a href="#choosing-primary-and-backup-psp-tokens" id="choosing-primary-and-backup-psp-tokens"></a>

These three issuance options can be mixed together.

A merchant can decide:

* which PSP tokens must be available immediately
* which PSP tokens should be prepared asynchronously as backup
* which PSP tokens should only be created on demand

Example strategy:

* issue Braintree synchronously on `POST /cards`
* issue Adyen asynchronously after card creation
* issue Stripe manually later through `POST /cards/{card_id}/psp-tokens`

With this approach, merchants can optimize for both:

* low-latency access to the primary PSP token they need right away
* durable backup coverage across secondary PSPs without forcing every provider to block the initial card creation flow

## Token Sync with Account Updater <a href="#token-sync-with-account-updater" id="token-sync-with-account-updater"></a>

Token Sync is not only about issuing PSP tokens when a card is first created. It is also meant to help merchants keep every downstream PSP token up to date over time as the underlying card changes.

VGS Account Updater and Token Sync can be used together as a continuous card-lifecycle model:

* VGS stores the canonical card object
* VGS Account Updater detects changes to the underlying card
* Token Sync uses the updated card state to refresh each configured PSP token
* VGS notifies the merchant whenever PSP token state changes

This combined model helps ensure that merchants do not have to manually track whether a Braintree, Adyen, or Stripe token has become stale after the underlying card changes.

The same propagation behavior should apply regardless of how the card change was introduced. If the canonical VGS card object is updated manually by an API call or operational workflow, Token Sync should still evaluate and propagate the change across all configured PSP tokens in the same way it would for an Account Updater-triggered update.

### How updates should work <a href="#how-updates-should-work" id="how-updates-should-work"></a>

When the canonical VGS card object changes, Token Sync should evaluate every PSP token associated with that card and determine whether each provider supports:

* updating the existing token in place
* issuing a replacement token
* deleting or retiring the token

The specific outcome depends on the provider behavior, but the merchant-facing expectation should stay consistent: VGS keeps the PSP-token layer aligned with the current card state.

### Examples of card changes that should trigger PSP token updates <a href="#examples-of-card-changes-that-should-trigger-psp-token-updates" id="examples-of-card-changes-that-should-trigger-psp-token-updates"></a>

* expiration date changes
* PAN changes
* card account moves to `closed`

#### Expiration date change <a href="#expiration-date-change" id="expiration-date-change"></a>

If the card expiration date changes, VGS should attempt to update each PSP token so that the provider reflects the current expiration month and year.

#### PAN change <a href="#pan-change" id="pan-change"></a>

If the PAN changes, VGS should attempt to update each PSP token. If a PSP does not support updating the existing token in place, VGS should create a new replacement token and mark the prior token as replaced or retired.

#### Card closed <a href="#card-closed" id="card-closed"></a>

If the underlying card account is moved to `closed`, VGS should delete, disable, or retire the related PSP tokens based on provider behavior and policy.

### Webhooks for PSP token lifecycle changes <a href="#webhooks-for-psp-token-lifecycle-changes" id="webhooks-for-psp-token-lifecycle-changes"></a>

VGS should send a webhook every time PSP token state changes as a result of Token Sync.

That webhook may represent:

* an actually updated PSP token
* a newly created replacement token when the PSP does not allow in-place updates
* a deleted or retired token when the card is closed

The merchant should not need to infer whether a provider performed an in-place update or issued a replacement token by comparing raw provider responses. VGS should normalize that into a stable event model.

### Merchant-facing event outcomes <a href="#merchant-facing-event-outcomes" id="merchant-facing-event-outcomes"></a>

The merchant-facing event model should clearly distinguish between:

* `updated`: the PSP token still exists and its linked card data was refreshed
* `replaced`: the prior PSP token could not be updated in place, so a new PSP token was created
* `deleted`: the PSP token was deleted, retired, or otherwise deactivated because the underlying card should no longer be used

### Operational expectation <a href="#operational-expectation" id="operational-expectation"></a>

With Token Sync and Account Updater working together, merchants can rely on VGS to:

* detect when the underlying card changed
* propagate those changes across all configured PSPs
* create replacement PSP tokens when necessary
* delete PSP tokens when a card is no longer valid
* emit webhooks whenever PSP token state changes

This is what allows Token Sync to function as an ongoing card-maintenance capability rather than a one-time token-issuance flow.

## Card Collection with Token Sync Implementation Models <a href="#merchant-token-sync-implementation" id="merchant-token-sync-implementation"></a>

This sample describes a generic merchant browser-led token-sync integration that can be shared across multiple merchants.

The merchant can support two front-end driven patterns:

1. `vgsCollect.createCard()` creates the VGS card object in the browser, then the browser manually submits that card object to the merchant backend.
2. `vgsCollect.submit()` sends card data from Collect through VGS and directly to the merchant backend, with VGS returning a card-object-shaped payload that includes PSP token data.

In both patterns:

* the raw PAN and CVC stay inside VGS Collect fields
* the merchant backend does not need to receive raw card data
* the merchant persists the VGS `card_id` as the canonical card reference
* VGS can include PSP token state when synchronous provisioning completes
* the key difference is whether the PSP token data returns to the browser first or is submitted directly to the merchant backend

### Option A: `vgsCollect.createCard()` returns PSP tokens to the browser <a href="#option-a-vgscollectcreatecard-returns-psp-tokens-to-the-browser" id="option-a-vgscollectcreatecard-returns-psp-tokens-to-the-browser"></a>

```mermaid
sequenceDiagram
    participant C as Cardholder
    participant B as Browser + VGS Collect
    participant V as VGS Cards API
    participant M as Merchant Backend
    participant P as PSP

    C->>B: Enter card details
    B->>V: createCard()
    V-->>B: Return card object with card_id and PSP token state
    B->>M: POST card object or normalized card payload
    M->>P: Submit payment using selected PSP token
    P-->>M: Return payment result
    M-->>B: Persist merchant payment method reference or payment result
```

#### 1. The browser collects card data with VGS Collect <a href="#id-1-the-browser-collects-card-data-with-vgs-collect" id="id-1-the-browser-collects-card-data-with-vgs-collect"></a>

The merchant renders the payment form with VGS Collect hosted fields.

```js
const form = await VGSCollect.session({
  vaultId: '<vault_id>',
  env: 'sandbox',
  formId: 'merchant-card-collect'
});

form.cardNumberField('#card-number');
form.cardholderNameField('#cardholder-name');
form.cardExpirationDateField('#card-expiration');
form.cardCVCField('#card-cvc');
```

#### 2. The browser creates the VGS card object directly <a href="#id-2-the-browser-creates-the-vgs-card-object-directly" id="id-2-the-browser-creates-the-vgs-card-object-directly"></a>

The browser calls `createCard()` and VGS returns the card object response directly to the front end. In this option, the browser is the first place where the card object and PSP token data are available.

```js
form.createCard(
  {},
  async function(status, cardObject) {
    console.log('VGS card object created', status, cardObject);
  },
  function(errors) {
    console.error('createCard failed', errors);
  }
);
```

#### 3. VGS returns the card object to the browser <a href="#id-3-vgs-returns-the-card-object-to-the-browser" id="id-3-vgs-returns-the-card-object-to-the-browser"></a>

The browser receives the canonical VGS card object. That object can already include PSP token state for Braintree, Adyen, and Stripe if provisioning completes inside the synchronous window.

```json
{
  "data": {
    "id": "CRD_merchant_12345",
    "type": "cards",
    "attributes": {
      "pan_alias": "tok_abcdefg",
      "cvc_alias": "tok_vwxyz",
      "token_type": "pan",
      "bin": "424242",
      "first8": "42424242",
      "last4": "4242",
      "exp_month": 12,
      "exp_year": 2028,
      "cardholder_name": "John Doe",
      "network_transaction_id": "0163125478900412"
    },
    "capabilities": [
      "psp-tokens"
    ],
    "included": [
      {
        "type": "psp-tokens",
        "attributes": {
          "braintree": {
            "attributes": {
              "token": "bt_tok_primary_123",
              "state": "active",
              "last_sync_status": "provisioned"
            }
          },
          "adyen": {
            "attributes": {
              "token": "ady_tok_789",
              "state": "active",
              "last_sync_status": "provisioned"
            }
          },
          "stripe": {
            "attributes": {
              "token": "pm_1RtExampleStripe",
              "state": "active",
              "last_sync_status": "provisioned"
            }
          }
        }
      }
    ]
  }
}
```

#### 4. The browser manually posts the card object to the merchant <a href="#id-4-the-browser-manually-posts-the-card-object-to-the-merchant" id="id-4-the-browser-manually-posts-the-card-object-to-the-merchant"></a>

This is the integration point the merchant owns. The browser sends the VGS card object, or a normalized subset of it, to the merchant backend.

The merchant backend stores:

* `card_id`
* customer linkage
* card summary fields like `last4`, `exp_month`, and `exp_year`
* PSP token data for the providers it needs immediately

### Option B: `vgsCollect.submit()` sends the card object directly to the merchant backend through VGS <a href="#option-b-vgscollectsubmit-sends-the-card-object-directly-to-the-merchant-backend-through-vgs" id="option-b-vgscollectsubmit-sends-the-card-object-directly-to-the-merchant-backend-through-vgs"></a>

```mermaid
sequenceDiagram
    participant C as Cardholder
    participant B as Browser + VGS Collect
    participant V as VGS Proxy
    participant M as Merchant Backend
    participant P as PSP

    C->>B: Enter card details
    B->>V: submit()
    Note over B,V: VGS Collect captures raw card data
    V->>V: Create card object and issue PSP tokens
    V->>M: Submit card object directly to merchant backend
    M->>P: Submit payment using selected PSP token
    P-->>M: Return payment result
    M-->>V: Return merchant response
    V-->>B: Return merchant response to browser
```

#### 1. The browser submits through VGS <a href="#id-1-the-browser-submits-through-vgs" id="id-1-the-browser-submits-through-vgs"></a>

In this pattern, the browser uses `submit()` so VGS Collect captures the sensitive fields, VGS creates the card object, provisions PSP tokens, and then submits the resulting card-object payload directly to the merchant backend.

```js
form.submit(
  '/merchant/payment-methods',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    }
  },
  async function(status, response) {
    console.log('Merchant response received', status, response);
  },
  function(errors) {
    console.error('submit failed', errors);
  }
);
```

#### 2. VGS submits the card object directly to the merchant backend <a href="#id-2-vgs-submits-the-card-object-directly-to-the-merchant-backend" id="id-2-vgs-submits-the-card-object-directly-to-the-merchant-backend"></a>

This is the important difference from Option A. The browser does not need to manually forward the VGS card object after it is created. Instead, VGS acts as the proxy boundary:

* Collect captures the card data from the customer
* VGS creates the card object
* VGS issues any synchronous PSP tokens
* VGS submits the resulting card object directly to the merchant backend

#### 3. This is what the merchant back end will receive <a href="#id-3-this-is-what-the-merchant-back-end-will-receive" id="id-3-this-is-what-the-merchant-back-end-will-receive"></a>

The merchant backend receives a card-object-shaped payload that can include the canonical `card_id`, the shared `network_transaction_id`, and any PSP tokens that were issued synchronously.

```json
{
  "data": {
    "id": "CRD12345",
    "type": "cards",
    "attributes": {
      "pan_alias": "tok_abcdefg",
      "cvc_alias": "tok_vwxyz",
      "token_type": "pan",
      "bin": "424242",
      "first8": "42424242",
      "last4": "4242",
      "exp_month": 12,
      "exp_year": 2028,
      "cardholder_name": "John Doe",
      "network_transaction_id": "0163125478900412"
    },
    "capabilities": [
      "psp-tokens"
    ],
    "included": [
      {
        "type": "psp-tokens",
        "attributes": {
          "braintree": {
            "attributes": {
              "token": "bt_tok_primary_123",
              "state": "active",
              "last_sync_status": "provisioned"
            }
          },
          "adyen": {
            "attributes": {
              "token": "ady_tok_789",
              "state": "active",
              "last_sync_status": "provisioned"
            }
          },
          "stripe": {
            "attributes": {
              "token": "pm_1RtExampleStripe",
              "state": "active",
              "last_sync_status": "provisioned"
            }
          }
        }
      }
    ]
  }
}
```

In this option, the merchant backend can persist the card object and PSP token data immediately, without requiring the browser to relay the payload after VGS has created it.

### Option C: VGS Routes create Card Objects and associate PSP Tokens

```mermaid
sequenceDiagram
    participant C as Cardholder
    participant B as Browser + VGS Collect
    participant I as VGS Inbound Route
    participant M as Merchant Backend
    participant O as VGS Outbound Route
    participant P as PSP

    C->>B: Enter card details
    B->>I: submit()
    Note over B,I: VGS Collect captures raw card data
    I->>I: Alias PAN and CVV, create or reference card object
    I->>M: Forward sanitized payload to merchant backend
    M->>O: Send tokenization or payment request
    O->>P: Reveal card data and call PSP
    P-->>O: Return PSP token or payment method id
    O->>O: Associate PSP token to existing card object
    O-->>M: Return PSP response
    M-->>I: Return merchant response
    I-->>B: Return merchant response
```

#### 1. The browser submits through VGS <a href="#id-1-the-browser-submits-through-vgs" id="id-1-the-browser-submits-through-vgs"></a>

This option is designed for merchants that want access to Account Updater, Network Tokens, and Token Syncing with minimal or no changes to the current integration. The customer still enters payment details through VGS Collect and submits them through the existing VGS inbound route.

```js
form.submit(
  '/merchant/payments',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    }
  },
  async function(status, response) {
    console.log('Merchant payment response received', status, response);
  },
  function(errors) {
    console.error('submit failed', errors);
  }
);
```

#### 2. The inbound route aliases the card data and establishes the canonical card object

When the inbound route receives the request, VGS accepts the submitted card data, aliases the PAN and CVV, and forwards the sanitized payload to the merchant backend. In this option, the canonical card object is also established at this stage so Token Sync has a stable VGS record to work with throughout the rest of the flow.

In this model, the inbound route is responsible for:

* capturing the sensitive fields from Collect
* creating or referencing the canonical VGS card object using the PAN and expiration date
* forwarding sanitized data to the merchant backend so the merchant can continue using its current payments architecture

If a matching card object already exists for the same PAN and expiration date, VGS should reference the existing card object instead of creating a duplicate.

#### 3. The merchant continues its normal PSP tokenization or payment flow through the outbound route

After the merchant backend receives the inbound request, it continues its normal payments flow. The merchant sends the aliased card data through the existing VGS outbound route, and VGS reveals the stored values only at the PSP boundary.

This is the path merchants already use to tokenize with PSPs or perform payments, so the main value of this option is that VGS can layer Token Syncing on top of that existing architecture.

#### 4. VGS associates the PSP token during the outbound response phase

This is the important difference from Options A and B. The PSP token does not need to be issued at the moment the card object is first created. Instead:

* the merchant completes the downstream PSP request through the outbound route using the VGS Card ID created in step 2
* the outbound response returns the PSP token or payment method identifier
* VGS associates that PSP token with the existing card object during the response flow

That keeps the VGS card object as the canonical record while still letting the PSP token originate from the merchant's existing payments architecture.

#### 5. Sample Stripe request and response through the outbound route

The merchant can keep using its existing Stripe `payment_methods` flow, but instead of sending the direct VGS Aliases, they send card-object references through the VGS outbound route.

This is what the merchant request can look like before VGS reveals the underlying PAN and CVC to Stripe:

```bash
curl -X POST https://api.stripe.com/v1/payment_methods \
  -u sk_live_xxxxxxxxxxxxxxxxxxxxx: \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'type=card' \
  --data-urlencode 'card[number]=CRD12345.pan' \
  --data-urlencode 'card[exp_month]=12' \
  --data-urlencode 'card[exp_year]=2028' \
  --data-urlencode 'card[cvc]=CRD12345.cvc' \
  --data-urlencode 'billing_details[name]=John Doe'
```

VGS resolves `{CRD12345}.pan` and `{CRD12345}.cvc` at the outbound boundary, Stripe provisions the payment method, and Stripe returns its payment method ID to the merchant response flow.

```json
{
  "id": "pm_1RtExampleStripe",
  "object": "payment_method",
  "type": "card",
  "card": {
    "brand": "visa",
    "last4": "4242",
    "exp_month": 12,
    "exp_year": 2028
  }
}
```

VGS can then associate the returned Stripe payment method ID with the existing card object as the Stripe PSP token for that card.

#### 6. The resulting card object reflects the PSP token learned from the outbound response

After the outbound response is processed, the same canonical card object can be updated so later `GET /cards/{card_id}` calls, token-sync workflows, or merchant reads reflect the PSP token that was actually created downstream.

```json
{
  "data": {
    "id": "CRD12345",
    "type": "cards",
    "attributes": {
      "pan_alias": "tok_abcdefg",
      "cvc_alias": "tok_vwxyz",
      "token_type": "pan",
      "bin": "424242",
      "first8": "42424242",
      "last4": "4242",
      "exp_month": 12,
      "exp_year": 2028,
      "cardholder_name": "John Doe",
      "network_transaction_id": "0163125478900412"
    },
    "capabilities": [
      "psp-tokens"
    ],
    "included": [
      {
        "type": "psp-tokens",
        "attributes": {
          "stripe": {
            "attributes": {
              "token": "pm_1RtExampleStripe",
              "state": "active",
              "last_sync_status": "provisioned"
            }
          }
        }
      }
    ]
  }
}
```

This option is a good fit when the merchant wants to preserve an existing VGS payment-architecture pattern where the PSP token is created as part of the downstream PSP interaction, but still wants VGS to keep the canonical card object synchronized with that PSP token afterward.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.verygoodsecurity.com/cmp/products-and-services/token-sync-beta.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
