Retrieving a card by transaction
Returns the customer's linked card that was used for the operation, by the transaction identifier in the
merchant's system. The response contains the card token for repeat payments, the masked card number, the issuing
bank details and, if a public key is set for the shop, the encrypted card number hash.
Integration flow
- Process an operation with card linking: pass
customer_keyandbind_card: true, for example in a card payment via H2H or a payment with preauthorization. - Save the
transactionandcustomer_keyof this operation. - Generate a signature from
X-Api-Key,transaction,customer_key, andsecret_key. - Send the request with the same
X-Api-Keyused to create the operation. - Save the card
tokento use it for payments with the linked card.
URL
Request
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_key |
string | Yes | Customer identifier in the merchant's system. |
transaction |
string | Yes | Operation identifier in the merchant's system. |
Headers
| Name | Type | Required | Description |
|---|---|---|---|
X-Api-Key |
string | Yes | Shop identifier. |
X-Signature |
string | Yes | Request signature. |
Building the signature
Concatenate X-Api-Key, transaction, customer_key, and secret_key without separators, compute the SHA-256
hash of the resulting UTF-8 encoded string, and pass the hash in lowercase in the X-Signature header.
secret_key is found in the shop's settings.
Example
Response
Select an HTTP code to see an example, the response parameters, and the recommended actions.
{
"customer_id": 25,
"token": "4VNSbS7nF410G8WLEhUUUnflH2UKvWlHxIzN3Jr9JJs",
"pan_mask": "411111******1111",
"holder": "IVAN IVANOV",
"expire": "2034-12",
"created_at": "2026-10-02T07:52:29.720321Z",
"pan_hash_encrypted": "EPNS42BPO6yazHvd6BKd...ObPg6EKv7mYKUy7zc8s=",
"bank_id": "100000000004",
"bank_name": "Tinkoff Bank",
"bank_url": "tinkoff.ru"
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
customer_id |
integer | Customer identifier in KVELL. |
token |
string | Linked card token. Used for paying with a linked card. |
pan_mask |
string | Masked card number. |
holder |
string | null | Cardholder name. |
expire |
string | null | Card expiration date in YYYY-MM format. |
created_at |
string | Date and time the card was linked, in ISO 8601 format. |
pan_hash_encrypted |
string | null | Card number hash encrypted with the shop's public key, in Base64. Decryption is described in «Decrypting the card number hash». |
bank_id |
string | null | Card issuing bank identifier. |
bank_name |
string | null | Card issuing bank name. |
bank_url |
string | null | Card issuing bank website domain. |
What this response means
The operation was found, and the card used for it is linked to the customer.
The bank_id, bank_name, and bank_url fields are determined by the card BIN and are returned as null
if the bank could not be identified. If an error occurs while encrypting the card number hash,
pan_hash_encrypted is returned as null, and the other response fields are filled in.
What to do next
- Save the
tokenand use it for payments with the linked card. - If you need
pan_hash_encrypted, decrypt it with the shop's private key.
{
"errors": [
{
"code": 20002,
"message": "Invalid signature"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in «HTTP response errors». |
errors[].code |
integer | Error code. In the example — 20002. |
errors[].message |
string | Description of the reason the request was rejected. |
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in «HTTP response errors». |
errors[].code |
integer | Error code. In the example — 20037. |
errors[].message |
string | Description of the reason access was denied. |
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in «HTTP response errors». |
errors[].code |
integer | Error code. In the example — 20009. |
errors[].message |
string | Description of the resource that was not found. |
{
"errors": [
{
"code": 20017,
"message": "customer_key is not set"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in «HTTP response errors». |
errors[].code |
integer | Error code. In the example — 20017. |
errors[].message |
string | Reason for the error. |
{
"errors": [
{
"code": 20000,
"message": "Unknown error"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in «HTTP response errors». |
errors[].code |
integer | Error code. For an unknown error — 20000. |
errors[].message |
string | Description of the technical error. |
What to do next
- Safely retry the same
GETrequest with the originalcustomer_keyandtransaction. - If the error persists, contact KVELL support and provide the URL, request time, HTTP code,
code, andmessage.
Decrypting the card number hash
The pan_hash_encrypted field lets you match the card against cards in your system without receiving the card
number. KVELL computes the SHA-256 hash of the card number, writes the result as a lowercase hexadecimal string,
and encrypts it with the shop's public key.
| Parameter | Value |
|---|---|
| Algorithm | RSA-OAEP |
| Hash function | SHA-256 |
| Mask generation function | MGF1 with SHA-256 |
| Label | Not used |
| Result encoding | Base64 |
Decrypt the value with the shop's private key. The result is a string of 64 hexadecimal characters that you can
compare with sha256(pan) computed on your side.
Working with the private key
Store the private key only on your server. Do not send it to KVELL or use it in client-side code.
Requires the package: pip install cryptography.
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
with open('private_key.pem', 'rb') as key_file:
private_key = serialization.load_pem_private_key(key_file.read(), password=None)
# value of the pan_hash_encrypted field from the method response
encrypted_base64 = 'EPNS42BPO6yazHvd6BKd...ObPg6EKv7mYKUy7zc8s='
pan_hash = private_key.decrypt(
base64.b64decode(encrypted_base64),
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
).decode('ascii')
print(pan_hash)
Uses the built-in node:crypto module.
const crypto = require('node:crypto');
const fs = require('node:fs');
const privateKeyPem = fs.readFileSync('private_key.pem', 'utf8');
// value of the pan_hash_encrypted field from the method response
const encryptedBase64 = 'EPNS42BPO6yazHvd6BKd...ObPg6EKv7mYKUy7zc8s=';
const panHash = crypto.privateDecrypt(
{
key: privateKeyPem,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
},
Buffer.from(encryptedBase64, 'base64'),
).toString('ascii');
console.log(panHash);