Resumen
La API de Vito permite que tu aplicación trabaje con el saldo de Vito de un usuario desde dentro de Discord: leerlo, cobrarlo o abonarlo. El usuario confirma cada cobro con su PIN en vetox.io antes de que se mueva ningún Vito, y tu app recibe una notificación del resultado. Vito es la moneda virtual interna del ecosistema de Vetox; usa la API para crear experiencias basadas en Vito: desbloquear contenido, funciones, ventajas o recompensas. No se realiza ninguna compra ni venta con dinero real.
Es una API REST autenticada con una clave secreta. Todos los endpoints devuelven JSON y están versionados bajo /v1.
https://api.vetox.io/public/vito/v1Saldos y liquidación
Tu clave de API está vinculada a tu cuenta de Vetox. Cada cargo se liquida a tu favor: cuando tu aplicación deduce Vito de un usuario, ese Vito se abona a tu propio saldo (menos la comisión de la plataforma), nunca se destruye. Cuando tu aplicación añade Vito a un usuario, se financia desde tu saldo.
El Vito siempre se mueve entre saldos, nunca desde o hacia dinero real. Al usuario se le cobra el importe completo que solicitas; tú recibes el importe tras la comisión de la plataforma; los abonos y recompensas se financian desde tu saldo mediante /add.
Requisitos
El acceso a la API de Vito se concede por proyecto. Antes de poder llamarla necesitas:
- Una membresía de usuario activa (nivel Silver o superior). La API se congela automáticamente si la membresía del propietario caduca, y se restaura cuando vuelve a suscribirse.
- Una solicitud de desarrollador aprobada. Envía una desde esta página; el equipo de Vetox la revisa manualmente.
- Términos para desarrolladores de la API aceptados: se confirman al enviar tu solicitud de desarrollador.
- Los permisos que necesita tu proyecto. Los concede el equipo de Vetox según tu solicitud.
Permisos disponibles
balance:readdeduct:createcredit:createtransfer:createtransactions:readAutenticación
Autentica cada solicitud con tu clave API secreta en la cabecera Authorization como token Bearer. Las claves llevan el prefijo vito_live_.
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxxDos capas opcionales protegen aún más tu proyecto:
- Lista de IP permitidas: restringe las solicitudes a IPs de servidor específicas (configúralo en la pestaña Ajustes).
- Límites de tasa: topes de solicitudes por proyecto que escalan con tu nivel de membresía.
Claves API: almacenamiento, rotación y seguridad
Tu clave se genera cuando se aprueba tu solicitud. Se muestra una sola vez: revélala desde la pestaña Claves API y guárdala de inmediato.
Vetox nunca almacena tu clave en forma legible (solo un hash con sal). Guárdala como un secreto del servidor —una variable de entorno o un gestor de secretos— nunca en código del cliente, un repositorio git o un navegador.
Rótala desde la pestaña Claves API. La clave anterior sigue funcionando durante un periodo de gracia de 24 horas para que despliegues la nueva sin tiempo de inactividad, y luego deja de hacerlo.
Flujo de confirmación del cobro
Cada cobro pasa por una confirmación que el usuario aprueba con su PIN: tu clave por sí sola nunca mueve Vito:
- 1Tu app llama a POST /deduct con el usuario, el importe, el guildId de origen y los detalles del elemento.
- 2Vito crea una confirmación pendiente (válida 10 minutos) y devuelve una confirmUrl. Si se indica un guildId, también se envía al usuario un DM con los detalles y un botón a la página de confirmación.
- 3El usuario abre la confirmUrl en vetox.io y aprueba el cargo con el PIN de su monedero.
- 4Vito deduce los Vito, registra la transacción y —si tienes un webhook configurado— envía un evento confirmation.completed firmado a tu URL de callback.
- 5Tu app verifica la firma y completa su acción (por ejemplo, desbloquea el contenido).
Parámetros de la solicitud (/deduct)
/v1/deductEl endpoint de deducción acepta los siguientes campos del cuerpo:
discordIdamountguildIdreasonmerchantRefproductproduct.imageUrlmetadata* campo obligatorio.
POST https://api.vetox.io/public/vito/v1/deduct
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
Idempotency-Key: req_9a1f2b # optional, dedupes retries for 24h
{
"discordId": "123456789012345678",
"amount": 500,
"guildId": "1470389021162209280",
"reason": "Unlock premium theme",
"merchantRef": "ref_1042",
"product": {
"type": "feature",
"name": "Premium theme",
"description": "Unlocks the premium theme pack",
"imageUrl": "https://cdn.example.com/theme.png"
},
"metadata": { "ref": "1042" }
}{
"success": true,
"data": {
"confirmationId": "f3c9...e1",
"confirmUrl": "https://vetox.io/confirm/vito/f3c9...e1",
"amount": 500,
"expiresAt": 1735689600000,
"status": "pending"
},
"requestId": "req_abc123",
"timestamp": 1735689300000
}Añadir Vito — abonos (/add)
/v1/addPOST /add abona Vito a un usuario desde tu propio saldo — para recompensas o abonos. Acepta los mismos campos que /deduct salvo guildId y product. A diferencia de un cargo, es inmediato y no requiere el PIN del usuario — tu clave de API es tu autoridad — por lo que la respuesta ya refleja la transferencia completada. El usuario recibe el importe completo; no se aplica comisión de plataforma.
POST https://api.vetox.io/public/vito/v1/add
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
Idempotency-Key: credit_7f3a # optional, dedupes retries for 24h
{
"discordId": "123456789012345678",
"amount": 250,
"reason": "Event reward",
"merchantRef": "reward_1042",
"metadata": { "ref": "1042" }
}{
"success": true,
"data": {
"transactionId": "txn_91b2",
"toDiscordId": "123456789012345678",
"amount": 250,
"ownerBalance": 48250,
"recipientBalance": 1250,
"status": "completed"
},
"requestId": "req_def456",
"timestamp": 1735689300000
}Webhooks y verificación de firma
Añade una o más URLs de callback https en la pestaña Ajustes. Vito entrega un POST firmado a cada URL configurada cada vez que una confirmación alcanza un estado final, reintentando hasta 5 veces con retroceso exponencial.
Verificar la firma
Cada entrega lleva una cabecera X-Vito-Signature con la forma ts=<unix>;h1=<hex>. Recalcula el HMAC sobre "<ts>:<rawBody>" con tu secreto de firma y compara en tiempo constante. Verifica siempre contra el cuerpo de la solicitud sin procesar, antes de analizar el JSON.
signature = HMAC_SHA256(signingSecret, "<ts>:<rawBody>")
header = X-Vito-Signature: ts=<unix-seconds>;h1=<hex-signature>import crypto from 'crypto';
import express from 'express';
const app = express();
// IMPORTANT: verify against the RAW body, before JSON parsing.
app.post('/webhooks/vito', express.raw({ type: '*/*' }), (req, res) => {
const sig = req.header('X-Vito-Signature') || ''; // "ts=<unix>;h1=<hmac>[;h1=<hmac>]"
const pairs = sig.split(';').map(p => p.split('='));
const ts = pairs.find(([k]) => k === 'ts')?.[1];
const sigs = pairs.filter(([k]) => k === 'h1').map(([, v]) => v);
const rawBody = req.body.toString('utf8');
const expected = crypto
.createHmac('sha256', process.env.VITO_SIGNING_SECRET) // whsec_...
.update(`${ts}:${rawBody}`)
.digest('hex');
const expectedBuf = Buffer.from(expected, 'hex');
// During a 24h secret rotation the header carries TWO h1= values — accept ANY.
// Length-guard FIRST: crypto.timingSafeEqual throws on a length mismatch.
const ok = sigs.some(h => {
const got = Buffer.from(h, 'hex');
return (
got.length === expectedBuf.length &&
crypto.timingSafeEqual(got, expectedBuf)
);
});
// Reject anything older than ~5 minutes to stop replays.
const fresh = ts && Math.abs(Date.now() / 1000 - Number(ts)) < 300;
if (!ok || !fresh) return res.status(401).end();
const event = JSON.parse(rawBody);
if (event.eventType === 'confirmation.completed') {
grantPremiumTheme(event.data.merchantRef, event.data.fromDiscordId);
}
res.status(200).end(); // ack fast; run your action async if slow
});Eventos de webhook y cargas útiles
Vito envía uno de cuatro tipos de evento. Todos comparten la misma forma de carga útil; data.status refleja el resultado.
confirmation.completedconfirmation.failedconfirmation.expiredconfirmation.cancelledcredit.completedPOST https://your-app.com/webhooks/vito
X-Vito-Signature: ts=1735689600;h1=4f8b2c...e9
X-Vito-Event-Id: evt_3a2b1c
X-Vito-Event-Type: confirmation.completed
Content-Type: application/json
{
"eventId": "evt_3a2b1c",
"eventType": "confirmation.completed",
"timestamp": 1735689600000,
"data": {
"confirmationId": "f3c9...e1",
"type": "deduct",
"fromDiscordId": "123456789012345678",
"toDiscordId": null,
"amount": 500,
"reason": "Unlock premium theme",
"merchantRef": "ref_1042",
"product": { "type": "feature", "name": "Premium theme" },
"status": "completed",
"transactionId": "txn_77a2",
"metadata": { "ref": "1042" },
"failureReason": null
}
}Códigos de error
Los errores devuelven un estado no 2xx con un cuerpo JSON que lleva un error.code de nivel superior. Casos comunes:
VITO_VALIDATION_ERRORinvalid keyOWNER_MEMBERSHIP_INACTIVEscope / IPnot foundinsufficient balanceINSUFFICIENT_OWNER_FUNDSrate limited{
"success": false,
"error": { "type": "Bad Request", "message": "guildId must be a valid Discord server ID", "code": "VITO_VALIDATION_ERROR" },
"requestId": "req_abc123",
"timestamp": 1735689300000
}Ejemplos de integración
Crear una solicitud de cobro:
/v1/deductcurl -X POST https://api.vetox.io/public/vito/v1/deduct \
-H "Authorization: Bearer $VITO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"discordId": "123456789012345678",
"amount": 500,
"guildId": "1470389021162209280",
"merchantRef": "ref_1042",
"product": { "type": "feature", "name": "Premium theme" }
}'Consultar el saldo de un usuario:
/v1/balance/:discordIdcurl https://api.vetox.io/public/vito/v1/balance/123456789012345678 \
-H "Authorization: Bearer $VITO_API_KEY"Pruebas con la integración de demostración
Se proporciona una integración de demostración independiente para ejercitar el flujo completo de principio a fin sin escribir código. Recorre solicitud → confirmación → PIN → webhook → finalización.
- 1Abre la carpeta de la integración de demostración y añade tu clave de la API de Vito, un token de bot de Discord y el ID de tu servidor (guild) de prueba a config.json.
- 2Ejecuta npm install && npm start y abre http://localhost:4000.
- 3Busca un usuario por su ID de Discord, elige un elemento e inicia un cobro: la demo llama a /deduct y muestra la confirmUrl.
- 4Aprueba el PIN en vetox.io; la demo detecta la finalización (por webhook si está configurado, si no por sondeo) y completa la acción de ejemplo.
Buenas prácticas, límites y seguridad
Seguridad
- Mantén tu clave API y el secreto de firma solo en el servidor; rota cualquiera de ellos de inmediato si se filtra.
- Verifica siempre la firma del webhook contra el cuerpo sin procesar y rechaza las entregas de más de ~5 minutos (protección contra reenvíos).
- Completa tu acción solo con confirmation.completed, nunca con la respuesta de /deduct (en ese punto el cobro aún no es definitivo).
- Usa la lista de IP permitidas y solicita solo los permisos que realmente necesitas.
Límites
- Las confirmaciones caducan a los 10 minutos; trata las solicitudes no confirmadas como abandonadas.
- Los límites de tasa son por proyecto y escalan con el nivel de membresía del propietario.
- amount debe ser un entero positivo; metadata está limitado a 10 claves.