Silicon SoukMerchant API

Platform

Silicon Souk is a payment processing & dispatch service. We support both PayIn and PayOut across every method in the regions listed below.

Projects & keys

A merchant has one or more projects. Each request is validated and matched to a project by its private API key — that single signed key is enough; we read currency, environment and methods from the project itself.

You receive a public_key and a private_key. The private key never travels in a request body — we only see the sign hash. For GET status, client-status updates and receipt uploads send the private key as the X-Api-Key header over HTTPS.

payment_method
card | sbp | account | iban

Request signature

For create / reject endpoints include sign = SHA256(order_id:public_key:private_key) (hex, lowercase). We authenticate the merchant, check the IP whitelist (if configured) and match the request to a project.

Production: X-Body-Signature is mandatory on every Merchant API request, including GET requests and multipart receipt uploads. Compute lowercase hex HMAC-SHA256(private_key, "v1|METHOD|PATH|RAW_BODY"). RAW_BODY is the exact byte sequence sent on the wire; use an empty byte string for GET. Do not canonicalize or re-serialize JSON.

Python
import hashlib

def merchant_sign(order_id: str, public_key: str, private_key: str) -> str:
    return hashlib.sha256(f"{order_id}:{public_key}:{private_key}".encode()).hexdigest()

def standart_sign(order_id: str, public_key: str, private_key: str) -> str:
    return hashlib.sha256(f"{order_id}:{public_key}:{private_key}:callback".encode()).hexdigest()
X-Body-Signature
import hmac

def body_hmac(private_key: str, method: str, path: str, raw_body: bytes) -> str:
    message = b"v1|" + method.upper().encode() + b"|" + path.encode() + b"|" + raw_body
    return hmac.new(private_key.encode(), message, "sha256").hexdigest()

POST /create_payin

Create a PayIn order. On success we reply HTTP 200 — show the returned requisites to the customer. The final status arrives as a callback (successful, cancelled, failed, rejected_*).

If we cannot allocate any requisite within ~10 seconds we reply 503 with "error": "overloading requisite" — do not treat that order as active, retry with a fresh order_id later.

FieldTypeDescription
order_idstringRequiredYour order id, unique per merchant.
payment_methodenumRequiredcard | sbp | account | iban.
fiat_amountstringRequiredDecimal amount as string, e.g. "15000.00".
fiat_currencystringRequiredISO-like code, e.g. "RUB".
bankstringRequiredRecipient bank. Canonical name from the bank dictionary (e.g. "Сбербанк"). Case-insensitive; English aliases ("Sberbank") are rejected.
signstringRequiredSHA256(order_id:public_key:private_key) hex.
success_callback_urlurlRequiredURL we POST the final success callback to.
error_callback_urlurlOptionalURL for error/cancel callbacks.
timeoutint (мин)OptionalOrder timeout in minutes.
customerstringOptionalYour customer id (free-form).
order_descriptionstringOptionalFree-form description.
Request body
POST https://ssouk.org/applications/v1/create_payin
Content-Type: application/json

{
  "order_id": "ORD-1001",
  "payment_method": "card",
  "fiat_amount": "15000.00",
  "fiat_currency": "RUB",
  "bank": "Сбербанк",
  "timeout": 900,
  "sign": "<sha256_hex>",
  "success_callback_url": "https://merchant.example/cb/success",
  "error_callback_url": "https://merchant.example/cb/error",
  "customer": "user-42",
  "order_description": "Deposit #1001"
}
200 OK response
HTTP/1.1 200 OK

{
  "ok": true,
  "internal_transaction_id": "TRX-20260528-120000-A1B2C3",
  "order_id": "ORD-1001",
  "payment_method": "card",
  "fiat_amount": "15000.00",
  "fiat_currency": "RUB",
  "sum_transaction": "15000.00",
  "currency": "RUB",
  "usdt_amount": "158.7301",
  "merchant_spent_usdt": "160.1234",
  "exchange_rate": "94.50",
  "number_card": "4276123456785678",
  "phone_number": null,
  "number_account": null,
  "iban_number": null,
  "bank": "Сбербанк",
  "full_name": "IVAN I.",
  "sign": "<response_signature>"
}
503
HTTP/1.1 503

{ "ok": false, "error": "overloading requisite" }

POST /reject_payin

Cancel an open PayIn order. Allowed while the order is not yet successful and not yet rejected. We release the hold on your balance.

FieldTypeDescription
order_idstringRequiredYour original order id.
standart_signstringRequiredSHA256(order_id:public_key:private_key:callback) hex.
Request body
POST https://ssouk.org/applications/v1/reject_payin
Content-Type: application/json

{
  "order_id": "ORD-1001",
  "standart_sign": "<sha256_hex>"
}
200 OK response
HTTP/1.1 200 OK
{ "ok": true }

POST /set_client_status_payin

Forward to us what the customer told you about the payment — payment_confirmed if they say they paid, payment_rejected if they cancelled. We use this together with the bank receipt to finalise the order.

FieldTypeDescription
order_idstringRequiredorder id.
statusenumRequired"payment_confirmed" or "payment_rejected".
Request body
POST https://ssouk.org/applications/v1/set_client_status_payin
X-Api-Key: <private_key>
Content-Type: application/json

{
  "order_id": "ORD-1001",
  "status": "payment_confirmed"
}
200 OK response
HTTP/1.1 200 OK

{
  "ok": true,
  "order_id": "ORD-1001",
  "status_from_client": "payment_confirmed"
}

GET /status_payin/{order_id}

Poll the current state of a PayIn order at any time.

GET
GET https://ssouk.org/applications/v1/status_payin/ORD-1001
X-Api-Key: <private_key>
200 OK response
HTTP/1.1 200 OK

{
  "ok": true,
  "internal_transaction_id": "TRX-1001",
  "order_id": "ORD-1001",
  "type": "pay_in",
  "status": "successful",
  "fiat_amount": "15000.00",
  "usdt_amount": "158.7301",
  "merchant_spent_usdt": "160.1234",
  "fiat_currency": "RUB",
  "exchange_rate": "94.50",
  "payment_method": "card",
  "created_at": "2026-05-28T12:00:00Z",
  "updated_at": "2026-05-28T12:15:00Z",
  "number_card": "4276123456785678",
  "phone_number": null,
  "number_account": null,
  "iban_number": null,
  "full_name": "IVAN I.",
  "bank": "Сбербанк",
  "cheque_urls": null
}

POST /create_payout

Create a PayOut order. We hold the merchant balance, process the payout and send a final callback with the result. The exact field you must pass with the recipient details depends on payment_method — see the request body below.

FieldTypeDescription
order_idstringRequiredUnique per merchant.
payment_methodenumRequiredcard | sbp | account | iban.
fiat_amountstringRequiredDecimal amount as string.
fiat_currencystringRequiredISO-like code.
bankstringRequiredRecipient bank. Canonical name from the bank dictionary (e.g. "Т-Банк").
number_cardstringRequiredRequired when payment_method=card.
phone_numberstringRequiredRequired when payment_method=sbp. Any human formatting is accepted.
number_accountstringRequiredRequired when payment_method=account.
iban_numberstringRequiredRequired when payment_method=iban.
bikstringOptionalBank identifier code.
full_namestringOptionalRecipient full name.
signstringRequiredSHA256(order_id:public_key:private_key) hex.
success_callback_urlurlRequiredURL for success callback.
error_callback_urlurlRequiredURL for error callback.
timeoutint (мин)OptionalOrder timeout in minutes.
customerstringOptionalYour customer id.
order_descriptionstringOptionalFree-form description.
Request body
POST https://ssouk.org/applications/v1/create_payout
Content-Type: application/json

{
  "order_id": "WD-2002",
  "payment_method": "sbp",
  "fiat_amount": "5000.00",
  "fiat_currency": "RUB",
  "bank": "Т-Банк",
  "phone_number": "79001234567",
  "full_name": "IVAN IVANOV",
  "sign": "<sha256_hex>",
  "success_callback_url": "https://merchant.example/cb/success",
  "error_callback_url": "https://merchant.example/cb/error"
}
200 OK response
HTTP/1.1 200 OK

{
  "ok": true,
  "internal_transaction_id": "TRX-20260528-120100-B2C3D4",
  "fiat_amount": "5000.00",
  "fiat_currency": "RUB",
  "usdt_amount": "52.9101",
  "merchant_spent_usdt": "53.5670",
  "exchange_rate": "94.50",
  "payment_method": "sbp",
  "reject_callback_url": "https://merchant.example/cb/error"
}

POST /reject_payout

Cancel a PayOut while it is not yet successful and not yet rejected. We release the hold on your balance.

FieldTypeDescription
order_idstringRequiredYour original order id.
standart_signstringRequiredSHA256(order_id:public_key:private_key:callback) hex.
Request body
POST https://ssouk.org/applications/v1/reject_payout
Content-Type: application/json

{
  "order_id": "WD-2002",
  "standart_sign": "<sha256_hex>"
}
200 OK response
HTTP/1.1 200 OK
{ "ok": true }

POST /set_client_status_payout (optional)

Optional. Use this only if you want to record the recipient's reaction to the payout on your side. The order finalises with or without it.

FieldTypeDescription
order_idstringRequiredorder id.
statusenumRequired"payment_confirmed" or "payment_rejected".
Request body
POST https://ssouk.org/applications/v1/set_client_status_payout
X-Api-Key: <private_key>
Content-Type: application/json

{
  "order_id": "WD-2002",
  "status": "payment_confirmed"
}
200 OK response
HTTP/1.1 200 OK

{
  "ok": true,
  "order_id": "WD-2002",
  "status_from_client": "payment_confirmed"
}

GET /status_payout/{order_id}

Poll the current state of a PayOut order at any time.

GET
GET https://ssouk.org/applications/v1/status_payout/WD-2002
X-Api-Key: <private_key>
200 OK response
HTTP/1.1 200 OK

{
  "ok": true,
  "internal_transaction_id": "TRX-2002",
  "order_id": "WD-2002",
  "type": "pay_out",
  "status": "successful",
  "fiat_amount": "5000.00",
  "usdt_amount": "52.9101",
  "merchant_spent_usdt": "53.5670",
  "fiat_currency": "RUB",
  "exchange_rate": "94.50",
  "payment_method": "sbp",
  "created_at": "2026-05-28T12:01:00Z",
  "updated_at": "2026-05-28T12:05:00Z",
  "number_card": null,
  "phone_number": "79001234567",
  "number_account": null,
  "iban_number": null,
  "full_name": "IVAN IVANOV",
  "bank": "Т-Банк",
  "cheque_urls": [
    "https://ssouk.org/applications/v1/receipts/9b2c8b1e-1234-4bcd-8ef0-000000000001"
  ]
}

GET /get_balance_payin / GET /get_balance_payout

Read your current free + held balance per project. Useful for dashboards and pre-flight checks before creating a large PayOut.

GET
GET https://ssouk.org/applications/v1/get_balance_payin
X-Api-Key: <private_key>

GET https://ssouk.org/applications/v1/get_balance_payout
X-Api-Key: <private_key>
200 OK response
HTTP/1.1 200 OK

{
  "ok": true,
  "type": "pay_in",
  "balance": "10240.5500",
  "hold_balance": "320.0000",
  "available": "9920.5500",
  "currency": "USDT",
  "projects": [
    {
      "id": "9b2c8b...",
      "name": "RUB / card",
      "balance": "10240.5500",
      "hold_balance": "320.0000"
    }
  ]
}

Outgoing callbacks

We POST JSON to your success_callback_url or error_callback_url. Verify the body via standart_sign = SHA256(order_id:public_key:private_key:callback). Reply with HTTP 2xx — anything else is treated as a delivery failure and we retry with exponential backoff (up to 10 attempts). Every callback carries the full order snapshot: order_id, internal_transaction_id, type, status, fiat_amount/fiat_currency, usdt_amount, merchant_spent_usdt, exchange_rate, payment_method, created_at/updated_at, requisite fields (number_card, phone_number, number_account, iban_number), full_name, bank. PayOut also ships cheque_urls (legacy string array) and receipts (typed objects: id, url, file_name, content_type, size_bytes, uploaded_at, check_status, source). Amount edits. If the order amount was adjusted (via the admin action on your behalf), we start including four additional fields in every subsequent callback for this order: old_fiat_amount, new_fiat_amount, old_usdt_amount, new_usdt_amount. old_* is the very first amount the order had (never changes after subsequent edits), new_* mirrors the current fiat_amount / usdt_amount values in the same callback. Our processing fee (merchant_spent_usdt) is recomputed against the new amount and reflected in the same payload.

callback body
POST https://merchant.example/cb/success
Content-Type: application/json

{
  "order_id": "ORD-1001",
  "internal_transaction_id": "TRX-1001",
  "standart_sign": "<callback_sign>",
  "type": "pay_in",
  "status": "successful",
  "fiat_amount": "15000.00",
  "usdt_amount": "158.7301",
  "merchant_spent_usdt": "160.1234",
  "fiat_currency": "RUB",
  "exchange_rate": "94.50",
  "payment_method": "card",
  "number_card": "4276123456785678",
  "bank": "Сбербанк",
  "full_name": "IVAN I.",
  "created_at": "2026-05-28T12:00:00+00:00",
  "updated_at": "2026-05-28T12:15:00+00:00"
}
X-Body-Signature header

Every callback carries an X-Body-Signature HTTP header — HMAC-SHA256 of the request body with your private_key. The standart_sign field in the body signs only order_id; X-Body-Signature signs the whole JSON, including fiat_amount, status, receipts[]. Verification is optional but recommended.

Python
import hmac, hashlib, json

def verify(body_bytes: bytes, header_value: str, private_key: str) -> bool:
    payload = json.loads(body_bytes)
    canonical = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
    msg = b"cb|v1|" + canonical
    expected = hmac.new(private_key.encode(), msg, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, (header_value or "").lower())

Value is lowercase hex. Body canonicalisation: json.dumps(payload, sort_keys=True, separators=(",", ":")) prefixed with cb|v1|.

Disputes

When a PayIn looks paid on our side but the merchant reports the customer did not receive the money (or vice versa), we open a dispute. Funds stay held on your account until the dispute is resolved.

If your error_callback_url is configured we POST a callback with status: "dispute" so your back-office can react automatically. You may also resolve the dispute via our support team — write to support with the internal_transaction_id and any evidence.

When we resolve the dispute the held balance is either charged (status flips to successful) or released (status flips to cancelled); a final callback is sent in both cases.

dispute callback
POST https://merchant.example/cb/error
Content-Type: application/json

{
  "order_id": "ORD-1001",
  "internal_transaction_id": "TRX-1001",
  "standart_sign": "<callback_sign>",
  "type": "pay_in",
  "status": "dispute",
  "fiat_amount": "15000.00",
  "fiat_currency": "RUB",
  "payment_method": "card",
  "created_at": "2026-05-28T12:00:00+00:00",
  "updated_at": "2026-05-28T13:30:00+00:00"
}

POST /upload_receipt

After the customer paid you may upload the bank receipt: multipart /upload_receipt with X-Api-Key, fields order_id + file (PDF/PNG/JPG up to 1 MB).

multipart
POST https://ssouk.org/applications/v1/upload_receipt
X-Api-Key: <private_key>
Content-Type: multipart/form-data

order_id=ORD-1001
file=@receipt.pdf
200 OK response
HTTP/1.1 200 OK
{ "ok": true, "url": "https://...your-stored-receipt..." }

Bank mapping

The bank field is the canonical recipient bank name. Comparison is case- and whitespace-insensitive. If the bank is not recognised, the order is rejected with HTTP 400 {"ok": false, "message": "Incorrect bankname", "code": "incorrect_bankname"}.

Loading dictionary…

Sample response — unknown bank
HTTP/1.1 400 Bad Request
{
  "ok": false,
  "message": "Incorrect bankname",
  "code": "incorrect_bankname",
  "bank": "NotARealBank",
  "fiat_currency": "RUB"
}

Currency mapping

Currencies supported by the system and their display symbols. The fiat_currency field in your requests must match the «Code» column (case-insensitive). This table is synchronised with the «Currencies» section in the admin panel — updates there propagate here automatically.

Loading dictionary…

HTTP error reference

Each failure returns a single JSON in production. Below are the exact strings emitted by the backend (not localised): grep for these verbatim on your side.

HTTP
400 — {"ok":false,"message":"Access denied. Invalid method."}
400 — {"ok":false,"message":"Incorrect bankname","code":"incorrect_bankname","bank":"…","fiat_currency":"…"}
400 — {"ok":false,"message":"Cannot reject successful application"}
400 — {"ok":false,"message":"Application already rejected"}
400 — {"ok":false,"message":"Unsupported payment_method: <value>"}
400 — {"ok":false,"message":"X-Merchant-Pub header required"}
400 — {"ok":false,"message":"empty file"}
400 — {"ok":false,"message":"file_too_large"}
400 — {"ok":false,"message":"unsupported file type: <content-type>"}
400 — {"ok":false,"message":"content_does_not_match_type"}
401 — {"ok":false,"message":"Unauthorized. X-Api-Key required"}
401 — {"ok":false,"message":"Unauthorized. Invalid X-Api-Key"}
401 — {"ok":false,"message":"Unauthorized. Invalid signature."}
401 — {"ok":false,"message":"Unauthorized. X-Body-Signature required."}
401 — {"ok":false,"message":"Unauthorized. Body signature mismatch."}
401 — {"ok":false,"message":"Unauthorized. Cannot verify body."}
401 — {"ok":false,"message":"Unauthorized. Replay of signed request rejected."}
402 — {"ok":false,"code":"insufficient_balance","message":"Недостаточно средств на балансе. Пополните баланс и повторите запрос.","message_en":"Insufficient balance. Top up your project balance and retry.","required_usdt":"…"}
403 — {"ok":false,"message":"blocked_merchant"}
403 — {"ok":false,"message":"Access denied. IP address is not allowed."}
403 — {"ok":false,"message":"ip_blocked"}
403 — {"ok":false,"message":"not your receipt"}
404 — {"ok":false,"message":"application not found"}
404 — {"ok":false,"message":"merchant_not_found"}
404 — {"ok":false,"message":"receipt not found"}
409 — {"ok":false,"message":"Application with id [<order_id>] already in work"}
410 — {"ok":false,"message":"file no longer available"}
413 — {"ok":false,"message":"request_entity_too_large"}
422 — {"ok":false,"message":"Invalid request","errors":[{"type","loc","msg"},…]}
422 — {"ok":false,"message":"Invalid fiat_amount: '<value>'"}
429 — {"ok":false,"message":"rate_limited"}
429 — {"ok":false,"message":"rate_limited_per_merchant"}
503 — {"ok":false,"error":"overloading requisite"}
503 — {"ok":false,"message":"replay_store_unavailable"}

Application status values in callbacks

Lifecycle: pendingprocessingsuccessful. Negative paths: rejected_gate (project / amount mismatch), rejected_merchant (you cancelled), rejected_timeout (no requisites in time), cancelled (admin cancellation), failed (processing error), dispute (under review — see Disputes).

Sandbox

Pick an endpoint and a response scenario — we render the request body we would send and the JSON we would receive back.

Example request
{
  "order_id": "ORD-SANDBOX-1",
  "payment_method": "card",
  "fiat_amount": "1500.00",
  "fiat_currency": "RUB",
  "bank": "Сбербанк",
  "timeout": 900,
  "success_callback_url": "https://merchant.example/cb/success",
  "error_callback_url": "https://merchant.example/cb/error",
  "customer": "user-42",
  "sign": "<sha256_hex>"
}
Example response
Pick an endpoint + scenario above to see the example response.