Payout API
Надсилайте виплати з мерчант-балансу на будь-яку блокчейн-адресу.
Payout API дозволяє програмно виводити кошти з вашого мерчант-балансу на будь-яку блокчейн-адресу.
Для всіх endpoints Payout ви маєте використовувати окремий Payout API key для генерації підпису sign. Цей ключ відрізняється від звичайного API key і має бути згенерований у налаштуваннях вашого проєкту.
Створити виплату
Створює запит на виплату з вашого мерчант-балансу.
/v1/payoutПараметри запиту
| Поле | Тип | Обов'язкове | Опис |
|---|---|---|---|
currency | string | так | Валюта виплати (див. References) |
network | string | так | Код мережі (див. References) |
amount | string | так | Сума виплати |
to_address | string | так | Блокчейн-адреса отримувача |
order_id | string | ні | Ключ ідемпотентності — унікальний у межах проєкту. Повторний POST із тим самим order_id не створює нову виплату — натомість повертається існуюча |
url_callback | string | ні | URL для webhooks виплат. Не вказуйте, щоб вимкнути webhooks для цієї виплати |
memo | string | null | ні | Destination tag / memo. Зараз використовується лише мережами TON та SOL; макс. 255 символів |
from_currency | string | ні | Вихідний баланс, з якого списати кошти та автоматично конвертувати у currency у момент виплати. Дозволяє виплачувати у волатильних активах (BTC, ETH, …), тримаючи баланс у стейблкоїні як USDT — вам не потрібно самим тримати волатильну криптовалюту. Передайте "USDT", щоб списати з USDT-балансу |
fee_option | string | ні | Як стягуються комісії. deduct (за замовчуванням) — мережева та платформна комісії віднімаються від amount, отримувач отримує amount - fees. add — комісії додаються зверху, з мерчанта списується amount + fees, отримувач отримує рівно amount |
Ідемпотентність. У межах проєкту виплата є унікальною за order_id. Повторне надсилання того самого POST із тим самим order_id є безпечним — API повертає існуючу виплату замість створення дубліката. У продакшні завжди передавайте order_id для виплат.
Приклади запитів
curl -X POST https://api.2328.io/api/v1/payout \
-H "Content-Type: application/json" \
-H "User-Agent: MyShop/1.0 (+https://myshop.example)" \
-H "project: YOUR_PROJECT_UUID" \
-H "sign: YOUR_HMAC_SIGNATURE" \
-d '{"currency":"TRX","network":"TRX-TRC20","amount":"1.00","to_address":"TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t","order_id":"9ed25264-8be4-439f-acf5-2a8732538d27","url_callback":"https://your-site.com/webhook/payout","memo":null,"fee_option":"deduct"}'<?php
function apiSign(string $body, string $apiKey): string {
return hash_hmac('sha256', base64_encode($body), $apiKey);
}
$project = 'YOUR_PROJECT_UUID';
$apiKey = 'YOUR_PAYOUT_API_KEY';
$data = [
'currency' => 'TRX',
'network' => 'TRX-TRC20',
'amount' => '1.00',
'to_address' => 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t',
'order_id' => '9ed25264-8be4-439f-acf5-2a8732538d27',
'url_callback' => 'https://your-site.com/webhook/payout',
'memo' => null,
'fee_option' => 'deduct',
];
$body = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$sign = apiSign($body, $apiKey);
$ch = curl_init('https://api.2328.io/api/v1/payout');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'User-Agent: MyShop/1.0 (+https://myshop.example)',
"project: $project",
"sign: $sign",
],
]);
$response = json_decode(curl_exec($ch), true);import { createHmac } from "crypto";
function apiSign(body, apiKey) {
const base64 = Buffer.from(body, "utf8").toString("base64");
return createHmac("sha256", apiKey).update(base64).digest("hex");
}
const PROJECT_UUID = "YOUR_PROJECT_UUID";
const PAYOUT_API_KEY = process.env.PAYOUT_API_KEY;
const data = {
currency: "TRX",
network: "TRX-TRC20",
amount: "1.00",
to_address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
order_id: "9ed25264-8be4-439f-acf5-2a8732538d27",
url_callback: "https://your-site.com/webhook/payout",
memo: null,
fee_option: "deduct",
};
const body = JSON.stringify(data);
const sign = apiSign(body, PAYOUT_API_KEY);
const res = await fetch("https://api.2328.io/api/v1/payout", {
method: "POST",
headers: {
"Content-Type": "application/json",
"User-Agent": "MyShop/1.0 (+https://myshop.example)",
project: PROJECT_UUID,
sign,
},
body,
});
const json = await res.json();import json
import hmac
import hashlib
import base64
import httpx
def api_sign(body: str, api_key: str) -> str:
b64 = base64.b64encode(body.encode("utf-8")).decode()
return hmac.new(api_key.encode(), b64.encode(), hashlib.sha256).hexdigest()
PROJECT_UUID = "YOUR_PROJECT_UUID"
PAYOUT_API_KEY = "YOUR_PAYOUT_API_KEY"
data = {
"currency": "TRX",
"network": "TRX-TRC20",
"amount": "1.00",
"to_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"order_id": "9ed25264-8be4-439f-acf5-2a8732538d27",
"url_callback": "https://your-site.com/webhook/payout",
"memo": None,
"fee_option": "deduct",
}
body = json.dumps(data, separators=(",", ":"), ensure_ascii=False)
sign = api_sign(body, PAYOUT_API_KEY)
r = httpx.post(
"https://api.2328.io/api/v1/payout",
headers={
"Content-Type": "application/json",
"User-Agent": "MyShop/1.0 (+https://myshop.example)",
"project": PROJECT_UUID,
"sign": sign,
},
content=body.encode("utf-8"),
)
response = r.json()package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"net/http"
)
func apiSign(body []byte, apiKey string) string {
b64 := base64.StdEncoding.EncodeToString(body)
h := hmac.New(sha256.New, []byte(apiKey))
h.Write([]byte(b64))
return hex.EncodeToString(h.Sum(nil))
}
func marshalCanonical(v any) ([]byte, error) {
var buf bytes.Buffer
enc := json.NewEncoder(&buf)
enc.SetEscapeHTML(false)
if err := enc.Encode(v); err != nil {
return nil, err
}
return bytes.TrimRight(buf.Bytes(), "\n"), nil
}
type CreatePayout struct {
Currency string `json:"currency"`
Network string `json:"network"`
Amount string `json:"amount"`
ToAddress string `json:"to_address"`
OrderID string `json:"order_id"`
URLCallback string `json:"url_callback"`
Memo *string `json:"memo"`
FeeOption string `json:"fee_option"`
}
func main() {
const projectUUID = "YOUR_PROJECT_UUID"
const payoutAPIKey = "YOUR_PAYOUT_API_KEY"
data := CreatePayout{
Currency: "TRX",
Network: "TRX-TRC20",
Amount: "1.00",
ToAddress: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
OrderID: "9ed25264-8be4-439f-acf5-2a8732538d27",
URLCallback: "https://your-site.com/webhook/payout",
Memo: nil,
FeeOption: "deduct",
}
body, err := marshalCanonical(data)
if err != nil {
panic(err)
}
sign := apiSign(body, payoutAPIKey)
req, _ := http.NewRequest("POST",
"https://api.2328.io/api/v1/payout",
bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "MyShop/1.0 (+https://myshop.example)")
req.Header.Set("project", projectUUID)
req.Header.Set("sign", sign)
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
}Приклад відповіді
{
"state": 0,
"result": {
"uuid": "019dea62-1727-72aa-ac2c-eaf2ade193ef",
"order_id": "9ed25264-8be4-439f-acf5-2a8732538d27",
"status": "pending",
"currency": "TRX",
"network": "TRX-TRC20",
"amount": "1.00",
"merchant_amount": "1",
"network_amount": "0.89",
"amount_usd": "0.33",
"to_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
"memo": null,
"txid": null,
"block_number": null,
"error_type": null,
"created_at": "2026-05-02T23:29:50+03:00",
"updated_at": "2026-05-02T23:29:50+03:00"
}
}Комісії. За замовчуванням fee_option: deduct — мережева та платформна комісії віднімаються від amount (отримувач отримує amount - fees). Передайте fee_option: add, щоб стягувати комісії зверху — отримувач отримує рівно amount, а з мерчанта списується amount + fees.
Розрахувати виплату
Оцінює суми та комісії виплати без створення виплати і без списання з балансу. Використовуйте, щоб показати користувачу точну суму, яку він отримає (або заплатить), до підтвердження.
/v1/payout/calcПараметри запиту
Ідентичні до Створити виплату — ті самі поля, той самий підпис. Поля order_id, url_callback, to_address та memo приймаються, але ігноруються: жодна виплата не зберігається і жоден callback не надсилається.
Приклад запиту
curl -X POST https://api.2328.io/api/v1/payout/calc \
-H "Content-Type: application/json" \
-H "User-Agent: MyShop/1.0 (+https://myshop.example)" \
-H "project: YOUR_PROJECT_UUID" \
-H "sign: YOUR_HMAC_SIGNATURE" \
-d '{"currency":"USDT","network":"TRX-TRC20","amount":"100","fee_option":"add"}'Приклад відповіді
{
"state": 0,
"result": {
"currency": "USDT",
"network": "TRX-TRC20",
"amount": "100",
"fee_option": "add",
"merchant_amount": "103.00000000",
"network_amount": "100",
"total_fee": "3.00000000",
"total_fee_usd": "3.00000000"
}
}Лише попередній перегляд. Цей ендпоінт лише для читання — баланс не списується і запис виплати не створюється. Викликайте стільки разів, скільки потрібно, щоб показати розкладку комісій у вашому UI.
Статус виплати
Отримати статус запиту на виплату.
/v1/payout/status/{uuid}Параметри шляху
| Поле | Тип | Обов'язкове | Опис |
|---|---|---|---|
uuid | string | так | UUID виплати (з result.uuid під час створення) |
Приклад відповіді
{
"state": 0,
"result": {
"uuid": "019dff1f-0dbd-7277-8d45-271e7775388f",
"order_id": "4dfdcc84402b1185b71cbe399321533e",
"status": "completed",
"currency": "TRX",
"network": "TRX-TRC20",
"amount": "3.00",
"merchant_amount": "3.00",
"network_amount": "3.00",
"amount_usd": "1.04",
"to_address": "THauRv5tcucQRohXg8NiyGTk16DX1XQG5x",
"memo": null,
"txid": "9242e533703704ef3eaba840f70b4a26333e72c943377ee375fea17badb53def",
"block_number": null,
"error_type": null,
"created_at": "2026-05-07T00:08:38+03:00",
"updated_at": "2026-05-07T00:08:54+03:00",
"from_currency": "USDT",
"debited_amount": "1.050735",
"debited_currency": "USDT"
}
}Для цього GET-запиту підпис обчислюється з порожнього тіла:
hash_hmac('sha256', base64_encode(''), $apiKey)
Поля відповіді
Поля, що повертаються у result від POST /v1/payout та GET /v1/payout/status/{uuid}:
| Поле | Тип | Опис |
|---|---|---|
uuid | string | UUID виплати, призначений системою |
order_id | string | Ваш внутрішній ідентифікатор виплати (унікальний у межах проєкту) |
status | string | Поточний статус виплати (див. нижче) |
currency | string | Валюта виплати |
network | string | Код мережі |
amount | string | Сума виплати, як її було запитано |
merchant_amount | string | Сума, списана з мерчант-балансу |
network_amount | string | Сума, фактично надіслана у блокчейн (після мережевих та платформних комісій) |
amount_usd | string | Еквівалент суми виплати у USD |
to_address | string | Блокчейн-адреса отримувача |
memo | string | null | Destination tag / memo (TON, SOL). null в інших випадках |
txid | string | null | Хеш блокчейн-транзакції. null, доки транзакцію не надіслано |
block_number | int | null | Номер блока, у якому транзакцію було включено. null, доки не включено |
error_type | string | null | Причина невдачі, коли status = failed (див. Error types нижче). null в інших випадках |
created_at | string (ISO 8601) | Час створення виплати |
updated_at | string (ISO 8601) | Час останньої зміни статусу |
from_currency | string | null | Вихідний баланс, з якого було списано виплату при використанні автоконвертації (наприклад, USDT для виплати у BTC). null, якщо конвертація не використовувалась |
debited_amount | string | null | Сума, фактично списана з вихідного балансу після конвертації. Присутнє лише за використання автоконвертації |
debited_currency | string | null | Валюта debited_amount — баланс, з якого було списано кошти |
Статуси виплат
Поле status може приймати такі значення:
| Статус | Опис |
|---|---|
pending | Створено, очікує обробки |
completed | Успішно завершено — txid встановлено |
failed | Помилка надсилання — див. error_type |
cancelled | Скасовано |
Типи помилок
Коли status = failed, поле error_type описує причину:
| Код | Опис |
|---|---|
aml_risk | Виплату заблоковано перевірками AML-ризиків (адресу отримувача позначено як високоризикову) |
Webhook-сповіщення
Коли статус виплати змінюється, система надсилає POST webhook на URL url_callback, переданий під час створення виплати. Якщо url_callback не було надано, для цієї виплати webhooks не надсилаються.
- Метод:
POST - Content-Type:
application/json - Підпис: поле
signу тілі запиту, обчислене за допомогою Payout API key (того самого ключа, що використовується для підпису запитів виплат).
Payload віддзеркалює об'єкт result із GET /v1/payout/status/{uuid} плюс поле sign для перевірки.
Payload
{
"uuid": "019dff1f-0dbd-7277-8d45-271e7775388f",
"order_id": "4dfdcc84402b1185b71cbe399321533e",
"status": "completed",
"currency": "TRX",
"network": "TRX-TRC20",
"amount": "3.00",
"merchant_amount": "3.00",
"network_amount": "3.00",
"amount_usd": "1.04",
"to_address": "THauRv5tcucQRohXg8NiyGTk16DX1XQG5x",
"memo": null,
"txid": "9242e533703704ef3eaba840f70b4a26333e72c943377ee375fea17badb53def",
"block_number": null,
"error_type": null,
"created_at": "2026-05-07T00:08:38+03:00",
"updated_at": "2026-05-07T00:08:54+03:00",
"from_currency": "USDT",
"debited_amount": "1.050735",
"debited_currency": "USDT",
"sign": "925ad7bf3d6841864101f7cc2c7e30652e70a06cdb04dbe07a0129480000ce4a"
}Перевірка підпису. Використовуйте той самий алгоритм, що й для webhooks платежів, але підписуйте за допомогою свого Payout API key замість звичайного API key. Видаліть поле sign, закодуйте решту payload у JSON, потім у base64, після чого обчисліть hash_hmac('sha256', $base64, $payoutApiKey) та порівняйте з отриманим sign.