VemoniVemoni
Checking your session…

How it works, start to finish

1

Create a key

Sign in with Discord and generate your key above. It is how we know which account to pay.

2

Offer a sponsor

Ask GET /api/ad which server to promote, then show your user its invite next to the reward you are giving.

3

Confirm, then reward

After they join, call POST /api/join-check. We confirm it with Discord and credit you — then you hand over the reward.

The base URL and your key

Every endpoint sits under one base URL. Send your key with each request — either as a bearer token or in the X-API-Key header. Because calls come from your server, the key never touches a browser.

# Base URL
https://api.vemoni.info

# Send your key one of these two ways
Authorization: Bearer <your-key>
X-API-Key: <your-key>

A working example in one screen

The full reward loop (Node.js). Three IDs, all required: userId — the Discord user you're rewarding; serverId — the server your bot runs in (we pick a sponsor for it); botId — your bot's application (client) ID.

const BASE = 'https://api.vemoni.info';
const h = { 'Authorization': 'Bearer ' + process.env.VEMONI_KEY, 'Content-Type': 'application/json' };

// 1) Ask which sponsor to show, then show it to your user
const ad = await fetch(`${BASE}/api/ad?serverId=${serverId}&botId=${botId}&userId=${userId}`, { headers: h }).then(r => r.json());
if (ad.sponsor) showToUser(ad.sponsor.name, ad.sponsor.invite, 'Join for +100 coins!');

// 2) When they say they joined, confirm it — we check Discord and pay you
const out = await fetch(`${BASE}/api/join-check`, {
  method: 'POST', headers: h,
  body: JSON.stringify({ userId, botId, sponsorId: ad.sponsor && ad.sponsor.guildId })
}).then(r => r.json());

// One field to switch on — reward ONLY on 'credited'.
if (out.status === 'credited') grantBonus(userId, 100);
else if (out.status === 'not_joined') tellUser('Join the server first, then press Check.');
else if (out.status === 'already_counted') tellUser('You already got the reward for this server.');
// 'no_ad' → nothing to promote right now → let them through, no reward.

Examples: discord.py & discord.js

The same three steps in a real bot — ask for an ad, show it, verify when the user presses your button.

# discord.py
import os, aiohttp

BASE = "https://api.vemoni.info"
H = { "Authorization": "Bearer " + os.environ["VEMONI_KEY"] }

async def api(method, path, **kw):
    async with aiohttp.ClientSession() as s:
        async with s.request(method, BASE + path, headers=H, **kw) as r:
            return await r.json()

# 1) on your command: get an ad and show it
ad = await api("GET", f"/api/ad?serverId={gid}&botId={bid}&userId={uid}")
if ad.get("sponsor"):
    sp = ad["sponsor"]
    await show_button(f"Join {sp['name']}: {sp['invite']}", sponsor_id=sp["guildId"])

# 2) on the "I joined" button: verify + reward
out = await api("POST", "/api/join-check", json={"userId": uid, "botId": bid, "sponsorId": sponsor_id})
if out["status"] == "credited":        grant_bonus(uid, 100)
elif out["status"] == "not_joined":     reply("Join the server first, then press Check.")
elif out["status"] == "already_counted": reply("You already got the reward for this server.")
# 'no_ad' → let them through, no reward
// discord.js
const BASE = 'https://api.vemoni.info';
const H = { Authorization: 'Bearer ' + process.env.VEMONI_KEY, 'Content-Type': 'application/json' };
const api = (m, p, body) => fetch(BASE + p, { method: m, headers: H, body: body && JSON.stringify(body) }).then(r => r.json());

// 1) on your slash command
const ad = await api('GET', `/api/ad?serverId=${gid}&botId=${bid}&userId=${uid}`);
if (ad.sponsor) showButton(`Join ${ad.sponsor.name}: ${ad.sponsor.invite}`, ad.sponsor.guildId);

// 2) on the "I joined" button
const out = await api('POST', '/api/join-check', { userId: uid, botId: bid, sponsorId });
if (out.status === 'credited') grantBonus(uid, 100);
else if (out.status === 'not_joined') reply('Join the server first, then press Check.');
else if (out.status === 'already_counted') reply('You already got the reward for this server.');

The endpoints, one by one

GET/api/ad

Parameters (all required): ?serverId=<guild>&botId=<bot>&userId=<user>. Returns { sponsor: { guildId, name, invite } | null, fallbackText } — build your message from sponsor.name and sponsor.invite. If sponsor is null, there's nothing to show: let the user through with no reward. 400 if a parameter is missing.

POST/api/join-check

Body: { userId, botId, sponsorId? }. sponsorId is the guildId from /api/ad — pass it to pin the check to that exact sponsor (recommended if your bot runs several ad commands at once); omit it and we use the most recent ad shown to this user. Every response has a status field — switch on it and reward only on "credited": 200 "credited" — join verified and you were paid → give the reward; 200 "not_joined" (joined:false) — not in the sponsor yet → ask them to join, no reward; 200 "already_counted" — already credited for this sponsor → no reward; 200 "no_ad" — nothing was shown → no reward; 503 "uncertain" — couldn't check right now, retry. Counts once per membership; leaving reverses the credit, a real rejoin counts again.

Errors, limits & rules

Webhooks

Set a URL in your cabinet (above) and we POST a signed event when a join is credited or reversed. Dedupe on eventId. Body:

{
  "event": "credited",        // or "reverted" (member left → undo the reward)
  "eventId": "a1b2c3…",       // unique per delivery
  "timestamp": 1730000000000,
  "user": "<discord user id>",
  "sponsorId": "<guild id>",
  "botId": "<your bot id>",
  "amount": 0.05
}

Verify the X-Vemoni-Signature: sha256=… header against the raw body before trusting it:

// Node — verify the signature
const crypto = require('crypto');
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex');
if (expected !== req.headers['x-vemoni-signature']) return res.sendStatus(401);

Changelog

Latest changes (machine-readable at GET /api/changelog). Prefer the versioned path /v1/api/….

Questions? We are around

Drop into our support server and we will help you wire it up.