Permita que apps aprovados cobrem seu Vito com confirmação por PIN e gerencie solicitações de pagamento na sua página Purchases.
Exige uma Assinatura Silver ou superior — tanto para se candidatar quanto para cada chamada autenticada. Se a Assinatura do dono da chave expirar, o projeto é congelado até ele reassinar.
Uma API REST que permite ao seu aplicativo trabalhar com o saldo de Vito de um usuário a partir do Discord: ler, cobrar, creditar ou mover entre usuários. Todos os endpoints retornam JSON e são versionados sob /v1.
Vito nunca se converte em dinheiro real, nem vem dele. Ele só circula entre saldos da Vetox.
Sua chave e seu segredo de assinatura são exibidos exatamente uma vez. Após a aprovação você tem uma janela de 7 dias para revelá-los na aba de chaves API. A Vetox guarda apenas um hash e não consegue exibi-los de novo — se perder a janela, será preciso rotacionar.
Mantenha-a apenas no servidor — quem a tiver pode cobrar dos seus usuários
Rotacione pela aba de chaves API. A chave anterior continua funcionando por um período de carência de 24 horas, para você implantar sem indisponibilidade
O segredo de assinatura dos webhooks é rotacionado separadamente, com sua própria sobreposição de 24 horas
Sua chave sozinha não move os Vito de um usuário. Toda cobrança exige que o usuário a aprove com o PIN da carteira dele, em vetox.io — nunca dentro do seu app nem dentro do Discord.
1
Seu app chama POST /v1/deduct
Com o usuário, o valor, o guildId de origem e os detalhes do item.
2
O Vito retorna uma confirmUrl
Uma confirmação pendente, válida por 10 minutos. O usuário também recebe uma DM.
3
O usuário aprova com o PIN dele
Em vetox.io.
4
O Vito liquida e notifica
O saldo é debitado, a transação registrada e um webhook assinado é enviado, caso você tenha um configurado.
5
Seu app verifica e conclui
Confira a assinatura e então libere o conteúdo ou entregue o item.
Conclua sua ação somente em confirmation.completed — nunca na resposta do /deduct. Nesse ponto a cobrança ainda não é definitiva.
POST /v1/add credita Vito a um usuário a partir do seu próprio saldo — para recompensas ou estornos. Os mesmos campos do /deduct, exceto guildId e product.
Diferente de uma cobrança, um crédito não tem etapa de confirmação: é liquidado na hora. Exige o scope credit:create e saldo suficiente, senão a chamada retorna 402 VITO_INSUFFICIENT_OWNER_FUNDS.
Toda cobrança é liquidada para você já descontada a taxa da plataforma — o mesmo esquema das transferências de Vito dentro do app, conforme o seu nível de Assinatura:
Sua Assinatura
Taxa
Normal, Silver, Gold
7%
Platinum
6%
Diamond
5%
Valores de 5 Vito ou menos são isentos de taxa, e créditos via /v1/add são sempre isentos.
Adicione uma ou mais URLs de callback https na aba de configurações. O Vito entrega um POST assinado sempre que uma confirmação chega a um estado final.
Os webhooks só são disparados se o seu projeto tiver ao mesmo tempo uma URL de callback e um segredo de assinatura. Revele o segredo (whsec_…) uma única vez na aba de chaves API.
Outros dois cabeçalhos acompanham cada entrega — use X-Vito-Event-Id como chave de deduplicação, já que uma retentativa reenvia o mesmo id:
Cabeçalho
Contém
X-Vito-Event-Id
Id estável deste evento — idêntico entre retentativas
X-Vito-Event-Type
Por exemplo confirmation.completed
Durante uma rotação do segredo de assinatura, o cabeçalho carrega mais de uma assinatura, a mais nova primeiro:
X-Vito-Signature: ts=<unix>;h1=<nova>;h1=<antiga>
Aceite a entrega se qualquerh1 conferir. Um verificador que lê apenas o primeiro vai rejeitar todos os webhooks até implantar o segredo novo — o que anula justamente o propósito da sobreposição de 24 horas.
Recalcule o HMAC sobre <ts>:<rawBody> com seu segredo de assinatura e compare em tempo constante.
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) ); });}
Verifique contra o corpo bruto, antes de qualquer parsing de JSON ou middleware que o reescreva.
Há também um limite por IP igual à metade da sua cota por minuto, com piso de 30.
Sob carga, os endpoints de escrita falham de forma fechada — a cobrança é recusada em vez de arriscar um gasto duplicado. Os de leitura falham de forma aberta. Trate uma escrita recusada como “não aconteceu” e tente de novo.
Envie um cabeçalho Idempotency-Key para deduplicar retentativas com segurança.