S
docs.syncra.money
API ReferenceMerchant API

Быстрый старт

Проведите первый платёж за 5 минут — от получения ключей до приёма webhook

Быстрый старт

Этот гайд проведёт вас от регистрации до первого успешного платежа за 5 минут. Мы создадим PayIn, перенаправим игрока на платёжную страницу и примем webhook о завершении. Готовый код можно скопировать и запустить.

Если вы хотите сначала понять общую картину — начните с обзора API.


Что вам нужно

Перед началом интеграции убедитесь, что у вас есть:

РесурсОписаниеГде получить
API TokenПубличный 64-символьный hex-токен мерчанта. Передаётся в заголовке X-Merchant-Token.Администратор платформы Syncra выдаёт при заведении мерчанта (разовое показ).
Webhook SecretПриватный секретный ключ (32 байта) для вычисления HMAC-SHA256 подписей. Не передаётся по сети.Выдаётся вместе с API Token (разовое показ). Храните его в секрете.
Callback URLПубличный HTTPS-адрес вашего сервера для приёма вебхуков.Ваш сервер (например, https://merchant.example/callbacks/syncra).

Webhook Secret показывается один раз при выдаче или ротации ключей. Если вы его потеряли — попросите администратора выполнить ротацию. Подробности в разделе Аутентификация.


Шаг 1. Авторизация

Каждый запрос к Syncra API подписывается с помощью HMAC-SHA256. Подпись вычисляется по строке {timestamp}.{raw_body} и передаётся в заголовке X-Signature. Это защищает запрос от подделки и повторного воспроизведения.

Ниже — готовые примеры подписи на curl и Node.js. Полная спецификация — в Аутентификации.

const crypto = require('crypto');

/**
 * Строит три заголовка аутентификации для запроса к Syncra API.
 * @param {string} body   — сырое тело запроса (строка JSON, как уходит по сети).
 * @param {string} token  — X-Merchant-Token (64-char hex).
 * @param {string} secret — Webhook Secret (для HMAC-SHA256).
 */
function authHeaders(body, token, secret) {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const payload = `${timestamp}.${body}`;
  const signature = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  return {
    'X-Merchant-Token': token,
    'X-Timestamp': timestamp,
    'X-Signature': `t=${timestamp},v1=${signature}`,
    'Content-Type': 'application/json',
  };
}

module.exports = { authHeaders };
# В curl подпись удобнее считать через openssl. Скрипт ниже подписывает
# произвольное тело и отправляет GET-запрос баланса.
BODY='{"amount":150000,"currency":"RUB"}'
SECRET='whsec_your_webhook_secret_here'
TOKEN='mtoken_your_merchant_token_here'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')

curl -X GET https://api.syncra.money/api/v1/p2p/merchant/balance \
  -H "X-Merchant-Token: $TOKEN" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: t=$TS,v1=$SIG" \
  -H "Content-Type: application/json"

Сервер отклоняет запросы, у которых X-Timestamp расходится с серверным временем более чем на 5 минут. Убедитесь, что системные часы синхронизированы (NTP).


Шаг 2. Создание PayIn

Чтобы принять платёж от игрока, создайте сделку вызовом POST /deals/payin. Минимально достаточно пяти полей: сумма, валюта, идемпотентный ключ, идентификатор игрока и метод оплаты.

POST https://api.syncra.money/api/v1/p2p/merchant/deals/payin

Минимальный набор полей

ПолеТипОписание
amountint64Сумма в минимальных единицах валюты (копейках для RUB). 150000 = 1500.00 RUB.
currencystringISO 4217, например "RUB".
idempotency_keystringУникальный ключ запроса (UUID v4). Защищает от дублей при retry. Подробнее →
client_idstringИдентификатор игрока в вашей системе (например, player_9921).
payment_method_idstringОбязательное поле: UUID (канонический формат) или слаг метода оплаты из GET /payment-methods (Banks → Получение списка методов). Пустое значение отклоняется с 400; получите идентификатор из каталога до первого платежа.

Пример запроса (curl)

# 1. Сгенерируйте idempotency_key (UUID v4) — он должен быть уникален для каждого платежа.
IDEMPOTENCY_KEY=$(uuidgen | tr 'A-Z' 'a-z')

# 2. Тело запроса в точности так, как оно пойдёт по сети (без лишних пробелов).
BODY='{"amount":150000,"currency":"RUB","idempotency_key":"'"$IDEMPOTENCY_KEY"'","client_id":"player_9921","payment_method_id":"<UUID из шага 1>"}'

# 3. Рассчитайте HMAC-SHA256 подпись.
SECRET='whsec_your_webhook_secret_here'
TOKEN='mtoken_your_merchant_token_here'
TS=$(date +%s)
SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')

# 4. Отправьте запрос.
curl -X POST https://api.syncra.money/api/v1/p2p/merchant/deals/payin \
  -H "X-Merchant-Token: $TOKEN" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: t=$TS,v1=$SIG" \
  -H "Content-Type: application/json" \
  -d "$BODY"

Пример ответа (200 OK)

{
  "deal": {
    "deal_id": "7ac148fe-19a3-45bb-b992-019fac55b721",
    "status": "ESCROW_LOCKED",
    "amount": "150000",
    "currency": "RUB",
    "payment_url": "https://checkout.syncra.money/pay/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
    "created_at": "2026-08-03T12:00:00Z"
  }
}

Поле payment_url — это ссылка на hosted платёжную страницу. Именно на неё нужно отправить игрока.

В ответе V2 используется поле payment_url. В этом же объекте сделки (MerchantDealInfo, proto field 9) оно хранится под именем payment_url. Подробное описание страницы оплаты — в Hosted Checkout.


Шаг 3. Редирект игрока

Возьмите payment_url из ответа и перенаправьте туда игрока. Это hosted страница на домене checkout.syncra.money/pay/{hash} — её брендируют под вашего мерчанта (цвета, логотип, название). Вам не нужно рисовать форму оплаты самостоятельно.

// Браузерный редирект после создания PayIn на бэкенде.
const response = await fetch('/your-backend/create-payin', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ playerId: 'player_9921', amount: 150000 }),
});
const { payment_url } = await response.json();

// Отправляем игрока на платёжную страницу Syncra.
window.location = payment_url;
app.post('/api/deposit', async (req, res) => {
  const deal = await syncra.createPayIn({
    amount: req.body.amount,
    currency: 'RUB',
    idempotency_key: crypto.randomUUID(),
    client_id: req.user.id,
  });
  // Возвращаем URL фронту — пусть редиректит игрока.
  res.json({ payment_url: deal.payment_url });
});
<!-- Если хотите редиректнуть сразу с бэкенда -->
<meta http-equiv="refresh" content="0; url={{payment_url}}" />

Что увидит игрок на странице оплаты:

  1. Реквизиты трейдера — номер карты / телефона, ФИО держателя.
  2. Сумму и таймер — сколько перевести и сколько времени осталось.
  3. Пошаговые инструкции — как совершить перевод в приложении банка.
  4. Кнопку «Я оплатил» — после нажатия запускается проверка поступления.

Полный разбор экранов — в Hosted Checkout.


Шаг 4. Приём webhook

Когда платёж подтверждается, Syncra отправляет POST на ваш callback_url событие deal.completed. Ниже — минимальный сервер на Node.js, который проверяет подпись и зачисляет депозит.

// webhook.js — запуск: node webhook.js (нужен express: npm i express)
const crypto = require('crypto');
const express = require('express');

const WEBHOOK_SECRET = 'whsec_your_webhook_secret_here';
const app = express();

// ВАЖНО: подпись считается по СЫРОМУ телу. Поэтому перехватываем тело
// до JSON-парсинга и сохраняем как rawBody.
app.use('/callbacks/syncra', express.raw({ type: 'application/json' }));

app.post('/callbacks/syncra', (req, res) => {
  const rawBody = req.body.toString('utf8');            // сырое тело как строка
  const signatureHeader = req.get('X-Signature'); // t=...,v1=...

  // 1. Проверяем подпись V2.
  if (!verifySignature(rawBody, signatureHeader, WEBHOOK_SECRET)) {
    return res.status(401).json({ error: 'invalid signature' });
  }

  // 2. Парсим тело ТОЛЬКО после успешной проверки.
  const event = JSON.parse(rawBody);

  // 3. Обрабатываем событие. ВАЖНО: возвращаем 200 как можно быстрее —
  //    тяжёлую логику (зачисление депозита) выносим в фоновую очередь.
  if (event.event === 'deal.completed') {
    console.log(`Deposit confirmed: deal=${event.deal_id} amount=${event.amount}`);
    // creditPlayer(event.deal_id, event.amount); // ваша бизнес-логика
  }

  // 4. Возвращаем 200 — иначе Syncra будет ретраить webhook.
  res.status(200).json({ status: 'success' });
});

// Проверка HMAC-SHA256 по формату t={ts},v1={hex}
function verifySignature(rawBody, signatureHeader, secret) {
  if (!signatureHeader) return false;
  const tsPart = signatureHeader.split(',').find(p => p.startsWith('t='));
  const sigPart = signatureHeader.split(',').find(p => p.startsWith('v1='));
  if (!tsPart || !sigPart) return false;

  const timestamp = tsPart.substring(2);
  const provided = sigPart.substring(3);

  // Защита от replay: отклоняем запросы старше 5 минут.
  const age = Math.abs(Math.floor(Date.now() / 1000) - parseInt(timestamp, 10));
  if (age > 300) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex');

  // Сравнение защищено от timing-атак.
  return crypto.timingSafeEqual(
    Buffer.from(provided, 'utf8'),
    Buffer.from(expected, 'utf8'),
  );
}

app.listen(3000, () => console.log('Webhook server on :3000'));
Полезные поля webhook deal.completed
{
  "event": "deal.completed",
  "deal_id": "7ac148fe-19a3-45bb-b992-019fac55b721",
  "merchant_id": "8f870a31-b88d-4cb0-a882-628d08cbda88",
  "amount": 150000,
  "currency": "RUB",
  "status": "COMPLETED",
  "received_amount": 150000,
  "amount_match_status": "full",
  "timestamp": "2026-08-03T12:04:12Z"
}
ПолеЗачем нужно
deal_idИдентификатор сделки — по нему вы сопоставляете платёж с вашим заказом.
amount / received_amountСколько должно было прийти / сколько пришло фактически.
amount_match_statusfull / partial / overpaid — сверка сумм; переплата/недоплата отдельными статусами бывают только в числовых кодах V1 (14/15), в V2 — всегда через это поле.
statusСтатус сделки (для deal.completedCOMPLETED; полный справочник — Statuses).

Всегда делайте обработку идемпотентной. Syncra может прислать один и тот же webhook повторно (рассинхрон сети, retry). Проверяйте по deal_id, что уже зачислили этот платёж. Полные правила — в Callbacks.


Готово

Вы только что провели первый платёж. Схема целиком:

Загрузка диаграммы...

Что дальше

On this page