Payment Orchestrator
Stored Cards

Stored Cards

You can store your customer's card in the platform vault and charge it later without asking for card details again. The card number is never returned to you — you only hold an opaque reference (storedCardToken).

⚠️

Card storage is off by default. Enable it in the panel under Payments → Form Settings → Card Storage. While it is off, no card is saved and charging a stored card is rejected.

Concepts

ConceptWhat it is
cardUserKeyYour customer key. Cards are filed under it. It is the customerId you send when creating the payment (or an explicit cardUserKey).
storedCardTokenThe permanent reference to a stored card. Sent together with cardUserKey when charging.
🚫

storedCardToken is not the cardToken returned by the checkout form's /payments/form/tokenize endpoint. That one is single-use and bound to one payment; this one is a permanent vault reference. Mixing them up makes the charge fail with StoredCardNotFound.

How a card gets saved

Cards are saved during a payment — there is no separate "save card" call.

Create the payment with a customer key

Send customerId on POST /payments/intents (or the direct API). The card will be filed under this key.

{
  "amount": 10050,
  "currency": "TRY",
  "orderId": "ORD-12345",
  "customerId": "cust_7788"
}

If your card-vault key differs from your customer id, send cardUserKey explicitly; when omitted, customerId is used.

Flag cardSave on the payment

On the hosted form the buyer ticks "save my card". On the direct API you send card.cardSave: true.

The payment completes with 3D Secure

A card is saved only on a 3D Secure payment.

The card is written to the vault after the charge succeeds

If the payment is declined or the buyer abandons 3DS, the card is not saved.

Why is 3DS required? Future automatic charges on a stored card (subscriptions, for example) need evidence of cardholder consent. That evidence is the 3D Secure authentication. Sending cardSave: true on a 2D payment returns 400 payment.card.save_requires_3ds — it is never silently ignored.

The same card is never stored twice

Even if the customer pays repeatedly with the same card and ticks "save" every time, only one record exists and the same storedCardToken is returned. If the expiry or card name changed, the existing record is refreshed.

How you get the token

The payment response carries a data.savedCard block:

{
  "success": true,
  "data": {
    "id": "pay_01H...",
    "status": 3,
    "savedCard": {
      "storedCardToken": "9f3c1a7e-...",
      "cardUserKey": "cust_7788",
      "maskedNumber": "457332******1234",
      "lastFour": "1234",
      "expireMonth": "12",
      "expireYear": "2030",
      "cardBrand": "VISA",
      "deduplicated": false
    }
  }
}

deduplicated: true means the card was already stored and the existing token was returned.

Charging a stored card

Send cardUserKey and storedCardToken together:

{
  "amount": 10050,
  "currency": "TRY",
  "orderId": "ORD-12346",
  "card": {
    "cardUserKey": "cust_7788",
    "cardToken": "9f3c1a7e-...",
    "cvcNumber": "123"
  }
}

Most banks still require the CVC when charging a stored card. If the buyer is not on screen (automatic collection) you cannot supply it — those charges must go through Subscription, where the transaction is flagged as merchant-initiated.

Listing cards

GET /payments/cards?cardUserKey=cust_7788

Permission: payment:cards-view

{
  "cards": [
    {
      "storedCardToken": "9f3c1a7e-...",
      "cardUserKey": "cust_7788",
      "cardHolderName": "JOHN DOE",
      "cardName": "My Garanti card",
      "maskedNumber": "457332******1234",
      "lastFour": "1234",
      "expireMonth": "12",
      "expireYear": "2030",
      "cardBrand": "VISA",
      "bankName": "Garanti",
      "createdAt": "2026-01-15T10:22:00.000Z"
    }
  ],
  "total": 1
}

cardUserKey is required. There is deliberately no endpoint that dumps every stored card of a merchant.

For a single card: GET /payments/cards/{storedCardToken}

Deleting a card

DELETE /payments/cards/{storedCardToken}

Permission: payment:cards-manage — read access alone is not enough.

⚠️

If a subscription bills this card, automatic collection stops. Make sure the customer has another card before deleting.

Deletion is final: the card can no longer be charged and disappears from the list. Deleting the same token twice returns 404.

The customer can store the card again later; that creates a new record with a new token.

Expired cards

Cards past their expiry date are deactivated automatically: they no longer appear in the list and cannot be charged. When the card is reissued, the customer must store it again.

Showing stored cards on the form

On the hosted checkout form the buyer can see and pick cards they stored earlier. All you need is to send customerId (or cardUserKey) when creating the payment — the form lists the active cards filed under that key.

Without a key the form shows no card list; it cannot know whose cards to display.

Security notes

  • The card number is never returned by any endpoint; only the masked form is stored and shown.
  • cardUserKey is never taken from the browser — the server resolves it from the payment record. This structurally prevents a buyer from reaching someone else's card list.
  • A query with another merchant's token returns 404 (not 403) — the existence of the token is not disclosed.