Permite que aplicaciones aprobadas carguen sobre tu Vito con confirmación por PIN, y gestiona las solicitudes de pago desde tu página de Compras.
Requiere una Membresía Silver o superior — tanto para solicitarla como para cada llamada autenticada. Si la Membresía del titular de la clave caduca, el proyecto se congela hasta que vuelva a suscribirse.
Una API REST que permite a tu aplicación trabajar con el saldo de Vito de un usuario desde dentro de Discord: leerlo, cobrarlo, abonarlo o moverlo entre usuarios. Todos los endpoints devuelven JSON y están versionados bajo /v1.
Vito nunca se convierte en dinero real ni proviene de él. Solo se mueve entre saldos de Vetox.
Tu clave y tu secreto de firma se muestran exactamente una vez. Tras la aprobación dispones de una ventana de 7 días para revelarlos en la pestaña de claves API. Vetox solo guarda un hash y no puede volver a mostrarlos: si pierdes la ventana, tendrás que rotar.
Guárdala solo en el servidor — cualquiera que la tenga puede cobrar a tus usuarios
Rota desde la pestaña de claves API. La clave anterior sigue funcionando durante un periodo de gracia de 24 horas para que puedas desplegar sin cortes
El secreto de firma de webhooks rota por separado, con su propio solapamiento de 24 horas
Tu clave por sí sola no puede mover el Vito de un usuario. Todo cobro exige que el usuario lo apruebe con el PIN de su monedero, en vetox.io — nunca dentro de tu app ni dentro de Discord.
1
Tu app llama a POST /v1/deduct
Con el usuario, el importe, el guildId de origen y los detalles del artículo.
2
Vito devuelve una confirmUrl
Una confirmación pendiente, válida 10 minutos. El usuario también recibe un MD.
3
El usuario aprueba con su PIN
En vetox.io.
4
Vito liquida y notifica
Se descuenta el saldo, se registra la transacción y se envía un webhook firmado si tienes uno configurado.
5
Tu app verifica y completa
Comprueba la firma y luego desbloquea el contenido o entrega el artículo.
Completa tu acción solo con confirmation.completed — nunca con la respuesta de /deduct. En ese punto el cobro todavía no es definitivo.
POST /v1/add abona Vito a un usuario desde tu propio saldo, para recompensas o reembolsos. Los mismos campos que /deduct salvo guildId y product.
A diferencia de un cobro, un abono no tiene paso de confirmación: se liquida al instante. Requiere el scope credit:create y saldo suficiente, o la llamada devuelve 402 VITO_INSUFFICIENT_OWNER_FUNDS.
Cada cobro se te liquida menos la comisión de la plataforma, con el mismo esquema que las transferencias de Vito dentro de la app y según tu nivel de Membresía:
Tu Membresía
Comisión
Normal, Silver, Gold
7 %
Platinum
6 %
Diamond
5 %
Los importes de 5 Vito o menos no tienen comisión, y los abonos vía /v1/add nunca la tienen.
Añade una o varias URL de callback https en la pestaña de ajustes. Vito entrega un POST firmado cada vez que una confirmación alcanza un estado final.
Los webhooks solo se envían si tu proyecto tiene a la vez una URL de callback y un secreto de firma. Revela el secreto (whsec_…) una única vez desde la pestaña de claves API.
Acepta la entrega si coincide cualquiera de los h1. Un verificador que solo lea el primero rechazará todos los webhooks hasta haber desplegado el secreto nuevo, que es justo lo que el solapamiento de 24 horas pretende evitar.
Recalcula el HMAC sobre <ts>:<rawBody> con tu secreto de firma y compara en tiempo 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) ); });}
Verifica contra el cuerpo en bruto, antes de cualquier parseo de JSON o middleware que lo reescriba.
También existe un límite por IP igual a la mitad de tu cuota por minuto, con un mínimo de 30.
Bajo carga, los endpoints de escritura fallan en cerrado: se rechaza el cobro en lugar de arriesgar un doble gasto. Los de lectura fallan en abierto. Trata una escritura rechazada como «no ocurrió» y reinténtala.
Envía una cabecera Idempotency-Key para deduplicar los reintentos con seguridad.