Autorisez les applications approuvées à prélever vos Vito avec confirmation par PIN, et gérez les demandes de paiement depuis votre page Achats.
Nécessite un abonnement Silver ou supérieur — à la fois pour postuler et pour chaque appel authentifié. Si l’abonnement du détenteur de la clé expire, le projet est gelé jusqu’à son renouvellement.
Une API REST qui permet à votre application de manipuler le solde Vito d’un utilisateur depuis Discord : le lire, le débiter, le créditer ou le déplacer entre utilisateurs. Tous les endpoints renvoient du JSON et sont versionnés sous /v1.
Les Vito ne se convertissent jamais en argent réel, ni l’inverse. Ils circulent uniquement entre les soldes Vetox.
Tous les endpoints se trouvent sous https://api.vetox.io/public/vito.Les chemins écrits dans cette page — /v1/deduct, /v1/balance/:discordId et les autres — sont relatifs à ce préfixe, que le SDK ajoute pour vous. Si vous appelez directement, utilisez l’URL complète :
https://api.vetox.io/public/vito/v1/deduct
https://api.vetox.io/v1/deduct n’est pas une route et renvoie 404.
Vous développez en Node.js ? N’écrivez pas les appels REST à la main — utilisez le paquet officiel @vetox-bot/vito. Il couvre les neuf endpoints ainsi que la vérification des webhooks, et gère pour vous l’idempotence et les nouvelles tentatives. Voir SDK Node.js officiel ci-dessous.
Votre clé et votre secret de signature ne sont affichés qu’une seule fois. Après l’approbation, vous disposez d’une fenêtre de 7 jours pour les révéler depuis l’onglet des clés API. Vetox n’en conserve qu’un hachage et ne peut pas les réafficher — si vous manquez la fenêtre, il faut effectuer une rotation.
Gardez-la uniquement côté serveur — quiconque la détient peut débiter vos utilisateurs
Effectuez la rotation depuis l’onglet des clés API. La clé précédente reste valable pendant un délai de grâce de 24 heures, ce qui permet de déployer sans interruption
Le secret de signature des webhooks se renouvelle séparément, avec son propre chevauchement de 24 heures
En cas de fuite, effectuez immédiatement une rotation
Le paquet officiel @vetox-bot/vito encapsule les neuf endpoints ainsi que la vérification des webhooks. Il gère pour vous l’en-tête Idempotency-Key, les nouvelles tentatives avec temporisation croissante, les délais d’attente et la classification des erreurs.
Nécessite Node.js 20 ou plus récent. Le paquet n’a aucune dépendance d’exécution — il utilise le fetch intégré et node:crypto — et est livré en ESM et en CommonJS, avec des définitions TypeScript complètes.
import { VitoClient } from '@vetox-bot/vito';const vito = new VitoClient({ apiKey: process.env.VITO_API_KEY });
Si vous omettez apiKey, le SDK lit VITO_API_KEY dans l’environnement. Le format de la clé est validé à la construction : une clé mal formée échoue immédiatement au lieu de vous coûter un aller-retour réseau et un 401.
La clé déplace de l’argent — gardez-la toujours côté serveur, jamais dans un bundle client ni dans un navigateur. console.log(vito) affiche [redacted] à la place de la clé, et le SDK refuse toute baseUrl en http:// pour un hôte non local, afin que la clé ne circule pas en clair.
Chaque méthode renvoie directement le champ data déjà déballé — vous n’avez jamais à extraire success ni data vous-même. Toutes acceptent en plus des options par appel : { timeoutMs, maxRetries, signal, headers }, et les méthodes d’écriture acceptent également { idempotencyKey }.
const wallet = await vito.balance.retrieve('123456789012345678');if (!wallet.hasPin) { // Pas encore de code PIN de portefeuille : aucun débit ne peut être confirmé.}
Enregistrez la commande comme en attente, rien de plus. Le débit n’a pas encore eu lieu : livrez sur le webhook confirmation.completed, pas sur cette réponse.
const credit = await vito.payments.credit( { discordId: '123456789012345678', amount: 250, reason: 'Remboursement de la commande order_10423', merchantRef: 'refund_10423', }, // Dériver la clé de votre propre enregistrement rend l'opération rejouable depuis // une file de tâches : une répétition renvoie le résultat d'origine au lieu de // payer une seconde fois. { idempotencyKey: 'refund_10423' },);console.log(credit.transactionId, credit.ownerBalance, credit.recipientBalance);
const since = Date.now() - 24 * 60 * 60 * 1000;// Un générateur asynchrone qui récupère les pages à la demande — sans boucle manuelle.for await (const tx of vito.transactions.iterate({ limit: 100 })) { if (tx.date < since) break; // les plus récentes d'abord // tx.type est le sens par rapport à tx.discordId : 1 = entrée, 0 = sortie.}
Le SDK envoie un en-tête Idempotency-Key sur chaque écriture (deduct, credit, transfer). Si vous n’en fournissez pas, il la génère une seule fois par appel et rejoue exactement la même clé à chaque nouvelle tentative : une nouvelle tentative ne peut donc jamais régler l’opération deux fois.Fournissez votre propre clé lorsque la même opération logique peut être relancée depuis un nouveau processus — un exécuteur de tâches, une relivraison de file d’attente ou un balayage planifié :
Pannes réseau, délais dépassés, 408, 429, 5xx et 409 VITO_IDEMPOTENCY_IN_PROGRESS
Jamais réessayé
409 VITO_IDEMPOTENCY_CONFLICT, toutes les autres erreurs 4xx et auth.rotateKey()
auth.rotateKey() est exclu délibérément — une nouvelle tentative y génère une seconde clé et invalide celle renvoyée par la première.
Si Retry-After dépasse maxRetryDelayMs (votre quota horaire est réellement épuisé), le SDK lève immédiatement VitoRateLimitError au lieu de dormir pendant tout votre budget de requête.
import { Webhooks, VitoSignatureVerificationError } from '@vetox-bot/vito';try { const event = Webhooks.constructEvent({ payload: rawRequestBody, // le corps brut — voir l'avertissement ci-dessous signature: req.header('x-vito-signature') ?? '', secret: process.env.VITO_WEBHOOK_SECRET, }); if (event.eventType === 'confirmation.completed') { // Le type de data est affiné automatiquement selon eventType. await fulfil(event.data.merchantRef); }} catch (error) { if (error instanceof VitoSignatureVerificationError) { // Rejetez la livraison avec un 400 : falsifiée, périmée ou corps ré-encodé. }}
constructEvent contrôle la fenêtre de rejeu de 5 minutes, compare en temps constant avec chaque signature h1 de l’en-tête — il fonctionne donc automatiquement pendant le chevauchement de 24 heures d’une rotation — puis analyse la charge utile et renvoie l’événement typé.Dans un gestionnaire de route Next.js (App Router), utilisez la variante qui lit elle-même le corps brut :
La signature couvre les octets bruts. Avec Express, montez express.raw({ type: 'application/json' }) sur la route du webhook — express.json() consomme le corps et toute vérification échoue ensuite. Avec Next.js, n’appelez pas request.json() avant constructEventFromRequest.
La livraison se fait au moins une fois. Dédupliquez sur event.eventId avant d’exécuter le moindre effet de bord.
Votre clé seule ne peut pas déplacer les Vito d’un utilisateur. Chaque débit exige que l’utilisateur l’approuve avec le code PIN de son portefeuille, sur vetox.io — jamais dans votre application ni dans Discord.
1
Votre application appelle POST /v1/deduct
Avec l’utilisateur, le montant, le guildId d’origine et les détails de l’article.
2
Vito renvoie une confirmUrl
Une confirmation en attente, valable 10 minutes. L’utilisateur reçoit également un MP.
3
L'utilisateur approuve avec son code PIN
Sur vetox.io.
4
Vito règle et notifie
Le solde est débité, la transaction enregistrée, et un webhook signé est envoyé si vous en avez configuré un.
5
Votre application vérifie et finalise
Vérifiez la signature, puis débloquez le contenu ou livrez l’article.
Ne finalisez votre action que sur confirmation.completed — jamais sur la réponse de /deduct. À ce stade, le débit n’est pas définitif.
POST /v1/add crédite des Vito à un utilisateur depuis votre propre solde — pour des récompenses ou des remboursements. Mêmes champs que /deduct, sauf guildId et product.
Contrairement à un débit, un crédit n’a pas d’étape de confirmation : il est réglé immédiatement. Nécessite le scope credit:create et un solde suffisant, sinon l’appel renvoie 402 VITO_INSUFFICIENT_OWNER_FUNDS.
Chaque débit vous est reversé après déduction des frais de la plateforme — selon le même barème que les transferts Vito dans l’application, en fonction de votre niveau d’abonnement :
Votre abonnement
Frais
Normal, Silver, Gold
7 %
Platinum
6 %
Diamond
5 %
Les montants de 5 Vito ou moins sont sans frais, et les crédits via /v1/add le sont toujours.
Ajoutez une ou plusieurs URL de rappel https dans l’onglet des paramètres. Vito envoie un POST signé dès qu’une confirmation atteint un état final.
Les webhooks ne partent que si votre projet possède à la fois une URL de rappel et un secret de signature. Révélez le secret (whsec_…) une seule fois depuis l’onglet des clés API.
Chaque livraison porte un en-tête X-Vito-Signature :
X-Vito-Signature: ts=<unix>;h1=<hex>
Deux autres en-têtes accompagnent chaque livraison — utilisez X-Vito-Event-Id comme clé de déduplication, car une nouvelle tentative renvoie le même identifiant :
En-tête
Contient
X-Vito-Event-Id
Identifiant stable de cet événement — identique d’une tentative à l’autre
X-Vito-Event-Type
Par exemple confirmation.completed
Pendant une rotation du secret de signature, l’en-tête porte plusieurs signatures, la plus récente en premier :
Acceptez la livraison si l’un quelconque des h1 correspond. Un vérificateur qui ne lit que le premier rejettera tous les webhooks tant qu’il n’aura pas déployé le nouveau secret — ce qui annule tout l’intérêt du chevauchement de 24 heures.
Sous Node.js : Webhooks.constructEvent de @vetox-bot/vito fait tout cela pour vous — la fenêtre de rejeu, la comparaison avec chaque signature h1 et le contrôle en temps constant — et renvoie l’événement typé. Le code ci-dessous sert à une implémentation manuelle ou à un autre langage.
Recalculez le HMAC sur <ts>:<rawBody> avec votre secret de signature et comparez en temps constant.
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) ); });}
Vérifiez sur le corps brut, avant tout parsing JSON ou middleware susceptible de le réécrire.
Il existe aussi une limite par IP égale à la moitié de votre quota par minute, avec un plancher de 30.
En charge, les endpoints d’écriture échouent en mode fermé — un débit est rejeté plutôt que de risquer une double dépense. Les lectures échouent en mode ouvert. Traitez une écriture rejetée comme « n’a pas eu lieu » et réessayez.
Envoyez un en-tête Idempotency-Key pour dédupliquer les nouvelles tentatives en toute sécurité.
La clé API et le secret de signature n’ont jamais leur place dans du code client. En cas de fuite de l’un ou l’autre, effectuez immédiatement une rotation.
Vérifiez chaque webhook
Contrôlez la signature sur le corps brut et rejetez les livraisons datant de plus de ~5 minutes.
Ne réglez que sur completed
Ne livrez jamais sur la seule réponse de /deduct — le débit n’est définitif qu’avec confirmation.completed.
Moindre privilège
Ne demandez que les scopes réellement utilisés et activez la liste d’IP autorisées.