H2H: Card Tokenization via API
Save a customer's card and receive a paymentInstrumentToken starting with pi_. Use this token for future CIT and MIT payments and to list, retrieve or delete saved cards.
You can save a card during a purchase or authorise it without a purchase. A successful payment and a successful card save are separate results: check both before offering the saved card to the customer.
For saving cards on a hosted form, see Payment Instrument Tokenization in Checkout.
Before you start
Ask the integration team to enable H2H tokenization for your settlement account. Card saving in Hosted Checkout is enabled separately in the checkout configuration.
Endpoint paths below are relative to the processing base URL for your environment. See Access URLs and API Authentication.
Request headers
| Header | Value |
|---|---|
Authorization | Bearer <access_token> |
settlement-account-id | Your settlement account ID, such as SA204264682781298688 |
Content-Type | application/json for requests with a JSON body |
Supported methods
Availability depends on the methods enabled for your account. Card saving and payment by token have separate availability.
| Payment method | Save a card | CIT by token | MIT by token |
|---|---|---|---|
BankCardRU | Supported | Supported | Supported |
BankCardKZ | Supported | Not available | Not available |
BankCardUZ | Supported | Not available | Not available |
The examples use BankCardRU and RUB.
Identifiers and token scope
| Identifier | Purpose |
|---|---|
paymentInstrumentToken | The reusable saved-card token, such as pi_01J8ZC4M7QK3T5V9XB2NDR6HAG. Supply it to pay with or manage the saved card |
paymentInstrument.id | The instrument identifier returned with a payment, such as PI204264682781298690. It is not the reusable pi_ token |
merchantPayerReference | Your stable customer identifier. Required when saving a card or paying by token. Use a nonblank string of up to 256 characters and keep the value unchanged for the same customer |
payerId | The platform's payer identifier, starting with PAYR. An alternative customer selector for the saved-card list and deletion endpoints |
settlement-account-id | The account within which the card is saved and used |
clientReferenceId | Your reference for a particular Intent. Use a new value for a new Intent; this is separate from the stable customer reference |
| Intent and payment IDs | Platform identifiers of individual operations. Use them to retrieve results and manage the relevant payment |
| API access token | The credential used in the Authorization header. It is separate from the saved-card token |
A saved card belongs to a settlement account and a merchantPayerReference. A token saved for customer 12345 in account A cannot be used for another customer or account B. Use the payment method associated with that token. Sandbox and production tokens are separate.
Send the customer reference belonging to the authenticated user in your application. Your application is responsible for mapping its users to the correct references.
Saving lifecycle
This diagram shows a typical asynchronous flow. The initial response may also contain an already available result.
sequenceDiagram
participant Customer
participant Merchant
participant API as Payment API
Merchant->>API: POST Intent with savePaymentInstrument=true
API-->>Merchant: Intent and payment IDs, savingRequested=true
opt Cardholder verification required
Merchant->>Customer: Open the returned verification URL
Customer->>API: Complete verification, including 3DS if required
end
Note over API: Successful authorisation allows card saving
par Saving
API->>API: Record the card-saving result
and Payment notification
API-->>Merchant: Webhook, saving result may still be pending
end
Merchant->>API: GET Intent or payment
API-->>Merchant: Payment status and current saving result
Note over Merchant: Check isSaved and paymentInstrumentToken separately from payment status
Save a card during a payment
Create an Intent containing a card payment. Set savePaymentInstrument to true on the payment and pass payer.merchantPayerReference.
This example covers a standard card payment. Include any additional fields required by your account, business category or payment method.
Endpoint: POST /api/v1/intents
{
"clientReferenceId": "order-1001",
"payments": [
{
"payer": {
"merchantPayerReference": "12345"
},
"paymentInstrument": {
"paymentMethodName": "BankCardRU",
"incomingDetails": {
"number": "4462603043025620",
"expiryMonth": "12",
"expiryYear": "2028",
"cvv": "123",
"holderName": "TEST CUSTOMER",
"redirectUrl": "https://merchant.example/payment/return",
"failureUrl": "https://merchant.example/payment/failure"
}
},
"submittedAmount": {
"value": 100,
"currency": "RUB"
},
"authCurrencyCode": "RUB",
"savePaymentInstrument": true,
"webhookUrl": "https://merchant.example/webhooks/payments"
}
]
}Intent request body
| Field | Description |
|---|---|
clientReferenceId | Your reference for this Intent. Use a new value for a new Intent |
payments | The payment array. This example creates one card payment |
payments[]
| Field | Description |
|---|---|
savePaymentInstrument | Set to true to request saving after authorisation |
payer | The customer to whom the saved card will belong |
paymentInstrument | The payment method and card details |
submittedAmount, authCurrencyCode | The amount and currency information, described below |
webhookUrl | Your endpoint for payment notifications |
payments[].payer
| Field | Description |
|---|---|
merchantPayerReference | Required for saving. Your stable, nonblank customer reference, up to 256 characters |
Include any additional payer fields required by the payment method or your account.
payments[].paymentInstrument
| Field | Description |
|---|---|
paymentMethodName | BankCardRU in this example |
incomingDetails | Card details and return URLs for this authorisation |
A new-card saving request does not contain paymentInstrumentToken or tokenPaymentReason.
payments[].paymentInstrument.incomingDetails
| Field | Description |
|---|---|
number | The card number. The example uses a sandbox test card |
expiryMonth, expiryYear | Strings in MM and YYYY format |
cvv, holderName | Card verification and cardholder data for the initial authorisation |
redirectUrl, failureUrl | URLs used by the BankCardRU payment flow |
See BankCardRU for the complete method-specific requirements and test data.
payments[].submittedAmount and authCurrencyCode
| Field | Description |
|---|---|
submittedAmount.value | The amount submitted for the payment |
submittedAmount.currency | The submitted currency code; RUB in this example |
authCurrencyCode | The authorisation currency code; RUB in this example |
The ordinary payment amount and currency rules apply. For binding without a purchase, see the zero amount and nonzero authorisation flows.
HTTP 200 — initial response excerpt.
The initial response may arrive before authorisation and card saving finish. This example shows an accepted payment for which saving was requested but no saving result is available yet.
{
"intentId": "204264682781298700",
"payments": [
{
"id": "204264682781298701",
"status": "ACCEPTED",
"paymentInstrument": {
"paymentMethodName": "BankCardRU",
"savingRequested": true
},
"additionalData": {
"details": {
"url": "https://checkout.example/verification/session-1001"
}
}
}
],
"paymentCreationErrors": []
}Intent response and payments[]
| Field | Description |
|---|---|
intentId | The ID returned by Intent creation. Use it to retrieve the Intent |
payments[].id | The payment ID |
payments[].status | The payment status; ACCEPTED is an intermediate result |
payments[].paymentInstrument.savingRequested | true confirms that saving was requested, not that it succeeded |
payments[].additionalData.details.url | The URL to open in the customer's browser or web view to continue the authorisation. Use the value returned for the actual payment |
paymentCreationErrors | Errors that prevented payment creation; an HTTP success response can still contain creation errors |
Open the returned additionalData.details.url and follow the BankCardRU authorisation flow. This step is required even if the customer is not asked to complete a 3DS challenge. The example URL is illustrative.
Do not combine
savePaymentInstrument: truewith a payment by an existing token. An HTTP success response alone does not confirm that the card was saved.
Retrieve the saving result
The platform saves the card after successful authorisation. You can read the result using GET /api/v1/intents/{intentId} or GET /api/v1/payments/{paymentId}. The result can also appear in a payment webhook.
Endpoint: GET /api/v1/intents/204264682781298700
No request body is required. Use the request headers described above.
Path parameters
| Parameter | Description |
|---|---|
intentId | The Intent ID returned by POST /api/v1/intents |
HTTP 200 — response excerpt after successful authorisation and saving.
{
"id": "204264682781298700",
"payments": [
{
"id": "204264682781298701",
"status": "AUTHORIZED",
"paymentInstrument": {
"paymentMethodName": "BankCardRU",
"savingRequested": true,
"isSaved": true,
"paymentInstrumentToken": "pi_01J8ZC4M7QK3T5V9XB2NDR6HAG"
}
}
]
}Intent response and payments[]
The create-Intent response calls the Intent identifier intentId; the GET response calls it id. In both, payment identifiers are returned as payments[].id. A direct GET of a payment returns the saving fields inside its top-level paymentInstrument object.
payments[].paymentInstrument
| Field | Meaning |
|---|---|
savingRequested | Present as true when saving was requested. Its absence does not mean that saving failed |
isSaved | true when the result resolves to an available saved-card record; false when saving did not succeed or that record is no longer available. It can be absent while the result is not available |
paymentInstrumentToken | The token created or reused by saving, or the token used for a payment by a saved card |
notSavedReason | The reason for an unsuccessful save, when supplied with isSaved: false |
Keep the returned token with the corresponding customer and settlement account. Do not infer saving success from the payment status or from the presence of masked card details.
Receive the result by webhook
Payment webhooks can include the same saving fields. The following event excerpt illustrates a payment notification after a purchase has completed and the card has been saved:
{
"transactionType": "PAYMENT",
"transactionId": "204264682781298701",
"intentId": "204264682781298700",
"status": "CAPTURED",
"isStoredCredential": false,
"paymentInstrument": {
"paymentMethodName": "BankCardRU",
"savingRequested": true,
"isSaved": true,
"paymentInstrumentToken": "pi_01J8ZC4M7QK3T5V9XB2NDR6HAG"
}
}Payment webhook fields
| Field | Description |
|---|---|
transactionType | PAYMENT for a payment notification |
transactionId | The payment ID |
intentId | The containing Intent ID |
status | The payment status; this example shows a completed purchase |
isStoredCredential | Whether the payment used a previously saved card. It does not indicate whether this payment saved a new card |
paymentInstrument
The webhook's top-level paymentInstrument contains the same saving fields described under Retrieve the saving result: savingRequested, isSaved, paymentInstrumentToken and, when applicable, notSavedReason. It can also identify the payment method.
In this example, isSaved: true and paymentInstrumentToken confirm the available saved-card record.
A payment webhook, including one reporting
AUTHORIZEDorCAPTURED, may be sent before the saving result is available. If it reportssavingRequested: truewithoutisSavedor a token, retrieve the updated Intent or payment. Do not rely on a separate card-saved notification.
See Webhooks for delivery and handling rules.
Save a card without a purchase
Zero amount authorisation
For BankCardRU, authorise an amount of zero with savePaymentInstrument: true. This verifies and saves the card without reserving a purchase amount.
Endpoint: POST /api/v1/intents
{
"clientReferenceId": "card-binding-1001",
"payments": [
{
"payer": {
"merchantPayerReference": "12345"
},
"paymentInstrument": {
"paymentMethodName": "BankCardRU",
"incomingDetails": {
"number": "4462603043025620",
"expiryMonth": "12",
"expiryYear": "2028",
"cvv": "123",
"holderName": "TEST CUSTOMER",
"redirectUrl": "https://merchant.example/cards/return",
"failureUrl": "https://merchant.example/cards/failure"
}
},
"submittedAmount": {
"value": 0,
"currency": "RUB"
},
"authCurrencyCode": "RUB",
"savePaymentInstrument": true,
"webhookUrl": "https://merchant.example/webhooks/payments"
}
]
}payments[].submittedAmount and saving fields
| Field | Value for zero amount binding |
|---|---|
submittedAmount.value | 0 |
submittedAmount.currency | RUB |
authCurrencyCode | RUB |
savePaymentInstrument | true |
The payer and paymentInstrument objects follow the saving request above. Use a new top-level clientReferenceId for this Intent.
HTTP 200 — initial response excerpt.
This response starts the authorisation flow. It does not yet confirm authorisation or card saving.
{
"intentId": "204264682781298730",
"payments": [
{
"id": "204264682781298731",
"status": "ACCEPTED",
"paymentInstrument": {
"paymentMethodName": "BankCardRU",
"savingRequested": true
},
"additionalData": {
"details": {
"url": "https://checkout.example/verification/card-binding-1001"
}
}
}
],
"paymentCreationErrors": []
}Intent response and payments[]
| Field | Meaning |
|---|---|
intentId | Use this Intent ID to retrieve the result |
payments[].id | The payment ID for this card-binding operation |
payments[].status | ACCEPTED is an intermediate result |
payments[].paymentInstrument.savingRequested | Saving was requested; its result is not yet available |
payments[].additionalData.details.url | Open the returned URL to continue authorisation |
paymentCreationErrors | Per-payment creation errors, if any |
Open the returned URL and complete the authorisation flow, then retrieve the saving result using this operation's Intent or payment ID. A successful zero amount authorisation uses the normal AUTHORIZED status. Check paymentInstrument.isSaved and paymentInstrument.paymentInstrumentToken separately; authorisation success does not guarantee saving success.
After a successful zero amount authorisation, no capture or cancellation request is required.
Authorisation with a nonzero amount
You can also save the card using a temporary authorisation for a nonzero amount supported by your account. Use the same saving request with a nonzero amount and a new clientReferenceId.
After successful authorisation, you must cancel the authorisation to release the hold, whether or not saving succeeded.
Endpoint: PATCH /api/v1/payments/{paymentId}
{
"status": "CANCELLED"
}Path parameters
| Parameter | Description |
|---|---|
paymentId | The ID returned in payments[].id for the nonzero card-binding authorisation |
Request body
| Field | Value |
|---|---|
status | CANCELLED, requesting release of the authorisation |
HTTP 200 — request accepted.
The response body is plain text:
SuccessAcceptance of the cancellation request is not the final cancellation result. Confirm CANCELLED through GET or webhook. Cancelling the authorisation does not remove a successfully saved card.
See Cancel Authorized Amount for the cancellation endpoint and conditions.
When saving does not succeed
A payment can succeed while saving the card fails. For example, an otherwise successful payment may contain this result excerpt:
{
"paymentInstrument": {
"savingRequested": true,
"isSaved": false,
"notSavedReason": "LIMIT_REACHED"
}
}paymentInstrument.notSavedReason
notSavedReason | Meaning and next step |
|---|---|
LIMIT_REACHED | The customer's saved-card limit has been reached. An existing card can be removed before saving another |
CAPABILITY_DISABLED | Card saving is not enabled for this account or flow. Contact the integration team |
TEMPORARY_ERROR | Saving could not be completed. Check the payment result independently; do not repeat the purchase just to obtain a token |
AUTH_DECLINED | Authorisation failed, so the card was not saved. Use the payment's decline information |
For H2H, the default limit is five usable saved cards per customer within a settlement account. The integration team can configure the limit from 1 to 100. It is shared across payment methods. Deleted cards and cards recognised as invalid do not consume a place in this limit.
Reusing and managing a saved card
Saving a card that is already saved for the same customer and settlement account returns the existing token. Saving the card again after deleting its token produces a new token.
After deletion, reading the original saving payment can show isSaved: false. A historical payment response is not proof that its token is still usable; retrieve the saved card to check its current state.
The token has no fixed expiry period of its own. It may stop being usable if it is deleted or the underlying card becomes unavailable. A saved card's ACTIVE status does not guarantee that a payment will be approved; validity also depends on the issuing bank and payment method.
Cards saved through Hosted Checkout can be retrieved and used through the API for the same settlement account and merchantPayerReference, with H2H tokenization enabled and a supported payment method. The same saved-card collection is available to Checkout when its saving feature is enabled. You do not need to collect the card details again solely because the integration channel changes.
Continue with Payments with Saved Cards or Manage Saved Cards.
Updated about 7 hours ago
