概览
Vito API 让你的应用可以在 Discord 内处理用户的 Vito 余额——读取余额、从中扣款或将其入账退回。用户在任何 Vito 移动之前都会在 vetox.io 上用 PIN 确认每笔扣款,你的应用会收到结果通知。Vito 是 Vetox 生态系统内的虚拟货币;使用 API 来构建基于 Vito 的体验——解锁内容、功能、权益或奖励。不会发生任何使用真实货币的买卖。
这是一个使用密钥进行身份验证的 REST API。所有端点都返回 JSON,并在 /v1 下进行版本管理。
https://api.vetox.io/public/vito/v1余额与结算
你的 API 密钥与你的 Vetox 账户绑定。每笔扣款都结算给你:当你的应用从用户处扣除 Vito 时,该 Vito 会记入你自己的余额(扣除平台费后),绝不会被销毁。当你的应用为用户增加 Vito 时,则由你的余额出资。
Vito 始终在余额之间流动——绝不从真实货币流入或流出。用户被收取你所请求的全额;你收到扣除平台费后的金额;入账与奖励通过 /add 从你的余额中出资。
要求
Vito API 访问按项目授予。在调用之前,你需要:
- 有效的用户会员资格(Silver 或更高等级)。如果所有者的会员资格到期,API 会自动冻结,重新订阅后恢复。
- 已批准的开发者申请。从此页面提交,Vetox 团队会手动审核。
- 已接受 API 开发者条款 — 在提交开发者申请时确认。
- 你的项目所需的权限范围。由 Vetox 团队根据你的申请授予。
可用权限范围
balance:readdeduct:createcredit:createtransfer:createtransactions:read身份验证
使用你的密钥 API 密钥在 Authorization 标头中作为 Bearer 令牌对每个请求进行身份验证。密钥带有 vito_live_ 前缀。
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx两个可选层进一步保护你的项目:
- IP 允许列表——将请求限制到特定的服务器 IP(在设置标签页配置)。
- 速率限制——随你的会员等级扩展的每个项目的请求上限。
API 密钥——存储、轮换和安全
你的密钥在申请获批时生成。它只显示一次——从 API 密钥标签页显示并立即保存。
Vetox 从不以可读形式存储你的密钥(仅加盐哈希)。将其作为服务器端密钥保存——环境变量或密钥管理器——绝不要放在客户端代码、git 仓库或浏览器中。
在 API 密钥标签页轮换。旧密钥在停止前会继续工作 24 小时的宽限期,让你无停机部署新密钥。
扣款确认流程
每笔扣款都会经过用户用 PIN 批准的确认——仅凭你的密钥永远不会转移 Vito:
- 1你的应用使用用户、金额、来源 guildId 和项目详情调用 POST /deduct。
- 2Vito 创建一个待确认(有效 10 分钟)并返回 confirmUrl。如果提供了 guildId,还会向用户发送一条包含详情和确认页面按钮的私信。
- 3用户在 vetox.io 上打开 confirmUrl,并用钱包 PIN 批准扣款。
- 4Vito 扣除 Vito、记录交易,并——如果你配置了 Webhook——向你的回调 URL 发送已签名的 confirmation.completed 事件。
- 5你的应用验证签名并完成自己的操作(例如,解锁内容)。
请求参数(/deduct)
/v1/deduct扣款端点接受以下正文字段:
discordIdamountguildIdreasonmerchantRefproductproduct.imageUrlmetadata* 必填字段。
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
}增加 Vito——入账(/add)
/v1/addPOST /add 从你自己的余额为用户记入 Vito——用于奖励或入账。它接受与 /deduct 相同的字段,但不含 guildId 和 product。与扣款不同,它是即时的,且无需用户 PIN——你的 API 密钥即你的授权——因此响应已反映已完成的转账。用户收到全额;不收取任何平台费。
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
}Webhook 与签名验证
在设置标签页添加一个或多个 https 回调 URL。每当确认达到最终状态时,Vito 都会向每个已配置的 URL 投递已签名的 POST,并以指数退避重试最多 5 次。
验证签名
每次投递都带有 ts=<unix>;h1=<hex> 形式的 X-Vito-Signature 标头。用你的签名密钥对 "<ts>:<rawBody>" 重新计算 HMAC,并以恒定时间比较。始终在解析 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
});Webhook 事件与负载
Vito 发送四种事件类型之一。它们共享相同的负载结构;data.status 反映结果。
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
}
}错误代码
错误返回非 2xx 状态,JSON 正文携带顶层 error.code。常见情况:
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
}集成示例
创建扣款请求:
/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" }
}'查询用户余额:
/v1/balance/:discordIdcurl https://api.vetox.io/public/vito/v1/balance/123456789012345678 \
-H "Authorization: Bearer $VITO_API_KEY"使用演示集成测试
提供了一个独立的演示集成,可在不编写代码的情况下端到端地演练完整流程。它演示请求 → 确认 → PIN → Webhook → 完成。
- 1打开演示集成文件夹,将你的 Vito API 密钥、一个 Discord 机器人令牌和你的测试服务器(guild)ID 添加到 config.json。
- 2运行 npm install && npm start,然后打开 http://localhost:4000。
- 3按 Discord ID 查找用户,选择一个项目并开始扣款——演示会调用 /deduct 并显示 confirmUrl。
- 4在 vetox.io 上批准 PIN;演示会检测完成(已配置则通过 Webhook,否则通过轮询)并完成示例操作。
最佳实践、限制与安全
安全
- 仅在服务器端保存你的 API 密钥和签名密钥;如有任何一个泄露,请立即轮换。
- 始终对原始正文验证 Webhook 签名,并拒绝超过约 5 分钟的投递(防重放)。
- 仅在 confirmation.completed 时完成你的操作——绝不要在 /deduct 响应时完成(那时扣款尚未最终确定)。
- 使用 IP 允许列表,并仅请求你真正需要的权限范围。
限制
- 确认在 10 分钟后过期;将未确认的请求视为已放弃。
- 速率限制按项目计,并随所有者的会员等级扩展。
- amount 必须是正整数;metadata 最多 10 个键。