Silicon Souk — Merchant 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.
card | sbp | account | ibanRequest 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.
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()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.
| Field | Type | Description | |
|---|---|---|---|
| order_id | string | Required | Your order id, unique per merchant. |
| payment_method | enum | Required | card | sbp | account | iban. |
| fiat_amount | string | Required | Decimal amount as string, e.g. "15000.00". |
| fiat_currency | string | Required | ISO-like code, e.g. "RUB". |
| bank | string | Required | Recipient bank. Canonical name from the bank dictionary (e.g. "Сбербанк"). Case-insensitive; English aliases ("Sberbank") are rejected. |
| sign | string | Required | SHA256(order_id:public_key:private_key) hex. |
| success_callback_url | url | Required | URL we POST the final success callback to. |
| error_callback_url | url | Optional | URL for error/cancel callbacks. |
| timeout | int (мин) | Optional | Order timeout in minutes. |
| customer | string | Optional | Your customer id (free-form). |
| order_description | string | Optional | Free-form description. |
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"
}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>"
}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.
| Field | Type | Description | |
|---|---|---|---|
| order_id | string | Required | Your original order id. |
| standart_sign | string | Required | SHA256(order_id:public_key:private_key:callback) hex. |
POST https://ssouk.org/applications/v1/reject_payin
Content-Type: application/json
{
"order_id": "ORD-1001",
"standart_sign": "<sha256_hex>"
}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.
| Field | Type | Description | |
|---|---|---|---|
| order_id | string | Required | order id. |
| status | enum | Required | "payment_confirmed" or "payment_rejected". |
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"
}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 https://ssouk.org/applications/v1/status_payin/ORD-1001
X-Api-Key: <private_key>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.
| Field | Type | Description | |
|---|---|---|---|
| order_id | string | Required | Unique per merchant. |
| payment_method | enum | Required | card | sbp | account | iban. |
| fiat_amount | string | Required | Decimal amount as string. |
| fiat_currency | string | Required | ISO-like code. |
| bank | string | Required | Recipient bank. Canonical name from the bank dictionary (e.g. "Т-Банк"). |
| number_card | string | Required | Required when payment_method=card. |
| phone_number | string | Required | Required when payment_method=sbp. Any human formatting is accepted. |
| number_account | string | Required | Required when payment_method=account. |
| iban_number | string | Required | Required when payment_method=iban. |
| bik | string | Optional | Bank identifier code. |
| full_name | string | Optional | Recipient full name. |
| sign | string | Required | SHA256(order_id:public_key:private_key) hex. |
| success_callback_url | url | Required | URL for success callback. |
| error_callback_url | url | Required | URL for error callback. |
| timeout | int (мин) | Optional | Order timeout in minutes. |
| customer | string | Optional | Your customer id. |
| order_description | string | Optional | Free-form description. |
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"
}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.
| Field | Type | Description | |
|---|---|---|---|
| order_id | string | Required | Your original order id. |
| standart_sign | string | Required | SHA256(order_id:public_key:private_key:callback) hex. |
POST https://ssouk.org/applications/v1/reject_payout
Content-Type: application/json
{
"order_id": "WD-2002",
"standart_sign": "<sha256_hex>"
}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.
| Field | Type | Description | |
|---|---|---|---|
| order_id | string | Required | order id. |
| status | enum | Required | "payment_confirmed" or "payment_rejected". |
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"
}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 https://ssouk.org/applications/v1/status_payout/WD-2002
X-Api-Key: <private_key>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 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>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.
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"
}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.
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.
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).
POST https://ssouk.org/applications/v1/upload_receipt
X-Api-Key: <private_key>
Content-Type: multipart/form-data
order_id=ORD-1001
file=@receipt.pdfHTTP/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…
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.
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: pending → processing → successful. 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.
{
"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>"
}Pick an endpoint + scenario above to see the example response.