ORGON API
Custodial wallets для крипто-обменников, банков и бирж. Один REST API — выпустить депозит-адреса своим клиентам, отслеживать поступления через webhooks, отправлять транзакции по подписи.
Не пишите HMAC сами — используйте SDK
npm i @orgon/sdk
import { OrgonClient } from "@orgon/sdk";
const orgon = new OrgonClient({ apiKey: process.env.ORGON_KEY!, secret: process.env.ORGON_SECRET! });
await orgon.users.create({ external_id: "u123", email: "[email protected]" });Первая транзакция — за 5 шагов
Выпустите API-ключ
Settings → API ключи → Выпустить. Сохраните secret — он показывается один раз.
Зарегистрируйте конечного пользователя
POST /v1/users с external_id (ваш id юзера) и email. Идемпотентно — можно вызывать на каждом логине.
Создайте депозит-кошелёк
POST /v1/wallets {network, end_user_id}. На email юзера придёт ссылка подтверждения от Safina — пользователь её кликает.
Настройте webhook
PUT /v1/webhooks/config {url, secret}. Мы будем POST'ить события wallet.deposit.detected, transaction.confirmed, и т.д.
Отправляйте транзакции
POST /v1/transactions {wallet_id, to_address, amount}. Подпись своим EC ключом → broadcast → Tronscan через ~30 сек.
HMAC-подпись каждого запроса
Получите пару key_pub/secret в Настройках → API ключи. Secret отображается один раз — сохраните его в свой vault. Дальше каждый запрос к /v1/* подписывайте четырьмя заголовками:
X-ORGON-Key: okl_a1b2c3d4... X-ORGON-Timestamp: 1715690000000 X-ORGON-Nonce: e3b0c442-98fc-1c14-9afb-f4c8996fb924 X-ORGON-Signature: 7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730 # Опционально на mutating вызовах — гарантирует, что повтор # при network-сбое не создаст дубль транзакции/кошелька: X-ORGON-Idempotency-Key: 8c1d4f0e-4af3-49e2-b8a3-1f0c0a9d1234
Подпись — hex(HMAC-SHA256(secret, message)), где message = конкатенация:
<timestamp>\n<nonce>\n<METHOD>\n<path>\n<raw body>
TypeScript
import crypto from "node:crypto";
import { randomUUID } from "node:crypto";
const KEY_PUB = process.env.ORGON_KEY!;
const SECRET = process.env.ORGON_SECRET!;
const BASE = "https://orgon.asystem.ai";
async function call(method: string, path: string, body?: unknown) {
const ts = Date.now().toString();
const nonce = randomUUID();
const raw = body ? JSON.stringify(body) : "";
const msg = `${ts}\n${nonce}\n${method}\n${path}\n${raw}`;
const sig = crypto.createHmac("sha256", SECRET).update(msg).digest("hex");
const res = await fetch(BASE + path, {
method,
headers: {
"Content-Type": "application/json",
"X-ORGON-Key": KEY_PUB,
"X-ORGON-Timestamp": ts,
"X-ORGON-Nonce": nonce,
"X-ORGON-Signature": sig,
},
body: raw || undefined,
});
if (!res.ok) throw new Error(`ORGON ${res.status}: ${await res.text()}`);
return res.json();
}
// Usage:
// const ping = await call("GET", "/v1/ping");
// const user = await call("POST", "/v1/users", { external_id: "u123", email: "[email protected]" });Python
import hmac, hashlib, json, time, uuid
import httpx
KEY_PUB = "okl_a1b2c3..."
SECRET = b"oksl_8e9f..."
BASE = "https://orgon.asystem.ai"
def call(method: str, path: str, body=None):
ts = str(int(time.time() * 1000))
nonce = str(uuid.uuid4())
raw = json.dumps(body, separators=(",", ":")) if body is not None else ""
msg = f"{ts}\n{nonce}\n{method}\n{path}\n{raw}".encode()
sig = hmac.new(SECRET, msg, hashlib.sha256).hexdigest()
headers = {
"Content-Type": "application/json",
"X-ORGON-Key": KEY_PUB,
"X-ORGON-Timestamp": ts,
"X-ORGON-Nonce": nonce,
"X-ORGON-Signature": sig,
}
r = httpx.request(method, BASE + path, headers=headers, content=raw or None)
r.raise_for_status()
return r.json()
# Usage:
# ping = call("GET", "/v1/ping")
# user = call("POST", "/v1/users", {"external_id": "u123", "email": "[email protected]"})Event-driven нотификации
Зарегистрируйте URL через PUT /v1/webhooks/config. Когда происходит событие — например, на адрес клиента приходит TRX или USDT — ORGON отправит POST на ваш endpoint с подписанным телом. Доставка надёжная: при ошибке мы повторяем с экспоненциальным backoff (30s → 2m → 10m → 1h → 6h, 6 попыток).
Safina выдала on-chain address для кошелька.
На адрес пришёл входящий on-chain transfer (TRX, USDT, …).
Исходящая транзакция отправлена в сеть, появился tx_hash.
Транзакция подтверждена сетью (нужное число блоков).
Транзакция отменена или отклонена сетью.
Эхо после POST /v1/users — для аудита у вас.
Проверка подписи (Node.js)
import crypto from "node:crypto";
import type { Request, Response } from "express";
// Same secret you set via PUT /v1/webhooks/config { secret }
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!;
export function orgonWebhook(req: Request, res: Response) {
const ts = req.header("X-ORGON-Webhook-Timestamp") ?? "";
const sig = req.header("X-ORGON-Webhook-Signature") ?? "";
const raw = (req as any).rawBody as Buffer; // capture raw body in middleware
// 5-min drift window — protects against replay of an old payload.
if (Math.abs(Date.now() - Number(ts)) > 5 * 60 * 1000) {
return res.status(401).end();
}
const expected = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(`${ts}\n`)
.update(raw)
.digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) {
return res.status(401).end();
}
const event = JSON.parse(raw.toString("utf8"));
switch (event.type) {
case "wallet.deposit.detected":
// credit user balance, mark order paid, etc.
break;
case "transaction.confirmed":
// mark payout completed
break;
}
res.json({ ok: true });
}Тестовый event без реального deposit
Чтобы убедиться что ваш endpoint валидно принимает события до прихода первого реального deposit — поставьте в очередь synthetic event:
# из вашей машины, через signed SDK-call или curl:
curl -X POST https://orgon.asystem.ai/v1/webhooks/test \
-H "X-ORGON-Key: $ORGON_KEY" \
-H "X-ORGON-Timestamp: $TS" \
-H "X-ORGON-Nonce: $NONCE" \
-H "X-ORGON-Signature: $SIG" \
-d '{"event_type":"webhook.test","payload":{"hello":"world"}}'
# → { "delivery_id": "...", "queued": true }Стандартный формат ответа
Любая 4xx/5xx-ошибка из /v1/* приходит в одном виде:
{
"error": "<machine-readable code>",
"message": "<human-readable description>",
"request_id": "92f3e4a7…",
"details": [ ... ] // только для 422 (валидация)
}В каждом ответе также есть заголовок X-Request-Id. Если что-то идёт не так — пришлите этот id в support, и мы найдём весь путь запроса в логах.
Подпись не прошла или ключ отозван / истёк.
Достигнут лимит вашего pricing-плана за день.
Sandbox-ключ обратился к mainnet-сети.
Тело запроса не прошло pydantic-валидацию. См. `details`.
Ресурс не существует или принадлежит другому merchant'у.
Идентификатор уже занят (например slug merchant'а).
Safina вернула ошибку при выполнении операции.
Непредвиденная ошибка на нашей стороне. Пришлите request_id.
Тестирование на testnet
При выпуске ключа поставьте галочку sandbox. Ключи в sandbox имеют префикс okt_* и работают только с testnet сетями (Tron Nile — 5010, BTC test — 1010, ETH Sepolia — 3010). Тестовые TRX можно получить через Nile faucet.
Что доступно
End-users
- POST /v1/users
- GET /v1/users/{id}
- GET /v1/users
- PATCH /v1/users/{id}
Wallets
- POST /v1/wallets
- GET /v1/wallets/{id}
- GET /v1/users/{id}/wallets
Transactions
- POST /v1/transactions
- POST /v1/transactions/{id}/sign
- GET /v1/transactions/{id}
- GET /v1/transactions
Deposits (incoming)
- GET /v1/wallets/{id}/deposits
- GET /v1/users/{id}/deposits
Webhooks
- GET /v1/webhooks/config
- PUT /v1/webhooks/config
- GET /v1/webhooks/deliveries
Meta
- GET /v1/health
- GET /v1/networks
- GET /v1/ping
Полная схема всех полей и кодов ошибок — Swagger UI.