Laat goedgekeurde apps je Vito afboeken met pincode-bevestiging, en beheer betalingsverzoeken vanaf je Aankopen-pagina.
Vereist een Silver-lidmaatschap of hoger — zowel om je aan te melden als voor elke geauthenticeerde aanroep. Verloopt het lidmaatschap van de sleutelhouder, dan wordt het project bevroren tot hij zich opnieuw abonneert.
Een REST-API waarmee je applicatie vanuit Discord met het Vito-saldo van een gebruiker werkt: uitlezen, afschrijven, bijschrijven of tussen gebruikers verplaatsen. Alle endpoints geven JSON terug en zijn geversioneerd onder /v1.
Vito wordt nooit omgezet in echt geld, en komt er ook nooit uit voort. Het beweegt uitsluitend tussen Vetox-saldi.
Je sleutel en signing secret worden precies één keer getoond. Na goedkeuring heb je een venster van 7 dagen om ze te onthullen op het tabblad met API-sleutels. Vetox bewaart alleen een hash en kan ze niet opnieuw tonen — mis je het venster, dan moet je roteren.
Houd hem uitsluitend serverzijdig — wie hem heeft, kan je gebruikers afschrijven
Roteer via het tabblad met API-sleutels. De vorige sleutel blijft nog 24 uur werken als overgangsperiode, zodat je zonder downtime kunt uitrollen
Het webhook-signing-secret roteert apart, met een eigen overlap van 24 uur
Je sleutel alleen kan het Vito van een gebruiker niet verplaatsen. Elke afschrijving vereist dat de gebruiker akkoord gaat met de pincode van zijn wallet, op vetox.io — nooit in je app en nooit in Discord.
1
Je app roept POST /v1/deduct aan
Met de gebruiker, het bedrag, de guildId van herkomst en de artikelgegevens.
2
Vito geeft een confirmUrl terug
Een openstaande bevestiging, 10 minuten geldig. De gebruiker krijgt ook een DM.
3
De gebruiker keurt goed met zijn pincode
Op vetox.io.
4
Vito verrekent en meldt
Het saldo wordt afgeschreven, de transactie vastgelegd en er wordt een ondertekende webhook verstuurd als je er een hebt ingesteld.
5
Je app verifieert en rondt af
Controleer de handtekening en ontgrendel dan de content of lever het artikel.
Rond je actie uitsluitend af bij confirmation.completed — nooit op basis van het antwoord van /deduct. Op dat moment staat de afschrijving nog niet vast.
POST /v1/add schrijft Vito bij een gebruiker bij uit je eigen saldo — voor beloningen of terugbetalingen. Dezelfde velden als /deduct, behalve guildId en product.
Anders dan een afschrijving heeft een bijschrijving geen bevestigingsstap: die wordt direct verrekend. Vereist de scope credit:create en voldoende saldo, anders geeft de aanroep 402 VITO_INSUFFICIENT_OWNER_FUNDS terug.
Elke afschrijving wordt aan jou uitbetaald minus de platformkosten — hetzelfde schema als Vito-overboekingen in de app, op basis van jouw lidmaatschapsniveau:
Jouw lidmaatschap
Kosten
Normal, Silver, Gold
7%
Platinum
6%
Diamond
5%
Bedragen van 5 Vito of minder zijn kosteloos, en bijschrijvingen via /v1/add zijn dat altijd.
Voeg op het tabblad met instellingen een of meer https-callback-URL’s toe. Vito stuurt een ondertekende POST zodra een bevestiging een eindtoestand bereikt.
Webhooks worden alleen verstuurd als je project zowel een callback-URL als een signing secret heeft. Onthul het secret (whsec_…) eenmalig op het tabblad met API-sleutels.
Elke aflevering draagt een X-Vito-Signature-header:
X-Vito-Signature: ts=<unix>;h1=<hex>
Twee andere headers vergezellen elke aflevering — gebruik X-Vito-Event-Id als deduplicatiesleutel, want een nieuwe poging stuurt hetzelfde id opnieuw:
Header
Bevat
X-Vito-Event-Id
Stabiel id voor deze gebeurtenis — identiek over alle pogingen
X-Vito-Event-Type
Bijvoorbeeld confirmation.completed
Tijdens een rotatie van het signing secret bevat de header meer dan één handtekening, de nieuwste eerst:
X-Vito-Signature: ts=<unix>;h1=<nieuw>;h1=<oud>
Accepteer de aflevering als een willekeurigeh1 klopt. Een verifier die alleen de eerste leest, wijst elke webhook af totdat het nieuwe secret is uitgerold — precies wat de overlap van 24 uur moest voorkomen.
Herbereken de HMAC over <ts>:<rawBody> met je signing secret en vergelijk in constante tijd.
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) ); });}
Verifieer tegen de ruwe body, vóór JSON-parsing of middleware die hem herschrijft.
Er geldt ook een limiet per IP van de helft van je minuutquotum, met een ondergrens van 30.
Onder belasting falen schrijf-endpoints gesloten — een afschrijving wordt geweigerd in plaats van dubbele besteding te riskeren. Lees-endpoints falen open. Behandel een geweigerde schrijfactie als “niet gebeurd” en probeer opnieuw.
Stuur een Idempotency-Key-header mee om herhaalde pogingen veilig te dedupliceren.