Дозвольте схваленим застосункам списувати ваші Vito з підтвердженням PIN та керуйте запитами оплати зі сторінки Purchases.
Потрібна підписка Silver або вище — і щоб подати заявку, і для кожного авторизованого виклику. Якщо підписка власника ключа спливає, проєкт заморожується до її поновлення.
REST API, що дає вашому застосунку працювати з балансом Vito користувача просто з Discord: читати його, списувати, нараховувати або переказувати між користувачами. Усі ендпоїнти повертають JSON і версіоновані під /v1.
Vito ніколи не переходить у реальні гроші й не береться з них. Він рухається лише між балансами Vetox.
Ключ і секрет підпису показуються рівно один раз. Після схвалення у вас є вікно у 7 днів, щоб розкрити їх на вкладці API-ключів. Vetox зберігає лише хеш і не може показати їх знову — якщо пропустите вікно, доведеться зробити ротацію.
Тримайте ключ лише на сервері — хто ним володіє, може списувати кошти ваших користувачів
Робіть ротацію на вкладці API-ключів. Попередній ключ працює ще 24 години — пільговий період, щоб викотити оновлення без простою
Секрет підпису вебхуків ротується окремо, з власним 24-годинним перекриттям
Сам лише ключ не може перемістити Vito користувача. Кожне списання вимагає підтвердження користувачем PIN-кодом його гаманця, на vetox.io — ніколи всередині вашого застосунку й ніколи всередині Discord.
1
Ваш застосунок викликає POST /v1/deduct
З користувачем, сумою, вихідним guildId і даними товару.
2
Vito повертає confirmUrl
Підтвердження в очікуванні, дійсне 10 хвилин. Користувач також отримує ПП.
3
Користувач підтверджує PIN-кодом
На vetox.io.
4
Vito проводить розрахунок і сповіщає
Баланс списується, транзакція записується, і надсилається підписаний вебхук, якщо він у вас налаштований.
5
Ваш застосунок перевіряє й завершує
Перевірте підпис, потім відкрийте доступ до вмісту або видайте товар.
Завершуйте свою дію лише за confirmation.completed — ніколи за відповіддю /deduct. На цьому етапі списання ще не остаточне.
POST /v1/add нараховує Vito користувачеві з вашого власного балансу — для нагород або повернень. Ті самі поля, що й у /deduct, окрім guildId і product.
На відміну від списання, нарахування не має кроку підтвердження — розрахунок відбувається одразу. Потрібен scope credit:create і достатній баланс, інакше виклик поверне 402 VITO_INSUFFICIENT_OWNER_FUNDS.
Кожне списання надходить вам за вирахуванням комісії платформи — за тією ж шкалою, що й перекази Vito всередині застосунку, залежно від вашого рівня підписки:
Ваша підписка
Комісія
Normal, Silver, Gold
7%
Platinum
6%
Diamond
5%
Суми 5 Vito й менше не обкладаються комісією, а нарахування через /v1/add не обкладаються нею ніколи.
Додайте одну або кілька https-адрес зворотного виклику на вкладці налаштувань. Vito надсилає підписаний POST, щойно підтвердження досягає кінцевого стану.
Вебхуки надсилаються, лише якщо у проєкту є і адреса зворотного виклику, і секрет підпису. Розкрийте секрет (whsec_…) один раз на вкладці API-ключів.
Кожна доставка містить заголовок X-Vito-Signature:
X-Vito-Signature: ts=<unix>;h1=<hex>
Кожну доставку супроводжують ще два заголовки — використовуйте X-Vito-Event-Id як ключ дедуплікації, бо повторна спроба надсилає той самий ідентифікатор:
Заголовок
Містить
X-Vito-Event-Id
Сталий ідентифікатор події — однаковий за всіх повторів
X-Vito-Event-Type
Наприклад, confirmation.completed
Під час ротації секрету підпису заголовок містить кілька підписів, найновіший першим:
Приймайте доставку, якщо збігається будь-який із h1. Код перевірки, що читає лише перший, відхилятиме всі вебхуки, доки не викотить новий секрет, — а це зводить нанівець весь сенс 24-годинного перекриття.
Перерахуйте HMAC за <ts>:<rawBody> вашим секретом підпису і порівняйте за сталий час.
const crypto = require('crypto');function verify(rawBody, header, secret) { const parts = header.split(';'); const ts = parts.find(p => p.startsWith('ts='))?.slice(3); const sigs = parts.filter(p => p.startsWith('h1=')).map(p => p.slice(3)); if (!ts || sigs.length === 0) return false; // Reject anything older than ~5 minutes — replay protection. if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; const expected = Buffer.from( crypto.createHmac('sha256', secret).update(`${ts}:${rawBody}`).digest('hex'), ); // Any matching signature is valid — a rotation emits several. return sigs.some(sig => { const actual = Buffer.from(sig); // timingSafeEqual throws when the lengths differ, so check first. return ( actual.length === expected.length && crypto.timingSafeEqual(actual, expected) ); });}
Перевіряйте за сирим тілом запиту, до будь-якого розбору JSON чи middleware, що його перепише.
Також діє ліміт на один IP, що дорівнює половині вашої хвилинної квоти, але не менш ніж 30.
Під навантаженням ендпоїнти запису відмовляють «закрито» — списання відхиляється, щоб не ризикувати подвійною витратою. Ендпоїнти читання відмовляють «відкрито». Вважайте відхилений запис таким, що «не стався», і повторіть його.
Надсилайте заголовок Idempotency-Key, щоб безпечно уникати дублів при повторних спробах.