Tài liệu Vito API

Cập nhật lần cuối: 26 tháng 6 năm 2026
01

Tổng quan

Vito API cho phép ứng dụng của bạn làm việc với số dư Vito của người dùng ngay trong Discord — đọc số dư, tính phí vào số dư hoặc ghi có trở lại. Người dùng xác nhận mỗi khoản thu bằng PIN trên vetox.io trước khi bất kỳ Vito nào di chuyển, và ứng dụng của bạn được thông báo kết quả. Vito là tiền ảo nội bộ trong hệ sinh thái Vetox; dùng API để xây dựng trải nghiệm dựa trên Vito — mở khóa nội dung, tính năng, đặc quyền hoặc phần thưởng. Không có hoạt động mua hay bán bằng tiền thật nào diễn ra.

Đây là REST API được xác thực bằng một khóa bí mật. Tất cả endpoint đều trả về JSON và được phân phiên bản dưới /v1.

URL gốchttps://api.vetox.io/public/vito/v1
02

Số dư & quyết toán

Khóa API của bạn gắn với tài khoản Vetox của bạn. Mọi khoản thu đều được quyết toán cho bạn: khi ứng dụng của bạn trừ Vito của người dùng, số Vito đó được ghi có vào số dư của chính bạn (sau khi trừ phí nền tảng), không bao giờ bị hủy. Khi ứng dụng của bạn cộng Vito cho người dùng, nó được tài trợ từ số dư của bạn.

Vito luôn di chuyển giữa các số dư — không bao giờ từ hay đến tiền thật. Người dùng bị tính đủ số tiền bạn yêu cầu; bạn nhận số tiền sau phí nền tảng; các khoản ghi có và phần thưởng được tài trợ từ số dư của bạn qua /add.

Phí nền tảng — cùng biểu phí với chuyển Vito trong ứng dụng, dựa trên cấp thành viên của bạn: 7% (Silver / Gold), 6% (Platinum), 5% (Diamond). Các khoản thu từ 5 Vito trở xuống được miễn phí. Khoản ghi có qua /add không bao giờ bị tính phí.
03

Yêu cầu

Quyền truy cập Vito API được cấp theo từng dự án. Trước khi gọi, bạn cần:

  • Tư cách thành viên người dùng đang hoạt động (cấp Silver trở lên). API tự động đóng băng nếu tư cách của chủ sở hữu hết hạn và được khôi phục khi họ đăng ký lại.
  • Một đơn đăng ký nhà phát triển đã được phê duyệt. Gửi từ trang này; đội ngũ Vetox xét duyệt thủ công.
  • Đã chấp nhận Điều khoản Nhà phát triển API — được xác nhận khi bạn gửi đơn đăng ký nhà phát triển.
  • Các phạm vi mà dự án của bạn cần. Đội ngũ Vetox cấp dựa trên đơn của bạn.

Các phạm vi khả dụng

balance:read
Đọc số dư Vito của người dùng.
deduct:create
Tạo một khoản trừ — tính phí vào số dư Vito của người dùng.
credit:create
Cộng Vito cho người dùng — tài trợ phần thưởng hoặc khoản ghi có từ số dư của bạn.
transfer:create
Tạo một chuyển khoản giữa hai người dùng.
transactions:read
Liệt kê và đọc các giao dịch của dự án bạn.
04

Xác thực

Xác thực mỗi yêu cầu bằng khóa API bí mật trong tiêu đề Authorization dưới dạng token Bearer. Khóa có tiền tố vito_live_.

http
Authorization: Bearer vito_live_xxxxxxxxxxxxxxxx

Hai lớp tùy chọn bảo vệ dự án của bạn thêm:

  • Danh sách IP cho phép — giới hạn yêu cầu ở các IP máy chủ cụ thể (cấu hình ở tab Cài đặt).
  • Giới hạn tốc độ — trần yêu cầu theo dự án, tăng theo cấp thành viên của bạn.
05

Khóa API — lưu trữ, xoay và bảo mật

Khóa của bạn được tạo khi đơn được phê duyệt. Nó chỉ hiển thị một lần — hiện nó ở tab Khóa API và lưu ngay.

Vetox không bao giờ lưu khóa của bạn ở dạng đọc được (chỉ một hash có muối). Hãy giữ nó như một bí mật phía máy chủ — một biến môi trường hoặc trình quản lý bí mật — không bao giờ trong mã client, kho git hay trình duyệt.

Xoay ở tab Khóa API. Khóa cũ vẫn hoạt động trong thời gian ân hạn 24 giờ để bạn triển khai khóa mới không gián đoạn, rồi ngừng.

Hãy coi khóa như mật khẩu — bất kỳ ai có nó đều có thể tính phí người dùng của bạn. Nếu rò rỉ, xoay ngay và xem lại các giao dịch gần đây.
06

Luồng xác nhận khoản thu

Mỗi khoản thu đều đi qua xác nhận mà người dùng phê duyệt bằng PIN — chỉ riêng khóa của bạn không bao giờ chuyển Vito:

  1. 1Ứng dụng của bạn gọi POST /deduct với người dùng, số tiền, guildId nguồn và chi tiết mục.
  2. 2Vito tạo một xác nhận đang chờ (hợp lệ 10 phút) và trả về confirmUrl. Nếu cung cấp guildId, người dùng cũng nhận được tin nhắn riêng kèm chi tiết và nút đến trang xác nhận.
  3. 3Người dùng mở confirmUrl trên vetox.io và phê duyệt khoản trừ bằng PIN ví của họ.
  4. 4Vito trừ Vito, ghi lại giao dịch và — nếu bạn đã cấu hình webhook — gửi sự kiện confirmation.completed đã ký đến URL callback của bạn.
  5. 5Ứng dụng của bạn xác minh chữ ký và hoàn tất hành động của mình (ví dụ, mở khóa nội dung).
PIN chỉ được nhập trên vetox.io — không bao giờ trong ứng dụng của bạn hay trong Discord. Nếu người dùng không xác nhận, yêu cầu hết hạn sau 10 phút và bạn nhận sự kiện confirmation.expired.
07

Tham số yêu cầu (/deduct)

POST/v1/deduct

Endpoint trừ chấp nhận các trường thân sau:

discordId
ID người dùng Discord của người dùng — một snowflake 17–20 chữ số. Bắt buộc.
amount
Lượng Vito cần tính phí — một số nguyên dương. Bắt buộc.
guildId
Máy chủ Discord nơi khoản thu phát sinh (snowflake). Bắt buộc — mỗi khoản thu phải đến từ một máy chủ cụ thể, điều này cũng bật tin nhắn riêng cho người dùng.
reason
Một mô tả ngắn dễ đọc (≤ 256 ký tự), hiển thị cho người dùng và lặp lại trong webhook.
merchantRef
Chuỗi tham chiếu của riêng bạn (≤ 128 ký tự). Dùng nó để khớp webhook với hồ sơ của bạn.
product
Bộ mô tả mục — { type, name, description?, imageUrl? }. Hiển thị cho người dùng trên trang xác nhận và tin nhắn riêng, và lặp lại trong mỗi webhook.
product.imageUrl
Ảnh sản phẩm tùy chọn — phải là URL https://.
metadata
Tối đa 10 cặp khóa/giá trị chuỗi, truyền nguyên trong webhook.

* trường bắt buộc.

Yêu cầu ví dụ
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" }
}
Phản hồi ví dụ
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

Cộng Vito — khoản ghi có (/add)

POST/v1/add

POST /add ghi có Vito cho người dùng từ số dư của chính bạn — cho phần thưởng hoặc khoản ghi có. Nó nhận cùng các trường như /deduct ngoại trừ guildId và product. Khác với một khoản thu, nó tức thì và không cần PIN của người dùng — khóa API của bạn là thẩm quyền của bạn — nên phản hồi đã phản ánh giao dịch hoàn tất. Người dùng nhận đủ số tiền; không áp dụng phí nền tảng.

Yêu cầu phạm vi credit:create và đủ Vito trong số dư của chính bạn — nếu không lệnh gọi trả về 402 INSUFFICIENT_OWNER_FUNDS.
Yêu cầu ví dụ
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" }
}
Phản hồi ví dụ
json
{
  "success": true,
  "data": {
    "transactionId": "txn_91b2",
    "toDiscordId": "123456789012345678",
    "amount": 250,
    "ownerBalance": 48250,
    "recipientBalance": 1250,
    "status": "completed"
  },
  "requestId": "req_def456",
  "timestamp": 1735689300000
}
09

Webhook và xác minh chữ ký

Thêm một hoặc nhiều URL callback https ở tab Cài đặt. Vito gửi một POST đã ký đến mỗi URL đã cấu hình mỗi khi một xác nhận đạt trạng thái cuối, thử lại tối đa 5 lần với backoff lũy thừa.

Webhook chỉ kích hoạt khi dự án của bạn có ĐỒNG THỜI một URL callback đã cấu hình VÀ một bí mật ký. Hiện bí mật ký của bạn (whsec_…) một lần ở tab Khóa API.

Xác minh chữ ký

Mỗi lượt gửi mang một tiêu đề X-Vito-Signature dạng ts=<unix>;h1=<hex>. Tính lại HMAC trên "<ts>:<rawBody>" bằng bí mật ký của bạn và so sánh trong thời gian không đổi. Luôn xác minh trên thân yêu cầu thô, trước khi phân tích JSON.

http
signature = HMAC_SHA256(signingSecret, "<ts>:<rawBody>")
header    = X-Vito-Signature: ts=<unix-seconds>;h1=<hex-signature>
Trình xác minh tham chiếu (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

Sự kiện webhook và payload

Vito gửi một trong bốn loại sự kiện. Tất cả đều chung một dạng payload; data.status phản ánh kết quả.

confirmation.completed
Người dùng đã xác nhận và Vito đã được trừ. Hãy hoàn tất hành động của bạn. Mang transactionId.
confirmation.failed
Không thể hoàn tất khoản trừ (ví dụ, số dư không đủ hoặc lỗi nội bộ). Mang failureReason.
confirmation.expired
Người dùng không xác nhận trong 10 phút. Không có Vito di chuyển.
confirmation.cancelled
Người dùng đã hủy yêu cầu. Không có Vito di chuyển.
credit.completed
Một khoản ghi có /add do chủ sở hữu tài trợ đã được quyết toán. Mang theo người nhận và transactionId, và kích hoạt ngay lập tức — không có bước xác nhận.
Payload ví dụ (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

Mã lỗi

Lỗi trả về trạng thái khác 2xx với thân JSON mang error.code ở cấp cao nhất. Các trường hợp phổ biến:

400VITO_VALIDATION_ERROR
Một trường yêu cầu bị thiếu hoặc sai định dạng (ví dụ, guildId không hợp lệ).
401invalid key
Khóa API bị thiếu, sai định dạng hoặc không xác định.
403OWNER_MEMBERSHIP_INACTIVE
Tư cách thành viên của chủ dự án đã hết hạn — API bị đóng băng cho đến khi họ đăng ký lại.
403scope / IP
Khóa thiếu phạm vi cần thiết, hoặc IP yêu cầu không nằm trong danh sách cho phép.
404not found
Người dùng, giao dịch hoặc tài nguyên không tồn tại.
402insufficient balance
Người dùng không có đủ Vito cho khoản trừ.
402INSUFFICIENT_OWNER_FUNDS
Số dư của chính bạn (chủ sở hữu) quá thấp để tài trợ một khoản ghi có /add.
429rate limited
Bạn đã vượt giới hạn tốc độ của dự án — hãy chờ và thử lại.
Dạng phản hồi lỗi
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

Ví dụ tích hợp

Tạo một yêu cầu thu phí:

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" }
  }'

Kiểm tra số dư của người dùng:

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

Kiểm thử với tích hợp demo

Một tích hợp demo độc lập được cung cấp để chạy thử toàn bộ luồng từ đầu đến cuối mà không cần viết mã. Nó đi qua yêu cầu → xác nhận → PIN → webhook → hoàn tất.

  1. 1Mở thư mục tích hợp demo và thêm khóa Vito API, một token bot Discord và ID máy chủ (guild) thử nghiệm của bạn vào config.json.
  2. 2Chạy npm install && npm start, rồi mở http://localhost:4000.
  3. 3Tìm một người dùng theo ID Discord, chọn một mục và bắt đầu thu phí — demo gọi /deduct và hiển thị confirmUrl.
  4. 4Phê duyệt PIN trên vetox.io; demo phát hiện hoàn tất (qua webhook nếu đã cấu hình, nếu không thì qua thăm dò) và hoàn tất hành động mẫu.
Nhận webhook cần một callback https công khai và một bí mật ký; không có chúng, demo quay về thăm dò các giao dịch của bạn, nên hoạt động mà không cần cấu hình.
14

Thực hành tốt nhất, giới hạn và bảo mật

Bảo mật

  • Giữ khóa API và bí mật ký chỉ ở phía máy chủ; xoay bất kỳ cái nào ngay nếu nó rò rỉ.
  • Luôn xác minh chữ ký webhook trên thân thô và từ chối các lượt gửi cũ hơn ~5 phút (bảo vệ chống phát lại).
  • Chỉ hoàn tất hành động của bạn khi confirmation.completed — không bao giờ ở phản hồi /deduct (lúc đó khoản thu chưa được hoàn tất).
  • Dùng danh sách IP cho phép và chỉ yêu cầu các phạm vi bạn thực sự cần.

Giới hạn

  • Xác nhận hết hạn sau 10 phút; hãy coi các yêu cầu chưa xác nhận là đã bỏ.
  • Giới hạn tốc độ theo từng dự án và tăng theo cấp thành viên của chủ sở hữu.
  • amount phải là số nguyên dương; metadata giới hạn ở 10 khóa.
Idempotency-Key khử trùng lặp thử lạiWebhook thử lại 5× với backoffPhản hồi 2xx nhanh, thực hiện bất đồng bộ