Authentication

All API requests require HMAC-SHA256 authentication via HTTP headers. Get your credentials in the merchant dashboard → Stores.

Required Headers

HeaderDescription
X-Api-KeyYour store API key
X-TimestampCurrent Unix timestamp (±5 min window)
X-SignatureHMAC-SHA256 of timestamp + method + path + body signed with API Secret

Signature Example

payload = "1700000000" + "POST" + "/api/v1/payment/create" + '{"amount":"100"}'
signature = HMAC-SHA256(payload, api_secret)
Each store has its own API key and secret. One merchant account can have multiple stores.

Create Payment

POST/api/v1/payment/createAuth Required

Create a new payment. Supports both direct crypto amounts and fiat→crypto conversion.

Request Body (JSON)

ParameterTypeDescription
amount*stringCrypto amount (e.g. "100.00" USDT or "0.005" BTC)
order_idstringYour internal order ID
fiat_amountstringAmount in fiat (overrides amount)
fiat_currencystringFiat currency: USD, EUR, RUB, CNY, GBP
metadataobjectArbitrary JSON (returned in webhook)
The accepted currency is configured per store in the Dashboard (Settings → Stores). If a store accepts multiple currencies, the API returns a payment_url with a currency selection page. If only one currency is enabled, the payment is created immediately.
Use fiat_amount + fiat_currency to auto-convert from fiat. The system fetches the current exchange rate and calculates the crypto amount.

Response — Single currency (direct payment)

{
  "success": true,
  "data": {
    "payment_id": "a1b2c3d4-...",
    "amount": "100.14",
    "currency": "USDT_TRC20",
    "pay_address": "TXyz...abc",
    "status": "pending",
    "expires_at": "2025-01-15T12:30:00Z",
    "payment_url": "https://soko.ai/pay/a1b2c3d4-..."
  }
}

Response — Multiple currencies (selection required)

{
  "success": true,
  "data": {
    "payment_id": null,
    "status": "selecting",
    "amount": 100,
    "currencies": ["USDT_TRC20", "BTC", "ETH"],
    "payment_url": "https://soko.ai/pay/select?token=..."
  }
}
Important: Always redirect the user to payment_url regardless of the response type. When payment_id is null, the user will choose a currency first, then the payment is created automatically.

Payment Status

GET/api/v1/payment/{payment_id}Auth Required

Response

{
  "success": true,
  "data": {
    "payment_id": "a1b2c3d4-...",
    "order_id": "ORDER-123",
    "status": "completed",
    "amount_requested": 100.14,
    "amount_with_cents": 100.14,
    "amount_received": 100.14,
    "currency": "USDT_TRC20",
    "pay_address": "TXyz...abc",
    "tx_hash": "abc123...",
    "confirmations": 21,
    "confirmed_at": "2025-01-15T12:25:00Z",
    "expires_at": "2025-01-15T12:30:00Z",
    "created_at": "2025-01-15T12:00:00Z"
  }
}

Payment Statuses

StatusDescription
selectingWaiting for customer to choose a currency (multiple currencies enabled)
pendingWaiting for payment. Show QR / address to customer
queuedAll USDT cent slots occupied. Will become pending soon
confirmingTransaction detected, waiting for blockchain confirmations
completedPayment confirmed. Safe to deliver goods
expiredNo payment received before timeout (default 30 min)
overpaidReceived more than expected. Treated as completed
underpaidReceived less than expected

List Payments

GET/api/v1/paymentsAuth Required

Paginated list of payments. Filter by status, currency, from/to dates.

Query Parameters

ParameterTypeDescription
pageoptionalintegerPage number (default: 1)
per_pageoptionalintegerResults per page, 1-100 (default: 20)
statusoptionalstringFilter by status
currencyoptionalstringFilter by currency

Merchant Balance

GET/api/v1/merchant/balanceAuth Required
{
  "success": true,
  "data": {
    "USDT_TRC20": {
      "available": 1250.50,
      "pending": 100.00,
      "total_received": 5000.00,
      "total_withdrawn": 3750.00,
      "total_commission": 75.00
    },
    "BTC": {
      "available": 0.05000000,
      "pending": 0.00100000,
      "total_received": 0.10000000,
      "total_withdrawn": 0.04900000,
      "total_commission": 0.00150000
    },
    "ETH": {
      "available": 1.25000000,
      "pending": 0.10000000,
      "total_received": 2.00000000,
      "total_withdrawn": 0.65000000,
      "total_commission": 0.03000000
    }
  }
}

Exchange Rates Public

GET/api/v1/rates

No authentication required. Cached for 5 minutes.

Conversion

GET /api/v1/rates?amount=5000&fiat=RUB&crypto=USDT_TRC20

{
  "success": true,
  "data": {
    "rates": {
      "btc": { "usd": 97500, "eur": 91200, "rub": 8775000 },
      "eth": { "usd": 3200, "eur": 2990, "rub": 288000 },
      "usdt": { "usd": 1.0001, "eur": 0.935, "rub": 90.5 }
    },
    "converted": 55.24
  }
}

Webhooks

When a payment status changes, we send a POST request to your webhook URL with the payment details.

Webhook Headers

HeaderDescription
X-SignatureHMAC-SHA256 of timestamp + body signed with your Webhook Secret
X-TimestampUnix timestamp of the webhook

Webhook Body

{
  "event": "payment.completed",
  "payment_id": "a1b2c3d4-...",
  "order_id": "ORDER-123",
  "amount": 100.14,
  "amount_received": 100.14,
  "currency": "USDT_TRC20",
  "tx_hash": "abc123...",
  "confirmed_at": "2025-01-15T12:25:00Z",
  "status": "completed",
  "metadata": { "user_id": 42 }
}

Events

payment.completedPayment confirmed (safe to deliver)
payment.confirmingTransaction detected, waiting for confirmations
payment.expiredPayment expired without receiving funds
payment.underpaidReceived less than expected
Always verify the webhook signature before processing. Return HTTP 200 to acknowledge. We retry up to 5 times with exponential backoff (1m, 5m, 30m, 2h, 12h).

Signature Verification (PHP)

$timestamp = $_SERVER['HTTP_X_TIMESTAMP'];
$signature = $_SERVER['HTTP_X_SIGNATURE'];
$body = file_get_contents('php://input');

$expected = hash_hmac('sha256', $timestamp . $body, $webhookSecret);

if (!hash_equals($expected, $signature)) {
    http_response_code(401);
    die('Invalid signature');
}

$payload = json_decode($body, true);
// Process payment...

JavaScript Widget

Embed a payment widget on your website. No backend integration required — just HTML + JS.

Quick Start

<script src="https://soko.ai/widget.js"></script>
<script>
CryptoMerchant.init({ apiKey: 'YOUR_STORE_API_KEY' });

document.getElementById('pay-btn').addEventListener('click', function() {
    CryptoMerchant.pay({
        amount: 100.00,
        order_id: 'ORDER-123',

        // Or use fiat conversion:
        // fiat_amount: 5000,
        // fiat_currency: 'RUB',

        onSuccess: function(payment) {
            alert('Payment confirmed! TX: ' + payment.tx_hash);
        },
        onClose: function() {
            console.log('Widget closed');
        },
        onError: function(err) {
            console.error('Payment error:', err);
        }
    });
});
</script>

<button id="pay-btn">Pay with Crypto</button>
The widget opens an iframe overlay. The payment is created server-side so your API key is never exposed to customers.

Accept crypto donations on any website. No API signatures required — just your Store API key.

How It Works

Enable donations in your Store settings, then add the widget to your website. Visitors can donate using USDT, BTC, or ETH. Each donation creates a tracked payment — you see all donations in your Dashboard under ❤️ Donations.

Quick Start — Button

Adds a "Donate" button where the script is placed:

<script src="https://soko.ai/donate-widget.js"
        data-store-key="YOUR_STORE_API_KEY"></script>

Top Bar

Shows a fixed donation bar at the top of the page:

<script src="https://soko.ai/donate-widget.js"
        data-store-key="YOUR_STORE_API_KEY"
        data-mode="topbar"></script>

Inline

Renders the button inside a specific element:

<div id="crypto-donate"></div>
<script src="https://soko.ai/donate-widget.js"
        data-store-key="YOUR_STORE_API_KEY"
        data-mode="inline"
        data-target="crypto-donate"></script>

Customization Attributes

ParameterDescription
data-store-keyStore API Key required
data-modebutton (default), topbar, or inline
data-targetElement ID for inline mode optional
data-textCustom text (overrides store settings) optional
data-amountsPreset amounts, comma-separated: "5,10,25" optional
data-colorAccent color, e.g. "#e11d48" optional

Direct Donation Page

You can also link directly to the donation page without a widget:

https://soko.ai/donate/YOUR_STORE_API_KEY

API: Create Donation

POST/donate/create Public

Create a donation payment programmatically. No signature required.

ParameterDescription
api_keyStore API Key required
fiat_amountAmount in fiat (e.g. 10) required*
fiat_currencyFiat currency code (e.g. USD) optional, default: USD
amountDirect crypto amount (alternative to fiat) optional
currencyUSDT_TRC20, BTC, or ETH required
donor_nameDonor's name optional
donor_messageDonor's message (max 500 chars) optional
Donations don't trigger webhooks. Monitor donations in your Dashboard → ❤️ Donations section.

Error Handling

{
  "success": false,
  "error": "Description of what went wrong"
}
HTTP CodeMeaning
200Success
400Bad request (invalid params)
401Missing or expired auth headers
403Invalid signature or suspended account
404Resource not found
500Server error

Code Examples

PHP
Python
Node.js
cURL
<?php
$apiKey = 'your_api_key';
$apiSecret = 'your_api_secret';

$body = json_encode([
    'amount' => '100.00',
    'order_id' => 'ORDER-' . time(),
]);

$timestamp = (string) time();
$method = 'POST';
$path = '/api/v1/payment/create';

$signature = hash_hmac('sha256', $timestamp . $method . $path . $body, $apiSecret);

$ch = curl_init('https://soko.ai' . $path);
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $body,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'X-Api-Key: ' . $apiKey,
        'X-Timestamp: ' . $timestamp,
        'X-Signature: ' . $signature,
    ],
]);

$response = json_decode(curl_exec($ch), true);
echo $response['data']['payment_url'];
import hmac, hashlib, time, json, requests

API_KEY = 'your_api_key'
API_SECRET = 'your_api_secret'
BASE_URL = 'https://soko.ai'

body = json.dumps({
    'amount': '100.00',
    'order_id': f'ORDER-{int(time.time())}'
})

timestamp = str(int(time.time()))
method = 'POST'
path = '/api/v1/payment/create'

payload = f'{timestamp}{method}{path}{body}'
signature = hmac.new(API_SECRET.encode(), payload.encode(), hashlib.sha256).hexdigest()

r = requests.post(f'{BASE_URL}{path}', data=body, headers={
    'Content-Type': 'application/json',
    'X-Api-Key': API_KEY,
    'X-Timestamp': timestamp,
    'X-Signature': signature,
})

print(r.json()['data']['payment_url'])
const crypto = require('crypto');
const https = require('https');

const API_KEY = 'your_api_key';
const API_SECRET = 'your_api_secret';

const body = JSON.stringify({
    amount: '100.00',
    order_id: `ORDER-${Date.now()}`
});

const timestamp = Math.floor(Date.now() / 1000).toString();
const method = 'POST';
const path = '/api/v1/payment/create';

const signature = crypto
    .createHmac('sha256', API_SECRET)
    .update(timestamp + method + path + body)
    .digest('hex');

fetch('https://soko.ai' + path, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-Api-Key': API_KEY,
        'X-Timestamp': timestamp,
        'X-Signature': signature,
    },
    body
})
.then(r => r.json())
.then(data => console.log(data.data.payment_url));
# Generate signature first (bash):
TIMESTAMP=$(date +%s)
BODY='{"amount":"100.00","order_id":"ORDER-1"}'
PAYLOAD="${TIMESTAMP}POST/api/v1/payment/create${BODY}"
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "your_api_secret" | cut -d' ' -f2)

curl -X POST https://soko.ai/api/v1/payment/create \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: your_api_key" \
  -H "X-Timestamp: $TIMESTAMP" \
  -H "X-Signature: $SIGNATURE" \
  -d "$BODY"
English Русский 中文 Español Deutsch Français Português 日本語