Public key for the cryptogram
Returns the current KVELL public RSA key. This key is used on the merchant's side to encrypt bank card details before sending them to KVELL: instead of plaintext details, a cryptogram is sent.
Integration flow
- Request the public key with this method.
- Store
public_keyandkey_idin your cache untilrefresh_after. - Encrypt the card details with the obtained key to get the cryptogram.
- Pass the cryptogram to the API method that accepts it.
- Before using the key again, check
refresh_afterand request the key again if needed.
URL
Request
Headers
| Name | Type | Required | Description |
|---|---|---|---|
X-Api-Key |
string | Yes | Shop identifier. |
Example
curl --request GET \
--url 'https://api.pay.stage.kvell.group/v1/cryptogram/public-key' \
--header 'X-Api-Key: 00000000-0000-4000-8000-000000000000'
Response
Select an HTTP code to see an example, response parameters, and recommended actions.
{
"public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu6tJntpB49kze14gfIkQ\nAQIDAQAB\n-----END PUBLIC KEY-----\n",
"key_id": "068eb60b259651c4",
"refresh_after": "2026-08-20T10:20:48.650203Z"
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
public_key |
string | RSA-2048 public key in PEM format. |
key_id |
string | Key identifier. Computed as SHA-256 of the DER representation of SubjectPublicKeyInfo, truncated to the first 16 characters, so you can recompute it yourself from public_key. |
refresh_after |
string | The UTC moment, in ISO 8601, until which the returned key is guaranteed to be valid. See «Caching and key rotation» for details. |
What to do next
- Store
public_keyandkey_idin your cache untilrefresh_after. - Use the key to build the cryptogram.
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and 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 recommendations are given in «HTTP response errors». |
errors[].code |
integer | Error code. In the example — 20006. |
errors[].message |
string | Description of the resource that was not found. |
{
"errors": [
{
"code": 20098,
"message": "x-api-key: Field required"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of validation errors. Codes and recommendations are given in «HTTP response errors». |
errors[].code |
integer | Error code. For a field error — 20098. |
errors[].message |
string | The field and reason for the validation error. |
{
"errors": [
{
"code": 20043,
"message": "The cryptogram public key is temporarily unavailable"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and recommendations are given in «HTTP response errors». |
errors[].code |
integer | Technical error code. |
errors[].message |
string | Description of the technical error. |
What to do next
- Safely retry the same GET request with the same
X-Api-Key. - If the error keeps recurring, contact KVELL support with the URL, request time, HTTP code,
code, andmessage.
How the cryptogram is built
Working with card data
Card details are encrypted on the merchant's side, and only the cryptogram is ever sent to KVELL. Do not store or log the card number and CVV, including intermediate values before encryption.
The cryptogram is built in two steps.
-
Build a string with the card details. Fields are separated by two colons, in a fixed order:
Field Required Description panYes Card number. cvvNo Card verification code. holderNo Cardholder name. exp_dateNo Card expiration date, exactly 5 characters in YY/MMformat, e.g.26/01.Optional fields can be omitted by trimming the string from the right:
4111111111111111::123and4111111111111111are also valid. A field in the middle cannot be skipped — an empty value will be parsed as an empty string, not as absent. -
Encrypt the string with the public key and Base64-encode the result.
Parameter Value Algorithm RSA-OAEP Hash function SHA-256 Mask generation function MGF1 with SHA-256 Label Not used Result encoding Base64 with the standard alphabet and padding
With a 2048-bit key and OAEP with SHA-256, the maximum length of the source string is 190 bytes. Full card details fit within this limit.
Examples
The examples use the test card 4111111111111111 with expiration date 2034-12, written in the
cryptogram as 34/12.
Required package: pip install cryptography.
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_public_key
# value of the public_key field from the method response
public_key_pem = '-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n'
public_key = load_pem_public_key(public_key_pem.encode('utf-8'))
payload = '4111111111111111::123::TEST HOLDER::34/12'
cryptogram = base64.b64encode(
public_key.encrypt(
payload.encode('utf-8'),
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None,
),
)
).decode('ascii')
print(cryptogram)
Uses the built-in node:crypto module.
const crypto = require('node:crypto');
// value of the public_key field from the method response
const publicKeyPem = '-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n';
const payload = '4111111111111111::123::TEST HOLDER::34/12';
const cryptogram = crypto.publicEncrypt(
{
key: publicKeyPem,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: 'sha256',
},
Buffer.from(payload, 'utf8'),
).toString('base64');
console.log(cryptogram);
# 1. Save the public key to a file
curl -s --request GET \
--url 'https://api.pay.stage.kvell.group/v1/cryptogram/public-key' \
--header 'X-Api-Key: 00000000-0000-4000-8000-000000000000' \
| jq -r '.public_key' > public_key.pem
# 2. Encrypt the card details and Base64-encode the result
printf '%s' '4111111111111111::123::TEST HOLDER::34/12' \
| openssl pkeyutl -encrypt -pubin -inkey public_key.pem \
-pkeyopt rsa_padding_mode:oaep \
-pkeyopt rsa_oaep_md:sha256 \
-pkeyopt rsa_mgf1_md:sha256 \
| openssl base64 -A
Caching and key rotation
The key can be cached. The refresh_after field indicates the moment until which the returned value is guaranteed
to be valid; after that moment, request the key again. There is no need to request the key before every operation.
Key rotation does not require a synchronous cutover: cryptograms encrypted with a key obtained before the rotation
are still accepted for some time afterward. To avoid relying on this grace period, honor refresh_after.
The key_id value changes together with the key, so it's convenient for detecting that the key has been updated and
for referencing it when contacting support.