EN RU

Merchant Integration API

Overview

This document describes the API for merchant integration with the Kotleta payment platform. The API allows merchants to create payment requests (pay-in), receive status updates via webhooks, check payment statuses, and manage their account.

Production Base URL: https://kotleta.best Sandbox Base URL: https://deora2.tech


Authorization

Obtaining Credentials

To begin integration, you must:

  1. Request access to the merchant dashboard from your account manager.
  2. Create a store (if one is not already created for you).
  3. Obtain your API Key and Secret Key from the store settings page.

Authentication Headers

All requests to the Merchant API must include the following headers:

Header Description
X-API-Key Your merchant API key (format: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
X-Signature HMAC-SHA256 signature of the request (see below)
Content-Type application/json for JSON requests

Signature Generation

The X-Signature header is a Base64-encoded HMAC-SHA256 signature computed from the concatenation of:

  1. HTTP method (GET, POST, etc.)
  2. Full request URL (including query parameters)
  3. Request body (only for application/json requests; empty string for GET requests)

Signature formula:

X-Signature = Base64(HMAC-SHA256(secret_key, METHOD + URL + BODY))

Code Examples

PHP:

<?php
$method = 'POST';
$url = 'https://kotleta.best/api/v1/payments';
$body = json_encode(['currency' => 'RUB', 'amount' => 5000]);
$secretKey = 'your_secret_key';

$stringToSign = $method . $url . $body;
$signature = base64_encode(hash_hmac('sha256', $stringToSign, $secretKey, true));

// Set header: X-Signature: $signature

JavaScript (Node.js):

const crypto = require('crypto');

const method = 'POST';
const url = 'https://kotleta.best/api/v1/payments';
const body = JSON.stringify({ currency: 'RUB', amount: 5000 });
const secretKey = 'your_secret_key';

const stringToSign = method + url + body;
const signature = crypto
  .createHmac('sha256', secretKey)
  .update(stringToSign)
  .digest('base64');

// Set header: X-Signature: <signature>

Python:

import hmac
import hashlib
import base64
import json

method = 'POST'
url = 'https://kotleta.best/api/v1/payments'
body = json.dumps({'currency': 'RUB', 'amount': 5000})
secret_key = 'your_secret_key'

string_to_sign = method + url + body
signature = base64.b64encode(
    hmac.new(
        secret_key.encode(),
        string_to_sign.encode(),
        hashlib.sha256
    ).digest()
).decode()

# Set header: X-Signature: <signature>

Host-to-Host Integration

Host-to-Host integration enables direct server-to-server communication between the merchant's backend and the payment platform API, allowing full automation of payment processing.

Pay-In Request Creation

The pay-in flow allows merchants to accept payments from their customers.

Flow Overview

1. Merchant server  -> POST /api/v1/payments       -> Creates payment, receives requisites
2. Merchant         -> Displays requisites to user  -> User transfers funds
3. Platform         -> Detects incoming transfer    -> Confirms payment
4. Platform         -> POST to merchant callback    -> Notifies merchant of status change
5. Merchant         -> GET /api/v1/payments/{id}    -> (Optional) Verify final status

Step 1: Create Payment

The merchant sends a payment creation request. The system routes the payment to an available trader, selects appropriate requisites (card number or SBP phone), and returns them in the response.

Request:

POST /api/v1/payments
Content-Type: application/json
X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
X-Signature: <computed_signature>
{
  "currency": "RUB",
  "amount": 5000.00
}

Response (201 Created):

{
  "success": true,
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "waiting_sms",
    "currency": "RUB",
    "amount": 5000.00,
    "amount_usdt": 52.63,
    "external_id": "ext-unique-id",
    "requisite": {
      "type": "card",
      "card_number": "2200 1234 5678 9012",
      "card_holder": "IVAN IVANOV",
      "bank_name": "Sberbank",
      "bank_code": "sberbank",
      "timer_seconds": 900
    },
    "expires_at": "2026-02-15T15:15:00Z",
    "created_at": "2026-02-15T15:00:00Z"
  }
}

The merchant must display the returned requisites to the customer and instruct them to transfer the exact amount. The payment expires after the timer_seconds period (default 15 minutes).

Possible requisite types:

Step 2: Payment Confirmation

Once the customer completes the transfer, the platform automatically detects the incoming payment. No manual confirmation endpoint is required from the merchant side — the system handles detection automatically via its internal network.

Step 3: Webhook Notification

Upon payment confirmation, the platform sends a webhook to the merchant's configured callback_url. See the Callbacks section for details.

Pay-Out Request Creation

Note: Pay-out functionality is currently under development. Contact your account manager for the timeline and early access.

The pay-out flow will allow merchants to send funds to customer bank accounts or cards.


Callbacks (Webhooks)

Overview

The platform sends HTTP POST callbacks to the merchant's callback_url whenever a payment status changes. The callback URL is configured in the merchant profile (not per-request).

Authentication

Each webhook request includes an X-Signature header computed using the merchant's secret key:

X-Signature = Base64(HMAC-SHA256(secret_key, "POST" + webhook_url + body))

The merchant must validate this signature to ensure the webhook is authentic and has not been tampered with.

Webhook Payload

{
  "payment_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "external_id": "ext-unique-id",
  "status": "confirmed",
  "currency": "RUB",
  "amount": 5000.00,
  "amount_usdt": 52.63,
  "tx_id": "",
  "confirmed_at": "2026-02-15T15:05:30Z",
  "timestamp": "2026-02-15T15:05:31Z"
}

Webhook Payload Fields

Field Type Description
payment_id string (UUID) Unique payment identifier in the platform
external_id string External reference ID
status string New payment status (see PaymentStatus)
currency string Payment currency (e.g., RUB)
amount number Payment amount in original currency
amount_usdt number Payment amount converted to USDT
tx_id string Blockchain transaction ID (when completed)
confirmed_at string (ISO 8601) Confirmation timestamp
timestamp string (ISO 8601) Webhook generation timestamp

Expected Response

The merchant must respond with HTTP status code 200 OK. Any other status code is considered a failure, and the webhook will be retried.

Retry Policy

If the merchant's server fails to respond with 200, the platform retries with exponential backoff:

Attempt Delay
1 5 minutes
2 15 minutes
3 1 hour
4 6 hours
5-10 24 hours

Maximum 10 retry attempts. After exhaustion, the webhook is marked as failed and can be manually retried through the dashboard or API.

Signature Verification Example

PHP:

<?php
$secretKey = 'your_secret_key';
$webhookUrl = 'https://yoursite.com/webhook';
$body = file_get_contents('php://input');
$receivedSignature = $_SERVER['HTTP_X_SIGNATURE'];

$stringToSign = 'POST' . $webhookUrl . $body;
$expectedSignature = base64_encode(
    hash_hmac('sha256', $stringToSign, $secretKey, true)
);

if (!hash_equals($expectedSignature, $receivedSignature)) {
    http_response_code(403);
    exit('Invalid signature');
}

// Process the webhook...
http_response_code(200);

Python:

import hmac
import hashlib
import base64

secret_key = 'your_secret_key'
webhook_url = 'https://yoursite.com/webhook'
body = request.get_data(as_text=True)
received_signature = request.headers.get('X-Signature')

string_to_sign = 'POST' + webhook_url + body
expected_signature = base64.b64encode(
    hmac.new(
        secret_key.encode(),
        string_to_sign.encode(),
        hashlib.sha256
    ).digest()
).decode()

if not hmac.compare_digest(expected_signature, received_signature):
    return 'Invalid signature', 403

# Process the webhook...
return 'OK', 200

API Reference

Create Payment

Creates a new pay-in payment request and returns transfer requisites.

Endpoint: POST /api/v1/payments

Authentication: API Key + Signature

Request Headers:

Header Required Description
X-API-Key Yes Merchant API key
X-Signature Yes Request signature
Content-Type Yes application/json

Request Body:

Parameter Type Required Description
currency string Yes Currency code, 3 characters (e.g., RUB, USD, EUR, THB)
amount number Yes Payment amount (minimum: 100)

Request Example:

{
  "currency": "RUB",
  "amount": 5000.00
}

Response (201 Created):

{
  "success": true,
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "waiting_sms",
    "currency": "RUB",
    "amount": 5000.00,
    "amount_usdt": 52.63,
    "external_id": "generated-uuid",
    "requisite": {
      "type": "card",
      "card_number": "2200 1234 5678 9012",
      "card_holder": "IVAN IVANOV",
      "bank_name": "Sberbank",
      "bank_code": "sberbank",
      "timer_seconds": 900
    },
    "expires_at": "2026-02-15T15:15:00Z",
    "created_at": "2026-02-15T15:00:00Z"
  }
}

Error Responses:

Status Description
400 Invalid request body, missing required fields, or no callback URL configured
401 Invalid or missing API key, or invalid signature
500 Internal server error (no available traders/requisites)

Get Payment by ID

Retrieves the current status and details of a payment.

Endpoint: GET /api/v1/payments/{id}

Authentication: API Key + Signature

URL Parameters:

Parameter Type Description
id string (UUID) Payment identifier

Response (200 OK):

{
  "success": true,
  "data": {
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "confirmed",
    "currency": "RUB",
    "amount": 5000.00,
    "amount_usdt": 52.63,
    "external_id": "generated-uuid",
    "created_at": "2026-02-15T15:00:00Z",
    "updated_at": "2026-02-15T15:05:30Z"
  }
}

Error Responses:

Status Description
400 Invalid payment ID format
404 Payment not found

Get Merchant Payments

Retrieves a paginated list of payments for the authenticated merchant.

Endpoint: GET /api/v1/merchants/{merchant_id}/payments

Authentication: JWT Bearer Token

URL Parameters:

Parameter Type Description
merchant_id string (UUID) Merchant identifier

Query Parameters:

Parameter Type Default Description
page integer 1 Page number
page_size integer 20 Items per page (max: 100)

Response (200 OK):

{
  "success": true,
  "data": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "status": "confirmed",
      "currency": "RUB",
      "amount": 5000.00,
      "amount_usdt": 52.63,
      "external_id": "generated-uuid",
      "created_at": "2026-02-15T15:00:00Z"
    }
  ],
  "pagination": {
    "total": 150,
    "page": 1,
    "page_size": 20,
    "total_pages": 8
  }
}

Webhook Management

Endpoints for monitoring and managing webhook delivery.

Authentication: JWT Bearer Token

Get Webhook Statistics

Endpoint: GET /api/v1/merchant/webhooks/stats

Response:

{
  "success": true,
  "data": {
    "pending": 2,
    "processing": 0,
    "completed": 145,
    "failed": 3
  }
}

Get Failed Webhooks

Endpoint: GET /api/v1/merchant/webhooks/failed

Retry Failed Webhook

Endpoint: POST /api/v1/merchant/webhooks/{id}/retry

Get Webhook Details

Endpoint: GET /api/v1/merchant/webhooks/{id}


API Object Descriptions

PaymentStatus

Status Description
created Payment has been created, trader assignment in progress
waiting_sms Requisites assigned, waiting for customer to transfer funds
confirmed Payment confirmed — funds received and verified
completed Payment fully completed — settlement processed
failed Payment failed at any processing stage
expired Payment not completed within the timeout period (default: 15 min)

PaymentResponse

Field Type Description
id string (UUID) Unique payment identifier
status PaymentStatus Current payment status
currency string Payment currency code (e.g., RUB)
amount number Payment amount in original currency
amount_usdt number Payment amount converted to USDT
external_id string External reference identifier
requisite RequisiteDTO Transfer requisites (when status is waiting_sms)
expires_at string (ISO 8601) Payment expiration timestamp
created_at string (ISO 8601) Payment creation timestamp

RequisiteDTO

Field Type Description
type string Requisite type: card or sbp
phone_number string SBP phone number (for type sbp)
card_number string Bank card number (for type card)
card_holder string Cardholder name (for type card)
bank_name string Bank name
bank_code string Bank identifier code
timer_seconds integer Time in seconds before payment expires

WebhookPayload

Field Type Description
payment_id string (UUID) Payment identifier
external_id string External reference ID
status PaymentStatus Updated payment status
currency string Payment currency
amount number Amount in original currency
amount_usdt number Amount in USDT
tx_id string Blockchain transaction hash (on completion)
confirmed_at string (ISO 8601) Confirmation timestamp
timestamp string (ISO 8601) Webhook timestamp

Error Handling

Response Format

All error responses follow the same format:

{
  "success": false,
  "error": "Error description message"
}

HTTP Status Codes

Code Description
200 Success
201 Created successfully
400 Bad Request — invalid parameters or business logic violation
401 Unauthorized — invalid or missing API key / signature
404 Not Found — requested resource does not exist
409 Conflict — duplicate request (idempotency)
500 Internal Server Error

Common Error Messages

Error Cause
Invalid request body Malformed JSON or missing required fields
currency must be 3 characters Currency code not in ISO 4217 format
Merchant not authenticated Missing or invalid X-API-Key header
Invalid signature X-Signature does not match computed value
Merchant has no callback URL configured Merchant profile missing callback_url
Payment not found or already processed Payment ID not found or in terminal state
Payment is not waiting for SMS confirmation Payment in unexpected status

Supported Currencies

Code Currency
RUB Russian Ruble
USD US Dollar
EUR Euro
THB Thai Baht

Note: Available currencies depend on the merchant's traffic group configuration. Contact your account manager for activation.


Rate Limits

API requests are rate-limited per merchant. Default limits:

Endpoint Limit
POST /payments 60 requests/minute
GET /payments/* 120 requests/minute

Exceeding rate limits returns HTTP 429 (Too Many Requests).


Testing and Sandbox

Use the sandbox environment at https://deora2.tech for integration testing. Sandbox API keys use the prefix sk_test_.

All flows work identically to production, but no real money movement occurs.