Biarkan aplikasi yang disetujui menagih Vito Anda dengan konfirmasi PIN, dan kelola permintaan pembayaran dari halaman Purchases Anda.
Membutuhkan Keanggotaan Silver atau lebih tinggi — baik untuk mendaftar maupun untuk setiap panggilan terautentikasi. Jika Keanggotaan pemilik kunci berakhir, proyek dibekukan sampai ia berlangganan lagi.
REST API yang memungkinkan aplikasimu bekerja dengan saldo Vito seorang pengguna dari dalam Discord — membacanya, menagihnya, menambahkannya, atau memindahkannya antar pengguna. Semua endpoint mengembalikan JSON dan diberi versi di bawah /v1.
Vito tidak pernah berpindah ke atau dari uang sungguhan. Ia hanya bergerak antar saldo Vetox.
Kunci dan secret penandatanganan ditampilkan tepat satu kali. Setelah disetujui kamu punya jendela 7 hari untuk mengungkapkannya di tab kunci API. Vetox hanya menyimpan hash dan tidak bisa menampilkannya lagi — jika terlewat, kamu harus melakukan rotasi.
Simpan hanya di sisi server — siapa pun yang memegangnya bisa menagih penggunamu
Rotasi dari tab kunci API. Kunci sebelumnya tetap berfungsi selama masa tenggang 24 jam agar kamu bisa merilis tanpa waktu henti
Secret penandatanganan webhook dirotasi terpisah, dengan tumpang tindih 24 jam tersendiri
Kuncimu saja tidak bisa memindahkan Vito seorang pengguna. Setiap penagihan mengharuskan pengguna menyetujuinya dengan PIN dompetnya, di vetox.io — tidak pernah di dalam aplikasimu maupun di dalam Discord.
1
Aplikasimu memanggil POST /v1/deduct
Dengan pengguna, jumlah, guildId asal, dan detail barang.
2
Vito mengembalikan confirmUrl
Konfirmasi tertunda, berlaku 10 menit. Pengguna juga menerima DM.
3
Pengguna menyetujui dengan PIN-nya
Di vetox.io.
4
Vito menyelesaikan dan memberi tahu
Saldo dipotong, transaksi dicatat, dan webhook bertanda tangan dikirim jika kamu sudah mengaturnya.
5
Aplikasimu memverifikasi dan menyelesaikan
Periksa tanda tangannya, lalu buka kontennya atau kirim barangnya.
Selesaikan tindakanmu hanya pada confirmation.completed — jangan pernah pada respons /deduct. Pada titik itu penagihan belum final.
POST /v1/add menambahkan Vito ke pengguna dari saldomu sendiri — untuk hadiah atau pengembalian dana. Bidang yang sama dengan /deduct kecuali guildId dan product.
Berbeda dari penagihan, penambahan tidak punya langkah konfirmasi — langsung diselesaikan. Membutuhkan scope credit:create dan saldo yang cukup, atau panggilan mengembalikan 402 VITO_INSUFFICIENT_OWNER_FUNDS.
Setiap penagihan diselesaikan kepadamu dikurangi biaya platform — skema yang sama dengan transfer Vito di dalam aplikasi, berdasarkan tingkat Keanggotaan kamu:
Keanggotaanmu
Biaya
Normal, Silver, Gold
7%
Platinum
6%
Diamond
5%
Jumlah 5 Vito atau kurang bebas biaya, dan penambahan lewat /v1/add selalu bebas biaya.
Tambahkan satu atau beberapa URL callback https di tab pengaturan. Vito mengirim POST bertanda tangan setiap kali sebuah konfirmasi mencapai keadaan akhir.
Webhook hanya terkirim jika proyekmu punya sekaligus URL callback dan secret penandatanganan. Ungkapkan secret (whsec_…) satu kali dari tab kunci API.
Setiap pengiriman membawa header X-Vito-Signature:
X-Vito-Signature: ts=<unix>;h1=<hex>
Dua header lain menyertai setiap pengiriman — gunakan X-Vito-Event-Id sebagai kunci deduplikasi, karena percobaan ulang mengirim id yang sama:
Header
Berisi
X-Vito-Event-Id
Id tetap untuk peristiwa ini — sama di semua percobaan ulang
X-Vito-Event-Type
Misalnya confirmation.completed
Selama rotasi secret penandatanganan, header membawa lebih dari satu tanda tangan, yang terbaru lebih dulu:
X-Vito-Signature: ts=<unix>;h1=<baru>;h1=<lama>
Terima pengiriman jika salah satuh1 cocok. Verifikator yang hanya membaca yang pertama akan menolak setiap webhook sampai ia merilis secret baru — dan itu justru meniadakan seluruh tujuan tumpang tindih 24 jam.
Hitung ulang HMAC atas <ts>:<rawBody> dengan secret penandatangananmu dan bandingkan dalam waktu konstan.
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) ); });}
Verifikasi terhadap body mentah, sebelum parsing JSON atau middleware menulis ulangnya.
Ada juga batas per IP sebesar setengah jatah per menitmu, dengan batas bawah 30.
Saat beban tinggi, endpoint tulis gagal secara tertutup — penagihan ditolak alih-alih mempertaruhkan pembelanjaan ganda. Endpoint baca gagal secara terbuka. Perlakukan penulisan yang ditolak sebagai “tidak terjadi” lalu coba lagi.
Kirim header Idempotency-Key untuk menghindari duplikasi saat mencoba ulang.