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:
- Request access to the merchant dashboard from your account manager.
- Create a store (if one is not already created for you).
- 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:
- HTTP method (GET, POST, etc.)
- Full request URL (including query parameters)
- Request body (only for
application/jsonrequests; 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:
card— Bank card transfer (card_number + card_holder)sbp— SBP (Faster Payments System) transfer (phone_number + bank_name)
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.