Обзор
Vito API позволяет вашему приложению работать с балансом Vito пользователя прямо внутри Discord — читать его, списывать с него или зачислять обратно. Пользователь подтверждает каждое списание своим PIN-кодом на vetox.io, прежде чем какие-либо Vito будут перемещены, а ваше приложение уведомляется о результате. Vito — это виртуальная валюта внутри экосистемы Vetox; используйте API, чтобы создавать опыт на основе Vito — разблокировку контента, функций, преимуществ или вознаграждений. Никакой покупки или продажи за реальные деньги не происходит.
Это REST API с аутентификацией по секретному ключу. Все эндпоинты возвращают JSON и версионируются под /v1.
https://api.vetox.io/public/vito/v1Балансы и расчёты
Ваш API-ключ привязан к вашему аккаунту Vetox. Каждое списание зачисляется в вашу пользу: когда ваше приложение списывает Vito у пользователя, эти Vito зачисляются на ваш собственный баланс (за вычетом комиссии платформы) и никогда не сгорают. Когда ваше приложение начисляет Vito пользователю, это финансируется с вашего баланса.
Vito всегда перемещается между балансами — никогда из реальных денег или в них. С пользователя взимается полная запрошенная вами сумма; вы получаете сумму за вычетом комиссии платформы; зачисления и вознаграждения финансируются с вашего баланса через /add.
Требования
Доступ к Vito API предоставляется для каждого проекта. Прежде чем вызывать его, вам нужно:
- Активное членство пользователя (уровень Silver или выше). API автоматически замораживается, если членство владельца истекает, и восстанавливается при повторной подписке.
- Одобренная заявка разработчика. Подайте её с этой страницы; команда Vetox проверяет её вручную.
- Приняты условия для разработчиков API — подтверждаются при отправке заявки разработчика.
- Области доступа, которые нужны вашему проекту. Их предоставляет команда Vetox на основе вашей заявки.
Доступные области
balance:readdeduct:createcredit:createtransfer:createtransactions:readАутентификация
Аутентифицируйте каждый запрос секретным ключом API в заголовке Authorization как токен Bearer. Ключи имеют префикс vito_live_.
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxxДва необязательных уровня дополнительно защищают ваш проект:
- Список разрешённых IP — ограничьте запросы конкретными IP сервера (настройте на вкладке Настройки).
- Лимиты запросов — потолки запросов на проект, которые растут вместе с уровнем членства.
Ключи API — хранение, ротация и безопасность
Ваш ключ создаётся при одобрении заявки. Он показывается только один раз — раскройте его на вкладке Ключи API и сразу сохраните.
Vetox никогда не хранит ваш ключ в читаемом виде (только солёный хеш). Храните его как секрет на стороне сервера — переменную окружения или менеджер секретов — никогда в клиентском коде, git-репозитории или браузере.
Ротируйте его на вкладке Ключи API. Прежний ключ работает ещё 24-часовой льготный период, чтобы вы развернули новый без простоя, а затем перестаёт.
Процесс подтверждения списания
Каждое списание проходит через подтверждение, которое пользователь одобряет PIN-кодом — один ваш ключ никогда не перемещает Vito:
- 1Ваше приложение вызывает POST /deduct с пользователем, суммой, исходным guildId и данными об элементе.
- 2Vito создаёт ожидающее подтверждение (действительно 10 минут) и возвращает confirmUrl. Если указан guildId, пользователю также отправляется ЛС с данными и кнопкой на страницу подтверждения.
- 3Пользователь открывает confirmUrl на vetox.io и одобряет списание PIN-кодом своего кошелька.
- 4Vito списывает Vito, записывает транзакцию и — если у вас настроен вебхук — отправляет подписанное событие confirmation.completed на ваш URL обратного вызова.
- 5Ваше приложение проверяет подпись и выполняет своё действие (например, разблокирует контент).
Параметры запроса (/deduct)
/v1/deductЭндпоинт списания принимает следующие поля тела:
discordIdamountguildIdreasonmerchantRefproductproduct.imageUrlmetadata* обязательное поле.
POST https://api.vetox.io/public/vito/v1/deduct
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
Idempotency-Key: req_9a1f2b # optional, dedupes retries for 24h
{
"discordId": "123456789012345678",
"amount": 500,
"guildId": "1470389021162209280",
"reason": "Unlock premium theme",
"merchantRef": "ref_1042",
"product": {
"type": "feature",
"name": "Premium theme",
"description": "Unlocks the premium theme pack",
"imageUrl": "https://cdn.example.com/theme.png"
},
"metadata": { "ref": "1042" }
}{
"success": true,
"data": {
"confirmationId": "f3c9...e1",
"confirmUrl": "https://vetox.io/confirm/vito/f3c9...e1",
"amount": 500,
"expiresAt": 1735689600000,
"status": "pending"
},
"requestId": "req_abc123",
"timestamp": 1735689300000
}Начисление Vito — зачисления (/add)
/v1/addPOST /add зачисляет Vito пользователю с вашего собственного баланса — для вознаграждений или зачислений. Принимает те же поля, что и /deduct, кроме guildId и product. В отличие от списания, оно мгновенное и не требует PIN пользователя — ваш API-ключ и есть ваше полномочие — поэтому ответ уже отражает завершённый перевод. Пользователь получает полную сумму; комиссия платформы не взимается.
POST https://api.vetox.io/public/vito/v1/add
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
Idempotency-Key: credit_7f3a # optional, dedupes retries for 24h
{
"discordId": "123456789012345678",
"amount": 250,
"reason": "Event reward",
"merchantRef": "reward_1042",
"metadata": { "ref": "1042" }
}{
"success": true,
"data": {
"transactionId": "txn_91b2",
"toDiscordId": "123456789012345678",
"amount": 250,
"ownerBalance": 48250,
"recipientBalance": 1250,
"status": "completed"
},
"requestId": "req_def456",
"timestamp": 1735689300000
}Вебхуки и проверка подписи
Добавьте один или несколько https-URL обратного вызова на вкладке Настройки. Vito доставляет подписанный POST на каждый настроенный URL, когда подтверждение достигает конечного состояния, повторяя до 5 раз с экспоненциальной задержкой.
Проверка подписи
Каждая доставка содержит заголовок X-Vito-Signature вида ts=<unix>;h1=<hex>. Пересчитайте HMAC по "<ts>:<rawBody>" вашим секретом подписи и сравните за постоянное время. Всегда проверяйте по необработанному телу запроса, до разбора JSON.
signature = HMAC_SHA256(signingSecret, "<ts>:<rawBody>")
header = X-Vito-Signature: ts=<unix-seconds>;h1=<hex-signature>import crypto from 'crypto';
import express from 'express';
const app = express();
// IMPORTANT: verify against the RAW body, before JSON parsing.
app.post('/webhooks/vito', express.raw({ type: '*/*' }), (req, res) => {
const sig = req.header('X-Vito-Signature') || ''; // "ts=<unix>;h1=<hmac>[;h1=<hmac>]"
const pairs = sig.split(';').map(p => p.split('='));
const ts = pairs.find(([k]) => k === 'ts')?.[1];
const sigs = pairs.filter(([k]) => k === 'h1').map(([, v]) => v);
const rawBody = req.body.toString('utf8');
const expected = crypto
.createHmac('sha256', process.env.VITO_SIGNING_SECRET) // whsec_...
.update(`${ts}:${rawBody}`)
.digest('hex');
const expectedBuf = Buffer.from(expected, 'hex');
// During a 24h secret rotation the header carries TWO h1= values — accept ANY.
// Length-guard FIRST: crypto.timingSafeEqual throws on a length mismatch.
const ok = sigs.some(h => {
const got = Buffer.from(h, 'hex');
return (
got.length === expectedBuf.length &&
crypto.timingSafeEqual(got, expectedBuf)
);
});
// Reject anything older than ~5 minutes to stop replays.
const fresh = ts && Math.abs(Date.now() / 1000 - Number(ts)) < 300;
if (!ok || !fresh) return res.status(401).end();
const event = JSON.parse(rawBody);
if (event.eventType === 'confirmation.completed') {
grantPremiumTheme(event.data.merchantRef, event.data.fromDiscordId);
}
res.status(200).end(); // ack fast; run your action async if slow
});События вебхуков и полезные нагрузки
Vito отправляет один из четырёх типов событий. Все они имеют одинаковую форму полезной нагрузки; data.status отражает результат.
confirmation.completedconfirmation.failedconfirmation.expiredconfirmation.cancelledcredit.completedPOST https://your-app.com/webhooks/vito
X-Vito-Signature: ts=1735689600;h1=4f8b2c...e9
X-Vito-Event-Id: evt_3a2b1c
X-Vito-Event-Type: confirmation.completed
Content-Type: application/json
{
"eventId": "evt_3a2b1c",
"eventType": "confirmation.completed",
"timestamp": 1735689600000,
"data": {
"confirmationId": "f3c9...e1",
"type": "deduct",
"fromDiscordId": "123456789012345678",
"toDiscordId": null,
"amount": 500,
"reason": "Unlock premium theme",
"merchantRef": "ref_1042",
"product": { "type": "feature", "name": "Premium theme" },
"status": "completed",
"transactionId": "txn_77a2",
"metadata": { "ref": "1042" },
"failureReason": null
}
}Коды ошибок
Ошибки возвращают статус, отличный от 2xx, с телом JSON, содержащим error.code верхнего уровня. Частые случаи:
VITO_VALIDATION_ERRORinvalid keyOWNER_MEMBERSHIP_INACTIVEscope / IPnot foundinsufficient balanceINSUFFICIENT_OWNER_FUNDSrate limited{
"success": false,
"error": { "type": "Bad Request", "message": "guildId must be a valid Discord server ID", "code": "VITO_VALIDATION_ERROR" },
"requestId": "req_abc123",
"timestamp": 1735689300000
}Примеры интеграции
Создать запрос на списание:
/v1/deductcurl -X POST https://api.vetox.io/public/vito/v1/deduct \
-H "Authorization: Bearer $VITO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"discordId": "123456789012345678",
"amount": 500,
"guildId": "1470389021162209280",
"merchantRef": "ref_1042",
"product": { "type": "feature", "name": "Premium theme" }
}'Проверить баланс пользователя:
/v1/balance/:discordIdcurl https://api.vetox.io/public/vito/v1/balance/123456789012345678 \
-H "Authorization: Bearer $VITO_API_KEY"Тестирование с демо-интеграцией
Предоставляется автономная демо-интеграция для отработки полного процесса от начала до конца без написания кода. Она проходит запрос → подтверждение → PIN → вебхук → завершение.
- 1Откройте папку демо-интеграции и добавьте ваш ключ Vito API, токен бота Discord и ID тестового сервера (guild) в config.json.
- 2Выполните npm install && npm start, затем откройте http://localhost:4000.
- 3Найдите пользователя по Discord ID, выберите элемент и начните списание — демо вызывает /deduct и показывает confirmUrl.
- 4Одобрите PIN на vetox.io; демо обнаруживает завершение (через вебхук, если настроен, иначе опросом) и выполняет демонстрационное действие.
Лучшие практики, лимиты и безопасность
Безопасность
- Храните ключ API и секрет подписи только на стороне сервера; при утечке немедленно ротируйте любой из них.
- Всегда проверяйте подпись вебхука по необработанному телу и отклоняйте доставки старше ~5 минут (защита от повторов).
- Выполняйте своё действие только при confirmation.completed — никогда по ответу /deduct (в этот момент списание ещё не окончательно).
- Используйте список разрешённых IP и запрашивайте только действительно нужные области доступа.
Лимиты
- Подтверждения истекают через 10 минут; считайте неподтверждённые запросы брошенными.
- Лимиты запросов действуют на проект и растут с уровнем членства владельца.
- amount должно быть положительным целым числом; metadata ограничено 10 ключами.