Pozwól zatwierdzonym aplikacjom obciążać Twoje Vito z potwierdzeniem PIN-em i zarządzaj żądaniami płatności ze strony Purchases.
Wymaga członkostwa Silver lub wyższego — zarówno do złożenia wniosku, jak i przy każdym uwierzytelnionym wywołaniu. Jeśli członkostwo właściciela klucza wygaśnie, projekt zostaje zamrożony do czasu odnowienia.
REST API pozwalające Twojej aplikacji operować na saldzie Vito użytkownika prosto z Discorda — odczytywać je, obciążać, uznawać albo przenosić między użytkownikami. Wszystkie endpointy zwracają JSON i są wersjonowane pod /v1.
Vito nigdy nie zamienia się w prawdziwe pieniądze ani z nich nie pochodzi. Krąży wyłącznie między saldami Vetox.
Klucz i sekret podpisu pokazywane są dokładnie raz. Po zatwierdzeniu masz okno 7 dni, aby odsłonić je w zakładce kluczy API. Vetox przechowuje tylko skrót i nie może pokazać ich ponownie — jeśli przegapisz okno, trzeba wykonać rotację.
Trzymaj go wyłącznie po stronie serwera — kto go ma, może obciążać Twoich użytkowników
Rotuj w zakładce kluczy API. Poprzedni klucz działa jeszcze przez 24 godziny jako okres przejściowy, żebyś mógł wdrożyć zmianę bez przestoju
Sekret podpisu webhooków rotuje się osobno, z własnym 24-godzinnym nakładaniem
Sam klucz nie przeniesie Vito użytkownika. Każde obciążenie wymaga zatwierdzenia przez użytkownika PIN-em jego portfela, na vetox.io — nigdy w Twojej aplikacji ani w Discordzie.
1
Twoja aplikacja wywołuje POST /v1/deduct
Z użytkownikiem, kwotą, źródłowym guildId i szczegółami przedmiotu.
2
Vito zwraca confirmUrl
Oczekujące potwierdzenie, ważne 10 minut. Użytkownik dostaje też wiadomość prywatną.
3
Użytkownik zatwierdza swoim PIN-em
Na vetox.io.
4
Vito rozlicza i powiadamia
Saldo zostaje obciążone, transakcja zapisana, a podpisany webhook wysłany, jeśli go skonfigurowałeś.
5
Twoja aplikacja weryfikuje i finalizuje
Sprawdź podpis, a potem odblokuj treść lub wydaj przedmiot.
Finalizuj swoje działanie wyłącznie przy confirmation.completed — nigdy na podstawie odpowiedzi /deduct. W tym momencie obciążenie nie jest jeszcze ostateczne.
POST /v1/add dodaje użytkownikowi Vito z Twojego własnego salda — na nagrody lub zwroty. Te same pola co /deduct, poza guildId i product.
W odróżnieniu od obciążenia, uznanie nie ma kroku potwierdzenia — rozliczane jest od razu. Wymaga scope’a credit:create i wystarczającego salda, w przeciwnym razie wywołanie zwróci 402 VITO_INSUFFICIENT_OWNER_FUNDS.
Każde obciążenie trafia do Ciebie pomniejszone o opłatę platformy — według tego samego cennika co przelewy Vito w aplikacji, zależnie od Twojego poziomu członkostwa:
Twoje członkostwo
Opłata
Normal, Silver, Gold
7%
Platinum
6%
Diamond
5%
Kwoty 5 Vito lub mniejsze są bez opłat, a uznania przez /v1/add są bez opłat zawsze.
Każdej dostawie towarzyszą jeszcze dwa nagłówki — użyj X-Vito-Event-Id jako klucza deduplikacji, bo ponowna próba wysyła to samo id:
Nagłówek
Zawiera
X-Vito-Event-Id
Stałe id tego zdarzenia — identyczne przy wszystkich ponowieniach
X-Vito-Event-Type
Na przykład confirmation.completed
Podczas rotacji sekretu podpisu nagłówek niesie więcej niż jeden podpis, najnowszy jako pierwszy:
X-Vito-Signature: ts=<unix>;h1=<nowy>;h1=<stary>
Przyjmij dostawę, jeśli zgadza się którykolwiekh1. Weryfikator czytający tylko pierwszy odrzuci każdy webhook, dopóki nie wdroży nowego sekretu — a to niweczy cały sens 24-godzinnego nakładania.
Przelicz HMAC z <ts>:<rawBody> swoim sekretem podpisu i porównaj w stałym czasie.
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) ); });}
Weryfikuj wobec surowego ciała żądania, zanim parsowanie JSON albo middleware je przepisze.
Obowiązuje też limit na adres IP równy połowie Twojego limitu minutowego, nie mniej niż 30.
Pod obciążeniem endpointy zapisu zawodzą „na zamknięte” — obciążenie zostaje odrzucone, zamiast ryzykować podwójne wydanie. Endpointy odczytu zawodzą „na otwarte”. Traktuj odrzucony zapis jako „nie wydarzył się” i ponów go.
Wysyłaj nagłówek Idempotency-Key, aby bezpiecznie deduplikować ponowienia.