H2H: Payouts to Saved Payee Cards
Bind a payee's card once and receive a paymentInstrumentToken starting with pi_. Use this token in disbursements instead of sending the card number every time. Binding does not create a payout.
Payouts by token are available for
DisbursementBankCardRUSandBankCardKZ. H2H tokenization must be enabled for the settlement account by the integration team. Payouts with card details work as before and do not require a token.
To save a customer's card for payments, see Card Tokenization via API. Customer cards and payee cards are kept separately: see Token scope.
Before you start
Ask the integration team to enable H2H tokenization for your settlement account and to configure a payout route for the payment method you use.
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
| Payment method | Bind a payee card | Payout by token |
|---|---|---|
DisbursementBankCardRUS | Supported | Supported |
BankCardKZ | Supported | Supported |
Other payout methods, including Napas, do not support binding. The examples use BankCardKZ and KZT.
Identifiers and token scope
| Identifier | Purpose |
|---|---|
paymentInstrumentToken | The reusable payee card token, such as pi_01J8ZC4M7QK3T5V9XB2NDR6HAF. Supply it in a disbursement instead of the card details |
merchantPayeeReference | Your stable payee identifier. Required when binding a card, paying out by token and deleting the binding. Use a nonblank string of up to 256 characters and keep it unchanged |
settlement-account-id | The account within which the card is bound and paid out to |
clientReferenceId | Your reference for the Intent, and for each disbursement inside it. Use a new value for every payout; it is separate from the stable payee reference |
A payee card token belongs to one settlement account, one merchantPayeeReference and one payment method. A token bound for payee payee-12345 in account A cannot be used for another payee, another account or another payment method. Sandbox and production tokens are separate.
Customer cards saved during payments are not used for payouts, and a payee card token is not accepted in a payment.
flowchart LR bind["Bind the payee card"] --> token["pi_ token"] token -->|"Payout by token, any number of times"| token token -->|"Delete the binding"| deleted["Token unavailable"] deleted -->|"Bind the card again"| replacement["New pi_ token"]
Bind a payee card
API reference: Bind a payout card.
Endpoint: POST /api/v1/payment-instruments
{
"paymentMethodName": "BankCardKZ",
"merchantPayeeReference": "payee-12345",
"incomingDetails": {
"number": "4111111111111111"
}
}Request body
| Field | Type | Required | Description |
|---|---|---|---|
paymentMethodName | String | Yes | DisbursementBankCardRUS or BankCardKZ |
merchantPayeeReference | String | Yes | Your payee identifier, nonblank, up to 256 characters |
incomingDetails | Object | Yes | The payee's card; only the card number is accepted, see the object below |
incomingDetails
| Field | Type | Required | Description |
|---|---|---|---|
number | String | Yes | Card number of 13 to 19 digits with a valid checksum |
Do not send the expiry date, holder name or CVV: a request with any field other than number is rejected with HTTP 400. Payouts to a bound card need the card number only.
HTTP status: 200, both for a new binding and for a repeated one.
{
"paymentInstrumentToken": "pi_01J8ZC4M7QK3T5V9XB2NDR6HAF",
"type": "CARD",
"status": "ACTIVE",
"paymentMethodId": "PM204264682781298692",
"paymentMethodName": "BankCardKZ",
"merchantPayeeReference": "payee-12345",
"createdAt": "2026-10-20T10:15:30Z",
"storedDetails": {
"brand": "VISA",
"maskedNumber": "411111******1111"
}
}Response body
| Field | Type | Nullable | Description |
|---|---|---|---|
paymentInstrumentToken | String | No | The token to pay out by |
type | String | No | CARD |
status | String | No | ACTIVE |
paymentMethodId | String | No | Platform payment method identifier, beginning with PM |
paymentMethodName | String | No | The payment method the card is bound to |
merchantPayeeReference | String | No | Your payee identifier, as supplied in the request |
createdAt | String | No | Time the binding was created, in ISO 8601 format |
storedDetails.brand | String | Yes | Card brand, such as VISA; null if it cannot be determined |
storedDetails.maskedNumber | String | No | Masked card number |
The full card number is not returned.
Binding the same card to the same payee with the same payment method again returns the same token. The same card bound to a different payee gets a different token.
Store the token together with
merchantPayeeReferenceand the masked number from this response. Payee card tokens cannot be listed or retrieved later:GET /api/v1/payment-instruments/{paymentInstrumentToken}answers404for them.
Pay out by token
Pass the token in payeePaymentInstrument and the payee reference in payee instead of the card details. Both ways of creating a disbursement accept a token:
| Endpoint | Use |
|---|---|
POST /api/v1/intents | Create a new Intent with disbursements |
POST /api/v1/intents/{intentId}/transactions | Add disbursements to an existing Intent |
Endpoint: POST /api/v1/intents
{
"clientReferenceId": "payout-20261020-0001",
"useCheckoutForm": false,
"disbursements": [
{
"payee": {
"merchantPayeeReference": "payee-12345"
},
"payeePaymentInstrument": {
"paymentMethodName": "BankCardKZ",
"paymentInstrumentToken": "pi_01J8ZC4M7QK3T5V9XB2NDR6HAF"
},
"submittedAmount": {
"value": 12300.00,
"currency": "KZT"
},
"authAmount": {
"currency": "KZT"
},
"clientReferenceId": "payout-20261020-0001-1",
"webhookUrl": "https://merchant.example.com/webhooks/disbursements"
}
]
}For POST /api/v1/intents/{intentId}/transactions, send the same disbursement objects in the disbursements array. Each element must carry its own clientReferenceId; see the API reference.
Token-specific fields of disbursements[]
| Field | Required | Description |
|---|---|---|
payee.merchantPayeeReference | Yes | The same payee reference used when the card was bound |
payeePaymentInstrument.paymentMethodName | Yes | The same payment method used when the card was bound |
payeePaymentInstrument.paymentInstrumentToken | Yes | The payee card token |
payeePaymentInstrument.incomingDetails | No | Omit it. Card details sent together with a token are rejected with HTTP 400 |
payeePaymentInstrument.tokenPaymentReason | No | Do not send it. CIT and MIT apply to payments only; with a disbursement it is rejected |
Other payee fields, including currentAddress, are optional. Amounts, webhookUrl and the other disbursement fields follow the usual rules: see Amount Fields: submittedAmount and authAmount, KZ: Bank Card - Disbursements and RU: Bank Card - Disbursements.
HTTP status: 200. The response has the usual structure of an Intent with disbursements; the example is an excerpt.
{
"intentId": "241485468702138350",
"payments": [],
"disbursements": [
{
"id": "241485668832161722",
"submittedAmount": {
"value": 12300.00,
"currency": "KZT"
},
"authAmount": {
"value": 12300.00,
"currency": "KZT"
},
"payeePaymentInstrument": {
"id": "PI352596778994327283",
"storedDetails": {
"maskedNumber": "411111******1111",
"lastFour": "1111",
"bin": "411111"
}
},
"createdAt": "2026-10-20T10:20:00Z",
"status": "ACCEPTED",
"clientReferenceTransactionId": "payout-20261020-0001-1"
}
],
"paymentCreationErrors": [],
"disbursementCreationErrors": [],
"clientReferenceIntentId": "payout-20261020-0001",
"intentStatus": "CREATED"
}The response shows the masked card number of the bound card. It does not contain the full number or the pi_ token, and the disbursement webhook carries no card details. Match payouts to your records by clientReferenceTransactionId: it returns the clientReferenceId you set on the disbursement. The final status (PAID_OUT, DECLINED or ERROR) is delivered by webhook, as for any disbursement.
The token is checked before anything is created. If any disbursement in the request has an invalid token, the whole request is rejected with an HTTP error, and no Intent or disbursement is created.
A token has no expiry date and no limit on the number of payouts. Your payout limits and duplicate-payout checks apply as for payouts with card details. If the payee's card is reissued with the same number, the token keeps working; a card with a new number must be bound again.
The disbursement possibility check does not use the token: to check a payout, send the card details in that request, as before.
Delete a payee card
Endpoint: DELETE /api/v1/payment-instruments/{paymentInstrumentToken}
Example: DELETE /api/v1/payment-instruments/pi_01J8ZC4M7QK3T5V9XB2NDR6HAF?merchantPayeeReference=payee-12345
Parameters
| Parameter | In | Required | Description |
|---|---|---|---|
paymentInstrumentToken | Path | Yes | The payee card token |
merchantPayeeReference | Query | Yes | The same payee reference used when binding |
Do not combine merchantPayeeReference with payerId or merchantPayerReference: such a request is rejected with HTTP 400. Those parameters select customer cards; see Manage Saved Cards.
HTTP status: 200 with an empty body.
Repeating the deletion of the same token for the same payee also returns 200. An unknown token or a different payee returns 404.
After deletion:
- New payouts by this token are rejected with HTTP
404andINSTRUMENT_NOT_FOUND. - Binding the same card again creates a new token.
- Payouts already created are not affected: deletion does not cancel a payout in progress.
Errors
| HTTP status | errorCode | Request | Meaning |
|---|---|---|---|
400 | 400 | Binding | Unsupported payment method, invalid card number, a field other than number in incomingDetails, or a blank or too long merchantPayeeReference |
400 | 400 | Payout | The payment method does not support tokens, card details are sent together with the token, payee.merchantPayeeReference is missing or tokenPaymentReason is sent |
400 | 400 | Deletion | merchantPayeeReference is combined with payerId or merchantPayerReference |
403 | CAPABILITY_DISABLED | Binding, payout, deletion | H2H tokenization is not enabled for the settlement account |
404 | INSTRUMENT_NOT_FOUND | Payout, deletion | The token is unknown or deleted, or it belongs to another settlement account or another payee. For a payout, also: another payment method, or a customer's saved card |
502 | UPSTREAM_UNAVAILABLE | Binding | Card tokenization is temporarily unavailable. Nothing was bound; retry later |
For a payout, 404 is returned in the same form in every case listed above, so the response does not reveal whether a token exists outside your scope. Check the token, the payee reference and the payment method.
Error response object
| Field | Type | Description |
|---|---|---|
errorCode | Number or string | Numeric 400 for an invalid request, or the named error code shown above |
failureMessage | String | Explanation of the error |
timestamp | Number | Time the error response was generated, as a Unix timestamp in seconds; it can include a fractional part |
Example HTTP 404 response excerpt:
{
"errorCode": "INSTRUMENT_NOT_FOUND",
"failureMessage": "Saved payment instrument not found"
}Errors that occur after the disbursement is created, such as a decline by the bank, are reported as for any payout: see Creation Errors and Processing Errors.
Test the integration
Use a sandbox settlement account with H2H tokenization enabled and a payout route for the payment method. Test card numbers and status triggers are the same as for payouts with card details: see the test data in KZ: Bank Card - Disbursements and RU: Bank Card - Disbursements.
Cover the complete journey: bind a card, bind it again and check that the token is the same, pay out by the token through both endpoints, make several payouts by one token, try the token with another payee reference and receive 404, delete the binding, and check that a payout by the deleted token is rejected.
Updated about 1 hour ago
