Erlaube geprüften Apps, dein Vito mit PIN-Bestätigung zu belasten, und verwalte Zahlungsanfragen auf deiner Käufe-Seite.
Erfordert eine Silver-Mitgliedschaft oder höher — sowohl für die Bewerbung als auch für jeden authentifizierten Aufruf. Läuft die Mitgliedschaft des Schlüsselinhabers aus, wird das Projekt eingefroren, bis er sie erneuert.
Eine REST-API, mit der deine Anwendung das Vito-Guthaben eines Nutzers aus Discord heraus verwendet — lesen, belasten, gutschreiben oder zwischen Nutzern bewegen. Alle Endpunkte liefern JSON und sind unter /v1 versioniert.
Vito bewegt sich nie zu oder von echtem Geld. Es wandert ausschließlich zwischen Vetox-Guthaben.
Dein Schlüssel und dein Signing Secret werden genau einmal angezeigt. Nach der Genehmigung hast du ein 7-Tage-Fenster, um sie im Tab „API Keys“ aufzudecken. Vetox speichert nur einen Hash und kann sie nicht erneut anzeigen — verpasst du das Fenster, musst du rotieren.
Halte ihn ausschließlich serverseitig — wer ihn besitzt, kann deine Nutzer belasten
Rotiere im Tab „API Keys“. Der vorherige Schlüssel funktioniert weitere 24 Stunden als Karenzzeit, damit du ohne Ausfall deployen kannst
Das Webhook-Signing-Secret rotiert separat, mit eigener 24-Stunden-Überlappung
Dein Schlüssel allein kann das Vito eines Nutzers nicht bewegen. Jede Belastung erfordert die Freigabe des Nutzers mit seiner Wallet-PIN, auf vetox.io — niemals in deiner App und niemals in Discord.
1
Deine App ruft POST /v1/deduct auf
Mit dem Nutzer, dem Betrag, der auslösenden guildId und den Artikeldetails.
2
Vito liefert eine confirmUrl
Eine ausstehende Bestätigung, 10 Minuten gültig. Der Nutzer erhält zusätzlich eine DM.
3
Der Nutzer bestätigt mit seiner PIN
Auf vetox.io.
4
Vito bucht und benachrichtigt
Das Guthaben wird belastet, die Transaktion gespeichert und ein signierter Webhook gesendet, sofern konfiguriert.
5
Deine App prüft und schließt ab
Signatur prüfen, dann den Inhalt freischalten oder den Artikel ausliefern.
Schließe deine Aktion ausschließlich bei confirmation.completed ab — niemals bei der Antwort von /deduct. Zu diesem Zeitpunkt ist die Belastung noch nicht endgültig.
POST /v1/add schreibt einem Nutzer Vito aus deinem eigenen Guthaben gut — für Belohnungen oder Erstattungen. Dieselben Felder wie /deduct, außer guildId und product.
Anders als eine Belastung hat eine Gutschrift keinen Bestätigungsschritt — sie wird sofort gebucht. Erfordert den Scope credit:create und ausreichendes Guthaben, sonst antwortet der Aufruf mit 402 VITO_INSUFFICIENT_OWNER_FUNDS.
Jede Belastung wird an dich ausgezahlt, abzüglich der Plattformgebühr — nach demselben Schema wie In-App-Vito-Transfers, basierend auf deiner Mitgliedschaftsstufe:
Deine Mitgliedschaft
Gebühr
Normal, Silver, Gold
7 %
Platinum
6 %
Diamond
5 %
Beträge von 5 Vito oder weniger sind gebührenfrei, und Gutschriften über /v1/add sind immer gebührenfrei.
Trage im Tab „Settings“ eine oder mehrere https-Callback-URLs ein. Vito sendet ein signiertes POST, sobald eine Bestätigung einen Endzustand erreicht.
Webhooks werden nur ausgeliefert, wenn dein Projekt sowohl eine Callback-URL als auch ein Signing Secret hat. Decke das Secret (whsec_…) einmalig im Tab „API Keys“ auf.
Jede Auslieferung trägt einen X-Vito-Signature-Header:
X-Vito-Signature: ts=<unix>;h1=<hex>
Zwei weitere Header begleiten jede Auslieferung — nutze X-Vito-Event-Id als Deduplizierungsschlüssel, denn ein Retry sendet dieselbe ID erneut:
Header
Enthält
X-Vito-Event-Id
Stabile ID für dieses Event — über Retries hinweg identisch
X-Vito-Event-Type
z. B. confirmation.completed
Während einer Signing-Secret-Rotation enthält der Header mehr als eine Signatur, die neueste zuerst:
X-Vito-Signature: ts=<unix>;h1=<neu>;h1=<alt>
Akzeptiere die Auslieferung, wenn irgendeinh1 passt. Ein Verifier, der nur den ersten liest, lehnt jeden Webhook ab, bis er das neue Secret ausgerollt hat — was den Sinn der 24-Stunden-Überlappung zunichtemacht.
Berechne den HMAC über <ts>:<rawBody> mit deinem Signing Secret neu und vergleiche in konstanter Zeit.
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) ); });}
Prüfe gegen den Raw Body, bevor JSON-Parsing oder Middleware ihn umschreibt.
Zusätzlich gilt ein Limit pro IP in Höhe der halben Minutenquote, mindestens jedoch 30.
Unter Last scheitern Schreib-Endpunkte geschlossen — eine Belastung wird abgelehnt, statt ein Double-Spend zu riskieren. Lesende Endpunkte scheitern offen. Behandle eine abgelehnte Schreiboperation als „nicht passiert“ und wiederhole sie.
Sende einen Idempotency-Key-Header, um Retries sicher zu deduplizieren.