Vito API 文档

最后更新:2026 年 6 月 26 日
01

概览

Vito API 让你的应用可以在 Discord 内处理用户的 Vito 余额——读取余额、从中扣款或将其入账退回。用户在任何 Vito 移动之前都会在 vetox.io 上用 PIN 确认每笔扣款,你的应用会收到结果通知。Vito 是 Vetox 生态系统内的虚拟货币;使用 API 来构建基于 Vito 的体验——解锁内容、功能、权益或奖励。不会发生任何使用真实货币的买卖。

这是一个使用密钥进行身份验证的 REST API。所有端点都返回 JSON,并在 /v1 下进行版本管理。

基础 URLhttps://api.vetox.io/public/vito/v1
02

余额与结算

你的 API 密钥与你的 Vetox 账户绑定。每笔扣款都结算给你:当你的应用从用户处扣除 Vito 时,该 Vito 会记入你自己的余额(扣除平台费后),绝不会被销毁。当你的应用为用户增加 Vito 时,则由你的余额出资。

Vito 始终在余额之间流动——绝不从真实货币流入或流出。用户被收取你所请求的全额;你收到扣除平台费后的金额;入账与奖励通过 /add 从你的余额中出资。

平台费——与应用内 Vito 转账相同的费率表,按你的会员等级:7%(Silver / Gold)、6%(Platinum)、5%(Diamond)。5 Vito 及以下的扣款免费。通过 /add 的入账从不收取费用。
03

要求

Vito API 访问按项目授予。在调用之前,你需要:

  • 有效的用户会员资格(Silver 或更高等级)。如果所有者的会员资格到期,API 会自动冻结,重新订阅后恢复。
  • 已批准的开发者申请。从此页面提交,Vetox 团队会手动审核。
  • 已接受 API 开发者条款 — 在提交开发者申请时确认。
  • 你的项目所需的权限范围。由 Vetox 团队根据你的申请授予。

可用权限范围

balance:read
读取用户的 Vito 余额。
deduct:create
创建扣款——从用户的 Vito 余额收费。
credit:create
为用户增加 Vito——用你的余额为奖励或入账出资。
transfer:create
在两个用户之间创建转账。
transactions:read
列出并读取你项目的交易。
04

身份验证

使用你的密钥 API 密钥在 Authorization 标头中作为 Bearer 令牌对每个请求进行身份验证。密钥带有 vito_live_ 前缀。

http
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx

两个可选层进一步保护你的项目:

  • IP 允许列表——将请求限制到特定的服务器 IP(在设置标签页配置)。
  • 速率限制——随你的会员等级扩展的每个项目的请求上限。
05

API 密钥——存储、轮换和安全

你的密钥在申请获批时生成。它只显示一次——从 API 密钥标签页显示并立即保存。

Vetox 从不以可读形式存储你的密钥(仅加盐哈希)。将其作为服务器端密钥保存——环境变量或密钥管理器——绝不要放在客户端代码、git 仓库或浏览器中。

在 API 密钥标签页轮换。旧密钥在停止前会继续工作 24 小时的宽限期,让你无停机部署新密钥。

将密钥视为密码——任何持有它的人都可以向你的用户收费。如果泄露,请立即轮换并检查最近的交易。
06

扣款确认流程

每笔扣款都会经过用户用 PIN 批准的确认——仅凭你的密钥永远不会转移 Vito:

  1. 1你的应用使用用户、金额、来源 guildId 和项目详情调用 POST /deduct。
  2. 2Vito 创建一个待确认(有效 10 分钟)并返回 confirmUrl。如果提供了 guildId,还会向用户发送一条包含详情和确认页面按钮的私信。
  3. 3用户在 vetox.io 上打开 confirmUrl,并用钱包 PIN 批准扣款。
  4. 4Vito 扣除 Vito、记录交易,并——如果你配置了 Webhook——向你的回调 URL 发送已签名的 confirmation.completed 事件。
  5. 5你的应用验证签名并完成自己的操作(例如,解锁内容)。
PIN 仅在 vetox.io 上输入——绝不在你的应用或 Discord 内输入。如果用户从未确认,请求会在 10 分钟后过期,你会收到 confirmation.expired 事件。
07

请求参数(/deduct)

POST/v1/deduct

扣款端点接受以下正文字段:

discordId
用户的 Discord 用户 ID——17 到 20 位的 snowflake。必填。
amount
要收取的 Vito 数量——正整数。必填。
guildId
扣款来源的 Discord 服务器(snowflake)。必填——每笔扣款都必须来自特定服务器,这也启用了向用户发送的私信。
reason
向用户显示并在 Webhook 中重复的简短可读描述(≤ 256 个字符)。
merchantRef
你自己的引用字符串(≤ 128 个字符)。用它将 Webhook 与你的记录匹配。
product
项目描述符——{ type, name, description?, imageUrl? }。在确认页面和私信中向用户显示,并在每个 Webhook 中重复。
product.imageUrl
可选的商品图片——必须是 https:// URL。
metadata
最多 10 个字符串键/值对,在 Webhook 中原样传递。

* 必填字段。

请求示例
http
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" }
}
响应示例
json
{
  "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
}
08

增加 Vito——入账(/add)

POST/v1/add

POST /add 从你自己的余额为用户记入 Vito——用于奖励或入账。它接受与 /deduct 相同的字段,但不含 guildId 和 product。与扣款不同,它是即时的,且无需用户 PIN——你的 API 密钥即你的授权——因此响应已反映已完成的转账。用户收到全额;不收取任何平台费。

需要 credit:create 权限,且你自己的余额中有足够的 Vito——否则调用返回 402 INSUFFICIENT_OWNER_FUNDS。
请求示例
http
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" }
}
响应示例
json
{
  "success": true,
  "data": {
    "transactionId": "txn_91b2",
    "toDiscordId": "123456789012345678",
    "amount": 250,
    "ownerBalance": 48250,
    "recipientBalance": 1250,
    "status": "completed"
  },
  "requestId": "req_def456",
  "timestamp": 1735689300000
}
09

Webhook 与签名验证

在设置标签页添加一个或多个 https 回调 URL。每当确认达到最终状态时,Vito 都会向每个已配置的 URL 投递已签名的 POST,并以指数退避重试最多 5 次。

只有当你的项目同时拥有已配置的回调 URL 和签名密钥时,Webhook 才会触发。从 API 密钥标签页显示一次你的签名密钥(whsec_…)。

验证签名

每次投递都带有 ts=<unix>;h1=<hex> 形式的 X-Vito-Signature 标头。用你的签名密钥对 "<ts>:<rawBody>" 重新计算 HMAC,并以恒定时间比较。始终在解析 JSON 之前对原始请求正文进行验证。

http
signature = HMAC_SHA256(signingSecret, "<ts>:<rawBody>")
header    = X-Vito-Signature: ts=<unix-seconds>;h1=<hex-signature>
参考验证器(Node / Express):
javascript
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
});
10

Webhook 事件与负载

Vito 发送四种事件类型之一。它们共享相同的负载结构;data.status 反映结果。

confirmation.completed
用户已确认且 Vito 已扣除。完成你的操作。携带 transactionId。
confirmation.failed
扣款无法完成(例如余额不足或内部错误)。携带 failureReason。
confirmation.expired
用户未在 10 分钟内确认。没有 Vito 转移。
confirmation.cancelled
用户取消了请求。没有 Vito 转移。
credit.completed
由所有者出资的 /add 记账已结算。包含收款人和 transactionId,并立即触发——没有确认步骤。
负载示例(confirmation.completed)
http
POST 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
  }
}
11

错误代码

错误返回非 2xx 状态,JSON 正文携带顶层 error.code。常见情况:

400VITO_VALIDATION_ERROR
请求字段缺失或格式错误(例如无效的 guildId)。
401invalid key
缺失、格式错误或未知的 API 密钥。
403OWNER_MEMBERSHIP_INACTIVE
项目所有者的会员资格已到期——API 被冻结,直到其重新订阅。
403scope / IP
密钥缺少所需的权限范围,或请求 IP 不在允许列表中。
404not found
用户、交易或资源不存在。
402insufficient balance
用户没有足够的 Vito 用于扣款。
402INSUFFICIENT_OWNER_FUNDS
你自己(所有者)的余额过低,无法为 /add 记账出资。
429rate limited
你超出了项目的速率限制——请退避后重试。
错误响应结构
json
{
  "success": false,
  "error": { "type": "Bad Request", "message": "guildId must be a valid Discord server ID", "code": "VITO_VALIDATION_ERROR" },
  "requestId": "req_abc123",
  "timestamp": 1735689300000
}
12

集成示例

创建扣款请求:

POST/v1/deduct
bash
curl -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" }
  }'

查询用户余额:

GET/v1/balance/:discordId
bash
curl https://api.vetox.io/public/vito/v1/balance/123456789012345678 \
  -H "Authorization: Bearer $VITO_API_KEY"
13

使用演示集成测试

提供了一个独立的演示集成,可在不编写代码的情况下端到端地演练完整流程。它演示请求 → 确认 → PIN → Webhook → 完成。

  1. 1打开演示集成文件夹,将你的 Vito API 密钥、一个 Discord 机器人令牌和你的测试服务器(guild)ID 添加到 config.json。
  2. 2运行 npm install && npm start,然后打开 http://localhost:4000。
  3. 3按 Discord ID 查找用户,选择一个项目并开始扣款——演示会调用 /deduct 并显示 confirmUrl。
  4. 4在 vetox.io 上批准 PIN;演示会检测完成(已配置则通过 Webhook,否则通过轮询)并完成示例操作。
接收 Webhook 需要公开的 https 回调和签名密钥;没有它们,演示会回退到轮询你的交易,因此无需配置即可运行。
14

最佳实践、限制与安全

安全

  • 仅在服务器端保存你的 API 密钥和签名密钥;如有任何一个泄露,请立即轮换。
  • 始终对原始正文验证 Webhook 签名,并拒绝超过约 5 分钟的投递(防重放)。
  • 仅在 confirmation.completed 时完成你的操作——绝不要在 /deduct 响应时完成(那时扣款尚未最终确定)。
  • 使用 IP 允许列表,并仅请求你真正需要的权限范围。

限制

  • 确认在 10 分钟后过期;将未确认的请求视为已放弃。
  • 速率限制按项目计,并随所有者的会员等级扩展。
  • amount 必须是正整数;metadata 最多 10 个键。
Idempotency-Key 去重重试Webhook 以退避重试 5 次快速返回 2xx,异步履行