Card Payment via H2H
H2H (Host-to-Host) is a way to accept payments without using the KVELL payment page. Use H2H if you want to build your own payment page.
The method creates a single-stage payment using bank card details. Before calling the method, you must create a payment session; the payment result is determined later from the transaction status or a callback.
Handling card data
To use the H2H integration, the merchant must have a valid document confirming compliance with PCI DSS requirements.
Do not store or log card.pan and card.cvv. The card.cvv value must be used only for
processing the current payment.
Integration flow
- Create a payment session with a unique
transaction. To return the user to the merchant's site after 3-D Secure, passredirect_urlin the session. - Collect the user's browser data and pass either the new card's details in
cardor the linked card's data incustomer_card. - Build the signature depending on the chosen card option and send the payment request.
- On receiving
200, redirect the user's browser toform_url. On this page KVELL performs the necessary redirects to the bank's pages and 3-D Secure, then redirects the user to theredirect_urlfrom the session. Ifredirect_urlwas not passed, the default KVELL payment page URL is used. - Determine the payment result from the transaction status: use a callback or poll the status method at 10-minute intervals until you receive a final status.
URL
Request
Headers
| Name | Type | Required | Description |
|---|---|---|---|
X-Api-Key |
string | Yes | Shop identifier. |
X-Signature |
string | Yes | Signature of the request. |
Building the signature
Concatenate the values without separators in the specified order, compute SHA-256 of the UTF-8 encoded string, and pass
the resulting lowercase hex hash in the X-Signature header.
If using a new card's details in the card object:
If using a linked card in the customer_card object:
secret_key is found in the shop's settings. Do not add spaces, line breaks, or other separators between
values. The values must exactly match the data in the JSON being sent.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
transaction |
string | Yes | Unique transaction number on the merchant's side, passed when creating the session. |
card |
object | Conditional | New card details. Required if customer_card is not passed. |
customer_card |
object | Conditional | Linked card data. Required if card is not passed. |
customer_key |
string | null | Conditional | Customer identifier in the merchant's system. Required when bind_card: true. |
browser_info |
object | Yes | User browser data. Field descriptions are given below. |
customer |
string | null | No | Payer identifier in the merchant's system. |
bind_card |
boolean | No | true — link the card in KVELL after a successful payment. Defaults to false. |
Choose a single source of card data
Pass exactly one object: card or customer_card. The signature formula depends on the chosen object.
card object
| Parameter | Type | Required | Description |
|---|---|---|---|
pan |
string | Yes | Card number: 12 to 19 digits, validated using the Luhn algorithm. |
expire |
string | Yes | Card expiration date in YYYY-MM format, e.g. 2028-12. |
cvv |
string | Yes | Three-digit card security code. |
holder |
string | Yes | Cardholder name as printed on the card. |
customer_card object
| Parameter | Type | Required | Description |
|---|---|---|---|
customer_key |
string | Yes | Identifier of the customer who owns the card. |
token |
string | Yes | Token from the linked cards list method. |
cvv |
string | Yes | Three-digit card security code. |
browser_info object
The data must be collected from the user's browser immediately before creating the payment.
| Parameter | Type | Required | Description |
|---|---|---|---|
user_agent |
string | Yes | The User-Agent HTTP header contents. Use navigator.userAgent. Maximum length is 2048 characters. |
accept_header |
string | No | The Accept HTTP header contents received from the user's browser. Maximum length is 2048 characters. Defaults to application/json, text/plain, */*. |
color_depth |
integer | Yes | Screen color depth in bits per pixel — the screen.colorDepth value. Valid values: 1, 4, 8, 15, 16, 24, 32, 48; no more than 2 digits. |
ip |
string | Yes | Public IPv4 or IPv6 address of the user's browser. IPv4 is passed as four decimal groups separated by ., IPv6 as eight hexadecimal groups separated by :. |
language |
string | No | Browser language in IETF BCP 47 format, e.g. ru-RU; no more than 8 characters. Defaults to ru-RU. |
screen_width |
integer | Yes | The user's full screen width in pixels — the screen.width value; no more than 6 digits. |
screen_height |
integer | Yes | The user's full screen height in pixels — the screen.height value; no more than 6 digits. |
screen_print |
string | Yes | A string with the current and available screen resolution and the color depth. See the example below for the format. |
tz |
integer | Yes | The difference between UTC and the browser's local time in minutes — the new Date().getTimezoneOffset() value; no more than 5 characters including the sign. For example, -180 for Moscow. |
time_zone |
string | Yes | The time zone name from Intl.DateTimeFormat().resolvedOptions().timeZone, e.g. Europe/Moscow. |
java_enabled |
boolean | Yes | Indicates whether Java is available in the browser — the result of navigator.javaEnabled(): true or false. |
device_channel |
string | Yes | Device type: 01 — merchant's mobile app, 02 — user's browser, 03 — 3DS Requestor. For this H2H flow, pass 02. |
Building browser_info in the browser
Pass the ip and accept_header values into the function from your backend: obtain them from the user's incoming request.
The remaining parameters can be collected in the browser immediately before creating the payment.
function collectBrowserInfo(ip, acceptHeader) {
const screen = window.screen;
return {
user_agent: navigator.userAgent,
accept_header: acceptHeader || "application/json, text/plain, */*",
color_depth: screen.colorDepth,
ip,
language: navigator.language || "ru-RU",
screen_width: screen.width,
screen_height: screen.height,
screen_print:
`Current Resolution: ${screen.width}x${screen.height}, ` +
`Available Resolution: ${screen.availWidth}x${screen.availHeight}, ` +
`Color Depth: ${screen.colorDepth}`,
tz: new Date().getTimezoneOffset(),
time_zone: Intl.DateTimeFormat().resolvedOptions().timeZone,
java_enabled:
typeof navigator.javaEnabled === "function"
? navigator.javaEnabled()
: false,
device_channel: "02"
};
}
Example
{
"transaction": "payment-20260807-0001",
"browser_info": {
"user_agent": "Mozilla/5.0",
"accept_header": "application/json, text/plain, */*",
"color_depth": 24,
"ip": "203.0.113.10",
"language": "ru-RU",
"screen_width": 1920,
"screen_height": 1080,
"screen_print": "Current Resolution: 1920x1080, Available Resolution: 1920x1040, Color Depth: 24",
"tz": -180,
"time_zone": "Europe/Moscow",
"java_enabled": false,
"device_channel": "02"
},
"card": {
"pan": "5100000000000123",
"expire": "2034-12",
"cvv": "123",
"holder": "VASYA PUPKIN"
}
}
curl --request POST \
--url 'https://api.pay.kvell.group/v1/orders/card2account' \
--header 'Content-Type: application/json' \
--header 'X-Api-Key: <api-key>' \
--header 'X-Signature: <signature>' \
--data '{
"transaction": "payment-20260807-0001",
"browser_info": {
"user_agent": "Mozilla/5.0",
"accept_header": "application/json, text/plain, */*",
"color_depth": 24,
"ip": "203.0.113.10",
"language": "ru-RU",
"screen_width": 1920,
"screen_height": 1080,
"screen_print": "Current Resolution: 1920x1080, Available Resolution: 1920x1040, Color Depth: 24",
"tz": -180,
"time_zone": "Europe/Moscow",
"java_enabled": false,
"device_channel": "02"
},
"card": {
"pan": "5100000000000123",
"expire": "2034-12",
"cvv": "123",
"holder": "VASYA PUPKIN"
}
}'
Response
Select the HTTP code to see an example, the response parameters, and recommended actions.
If no HTTP response is received
Handle a timeout or connection drop the same way as a 5XX response: the payment creation result is unknown. First
request the status for the original transaction and do not create a new payment until the result of the original
one is determined.
Response parameters
| Parameter | Type | Description |
|---|---|---|
form_url |
string | URL to continue the payment flow and go through 3-D Secure. |
What to do next
- Redirect the user's browser to
form_url. - After returning to
redirect_url, get the transaction status or handle the callback.
{
"errors": [
{
"code": 20007,
"message": "The transaction was already made"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in the «HTTP response errors» section. |
errors[].code |
integer | Error code. 20007 in the example. |
errors[].message |
string | Description of why the request was rejected. |
What to do next
- For
20007, do not create a duplicate — request the status of the originaltransaction. - For
20004, re-create the session with the sametransaction, then retry the payment request. - For other codes, follow the recommendation from the HTTP errors reference.
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in the «HTTP response errors» section. |
errors[].code |
integer | Error code. 20037 for access denial. |
errors[].message |
string | Description of why access was denied. |
What to do next
Check the shop's status and contact your KVELL manager. Only retry the request after access is restored.
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in the «HTTP response errors» section. |
errors[].code |
integer | Error code. 20006 in the example. |
errors[].message |
string | Description of why the resource was not found. |
What to do next
Check X-Api-Key and whether you're on the Stage or Production environment. Do not retry the request with the same data until you fix the cause.
{
"errors": [
{
"code": 20098,
"message": "transaction: Field required"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of validation errors. Codes and handling recommendations are given in the «HTTP response errors» section. |
errors[].code |
integer | Validation error code. 20098 for a field error. |
errors[].message |
string | Field name and validation failure reason. |
{
"errors": [
{
"code": 20000,
"message": "Unknown error"
}
]
}
Response parameters
| Parameter | Type | Description |
|---|---|---|
errors |
array | List of errors. Codes and handling recommendations are given in the «HTTP response errors» section. |
errors[].code |
integer | Error code. 20000 for an unknown error. |
errors[].message |
string | Description of the technical error. |
5XX, timeout, and connection drop
In all of these cases the request result is considered undetermined: the payment may have been created even if the client did not receive a response. Keep the operation in your system in a "processing" state until you get a confirmed result from KVELL.
Common handling algorithm
- Do not create a new payment and do not change
transaction. - Request the transaction status for the original
transaction. - If the transaction is found, keep checking it until it reaches a final status, or wait for the callback.
- If status requests keep failing with a technical error, contact support and provide the
transaction.
Linking a card during a payment
To link a new card to the customer in KVELL, add bind_card: true to the request and pass customer_key.
Paying with a linked card
If the card was linked previously, pass customer_card instead of card and build the signature using the formula for
a linked card.
{
"transaction": "payment-20260807-0002",
"browser_info": {
"user_agent": "Mozilla/5.0",
"accept_header": "application/json, text/plain, */*",
"color_depth": 24,
"ip": "203.0.113.10",
"language": "ru-RU",
"screen_width": 1920,
"screen_height": 1080,
"screen_print": "Current Resolution: 1920x1080, Available Resolution: 1920x1040, Color Depth: 24",
"tz": -180,
"time_zone": "Europe/Moscow",
"java_enabled": false,
"device_channel": "02"
},
"customer_card": {
"customer_key": "customer-42",
"token": "<card-token>",
"cvv": "123"
}
}
The card becomes available in the get cards list method after the payment successfully
completes with the completed status. The link applies within the shop identified by X-Api-Key.