Skip to content

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

  1. Create a payment session with a unique transaction. To return the user to the merchant's site after 3-D Secure, pass redirect_url in the session.
  2. Collect the user's browser data and pass either the new card's details in card or the linked card's data in customer_card.
  3. Build the signature depending on the chosen card option and send the payment request.
  4. On receiving 200, redirect the user's browser to form_url. On this page KVELL performs the necessary redirects to the bank's pages and 3-D Secure, then redirects the user to the redirect_url from the session. If redirect_url was not passed, the default KVELL payment page URL is used.
  5. 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

POST https://api.pay.kvell.group/v1/orders/card2account
POST https://api.pay.stage.kvell.group/v1/orders/card2account

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:

SHA256(X-Api-Key + transaction + card.pan + card.expire + card.cvv + card.holder + secret_key)

If using a linked card in the customer_card object:

SHA256(X-Api-Key + transaction + customer_card.token + customer_card.cvv + secret_key)

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.

Example 200 (OK) response
{
  "form_url": "https://api.pay.kvell.group/3ds/form?f=<form-data>"
}

Response parameters

Parameter Type Description
form_url string URL to continue the payment flow and go through 3-D Secure.

What to do next

  1. Redirect the user's browser to form_url.
  2. After returning to redirect_url, get the transaction status or handle the callback.
Example 400 (Bad Request) response
{
  "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

  1. For 20007, do not create a duplicate — request the status of the original transaction.
  2. For 20004, re-create the session with the same transaction, then retry the payment request.
  3. For other codes, follow the recommendation from the HTTP errors reference.
Example 403 (Forbidden) response
{
  "errors": [
    {
      "code": 20037,
      "message": "Access denied"
    }
  ]
}

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.

Example 404 (Not Found) response
{
  "errors": [
    {
      "code": 20006,
      "message": "Shop not found"
    }
  ]
}

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.

Example 422 (Unprocessable Entity) response
{
  "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.
Example 5XX (Internal Server Error) response
{
  "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

  1. Do not create a new payment and do not change transaction.
  2. Request the transaction status for the original transaction.
  3. If the transaction is found, keep checking it until it reaches a final status, or wait for the callback.
  4. 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.

{
  "bind_card": true,
  "customer_key": "customer-42"
}

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.