Reseller API · v1
Vum Game Market API
Base URL https://vumgm.com/api/v1. JSON in, JSON out, USD cents for money. Keys are created in the reseller portal after your account is approved.
Authentication
Every request carries four headers. The signature is a hex HMAC-SHA256 of a canonical string, using your signing secret.
X-Api-Key: vgmk_…
X-Timestamp: 1761170000 (unix seconds, ±300 s)
X-Nonce: 8f2c0e4d9a1b4c77 (16–64 chars, never reused)
X-Signature: hex(HMAC_SHA256(secret, canonical))
canonical = timestamp + "\n" + nonce + "\n" + METHOD + "\n" + path_with_query + "\n" + sha256_hex(raw_body)
e.g. "1761170000\n8f2c0e4d9a1b4c77\nPOST\n/api/v1/orders\n"
(GET requests hash the empty string)
PHP
function vumgm(string $method, string $path, ?array $body = null): array {
$key = getenv('VUMGM_KEY'); $secret = getenv('VUMGM_SECRET');
$raw = $body === null ? '' : json_encode($body);
$ts = (string) time(); $nonce = bin2hex(random_bytes(12));
$sig = hash_hmac('sha256', "$ts\n$nonce\n$method\n$path\n".hash('sha256', $raw), $secret);
$ch = curl_init('https://vumgm.com'.$path);
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_POSTFIELDS => $raw ?: null,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', "X-Api-Key: $key", "X-Timestamp: $ts", "X-Nonce: $nonce", "X-Signature: $sig"]]);
return json_decode(curl_exec($ch), true);
}
Node.js
import crypto from 'node:crypto';
async function vumgm(method, path, body) {
const raw = body ? JSON.stringify(body) : '';
const ts = String(Math.floor(Date.now() / 1000)), nonce = crypto.randomBytes(12).toString('hex');
const hash = crypto.createHash('sha256').update(raw).digest('hex');
const sig = crypto.createHmac('sha256', process.env.VUMGM_SECRET).update(`${ts}\n${nonce}\n${method}\n${path}\n${hash}`).digest('hex');
const res = await fetch('https://vumgm.com' + path, { method, body: raw || undefined, headers: {
'Content-Type': 'application/json', 'X-Api-Key': process.env.VUMGM_KEY, 'X-Timestamp': ts, 'X-Nonce': nonce, 'X-Signature': sig } });
return res.json();
}
Errors
Errors return {"error": {"code": "…", "message": "…"}}. Codes: missing_auth, invalid_key, bad_signature, stale_timestamp, replayed_nonce, ip_not_allowed, insufficient_scope, rate_limited (429), validation_failed, product_unavailable, package_unavailable, insufficient_credit (402), daily_limit, order_limit, price_below_cost, reference_conflict (409), not_found, server_error.
GET /account
Credit line, tier and key info. Any scope. Also returns server_time for clock checks.
GET /products · GET /products/{slug}
Scope catalog:read. Products currently open for ordering, with packages[].id, price_usd_cents (your wholesale price), requires_zone, and custom_quantity limits where custom amounts are allowed.
POST /recipients/validate
Scope recipients:validate. Body {"product":"bigo-live","recipient_id":"902837461"}. Returns valid, verified (false when the supplier cannot check) and name where available.
POST /orders
Scope orders:write. Charged to your credit line; fulfilment starts automatically.
{
"reference": "your-unique-id-123", // required, idempotency key (max 64, [A-Za-z0-9._-])
"product": "bigo-live",
"package_id": 3, // or "quantity": 1234 for custom amounts
"recipient_id": "902837461",
"recipient_zone": null // required when the product requires_zone
}
201 = created. 200 = the same reference was already used with identical parameters; the original order is returned and you are not charged twice. A different payload with a used reference returns 409. On network errors, retry with the same reference.
Statuses: queued → processing → supplier_pending → completed, or under_review / failed / refunded. Only completed means delivered.
GET /orders/{reference}
Scope orders:read. Accepts our reference or yours.
GET /orders?status=&limit=&before_id=
Newest first, up to 100 per page. Pass next_before_id as before_id to page.
Webhooks
Set an HTTPS endpoint in the portal. We POST the order object on order.pending, order.under_review, order.completed, order.failed, order.refunded, retrying with backoff for about 2 hours. Verify every delivery:
expected = "sha256=" + hex(HMAC_SHA256(webhook_secret, X-VumGM-Timestamp + "." + raw_body)) compare expected with X-VumGM-Signature in constant time; reject timestamps older than 5 minutes; de-duplicate on X-VumGM-Delivery (event id). Respond 2xx within 10 s.
Test mode
Keys created in test mode authenticate and sign exactly like live keys, but POST /orders returns a simulated completed order with "test": true. Nothing reaches a supplier and nothing is charged. All other endpoints return real data.