V-ENT pour les studios de jeux
Faites tourner les tournois V-ENT dans votre jeu
Reliez votre jeu une fois et chaque format V-ENT y fonctionne, ceux d’aujourd’hui comme ceux à venir. Votre jeu ne gère qu’une session de jeu à la fois ; V-ENT s’occupe du tableau, des classements, de l’argent et du direct autour.
Relier un jeuComment tout s’emboîte
- Reliez votre jeu dans la section Studios du portail partenaire. Il commence dans le bac à sable, avec un secret de signature affiché une seule fois.
- Les joueurs lient leur compte de jeu à leur compte V-ENT, avec un code que votre jeu affiche ou avec la connexion avec V-ENT.
- Un organisateur relie son tournoi à votre jeu depuis la console du tournoi.
- Quand un match est prêt, V-ENT demande un salon à votre jeu. Votre jeu répond avec un identifiant de session et la façon de rejoindre.
- Quand la partie se termine, votre serveur envoie le résultat brut. V-ENT le vérifie, le note avec votre expression et l’enregistre sur le match.
- Demandez la mise en production quand tout fonctionne. Un administrateur V-ENT examine d’abord le jeu.
Le bac à sable et les clés de test
Un jeu en bac à sable n’atteint que les tournois que votre propre compte organise et les testeurs que vous désignez, pour construire sur de vrais tableaux sans toucher à ceux des autres. Créez une clé de test dans le portail : elle commence par le préfixe ci-dessous et n’atteint que les jeux en bac à sable. Une clé de production n’atteint que les jeux en production.
Authorization: Bearer vent_pk_test_<key id>.<secret>Chaque requête est signée, dans les deux sens
Chaque requête que V-ENT vous envoie, et chaque mise à jour que vous nous envoyez, est signée en HMAC SHA-256 avec le secret de signature de votre jeu, sur la méthode, le chemin et la requête, un horodatage, l’hôte et une empreinte du corps. Refusez tout ce qui est plus ancien que la fenêtre ci-dessous, et gardez chaque identifiant de requête vu pendant cette durée pour qu’une même requête ne puisse pas être rejouée.
{METHOD}\n{path and query}\n{timestamp};{host};{base64 SHA-256 of the body}V-Ent-Timestamp- Le moment de la signature, en secondes unix
V-Ent-Content-SHA256- Le SHA-256 du corps, en base64
V-Ent-Signature- v1= suivi de la signature, une fois par secret valide
V-Ent-Delivery- Un identifiant de requête utilisé une fois ; gardez-le pendant la fenêtre pour refuser les répétitions
- Fenêtre
- 300 secondes avant ou après maintenant
Après un renouvellement du secret, l’ancien reste valable 24 heures et chaque requête porte une signature v1= par secret, pour changer sans rien perdre.
import base64, hashlib, hmac, time
def verify(secret, method, path_and_query, host, headers, body):
stamp = int(headers["V-Ent-Timestamp"])
if abs(time.time() - stamp) > 300:
raise ValueError("stale")
content = base64.b64encode(hashlib.sha256(body).digest()).decode()
if not hmac.compare_digest(content, headers["V-Ent-Content-SHA256"]):
raise ValueError("body changed")
text = f"{method}\n{path_and_query}\n{stamp};{host};{content}"
expected = base64.b64encode(
hmac.new(secret.encode(), text.encode(), hashlib.sha256).digest()).decode()
offered = [p.strip()[3:] for p in headers["V-Ent-Signature"].split(",")]
if not any(hmac.compare_digest(expected, o) for o in offered):
raise ValueError("bad signature")import crypto from 'node:crypto';
export function verify(secret, method, pathAndQuery, host, headers, body) {
const stamp = Number(headers['v-ent-timestamp']);
if (Math.abs(Date.now() / 1000 - stamp) > 300) throw new Error('stale');
const content = crypto.createHash('sha256').update(body).digest('base64');
if (content !== headers['v-ent-content-sha256']) throw new Error('body changed');
const text = `${method}\n${pathAndQuery}\n${stamp};${host};${content}`;
const expected = crypto.createHmac('sha256', secret).update(text).digest('base64');
const offered = headers['v-ent-signature'].split(',').map(p => p.trim().slice(3));
const ok = offered.some(o => o.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(o), Buffer.from(expected)));
if (!ok) throw new Error('bad signature');
}Ce que V-ENT vous envoie
Chaque requête a le même corps, une seule adresse peut donc toutes les recevoir. Les appels de session de jeu vont à votre adresse des sessions ; les événements vont à votre adresse des événements, ou à celle des sessions si vous l’avez laissée vide.
{
"id": "dl_8f2c...",
"type": "event",
"event": "result_confirmed",
"created_at": "2026-10-17T18:04:11Z",
"game": "<your game>",
"environment": "live",
"data": { }
}eventÉvénementcreate_game_sessionCréer une session de jeucancel_game_sessionAnnuler une session de jeuget_game_sessionDemander une session de jeu
Répondez avec n’importe quel 2xx et la requête est livrée. Toute autre réponse, ou aucune réponse en 10 secondes, est réessayée selon le calendrier ci-dessous puis abandonnée. Répondez 410 pour arrêter tout de suite. Chaque requête et la réponse de votre serveur figurent dans le journal des envois sur la page de votre jeu, et par clé sur /api/v1/studio/games/<game>/deliveries/.
Attentes entre les essais pour un événement, en secondes : 30, 120, 600, 1 800, 3 600, 10 800, 21 600, 43 200, 86 400
Sessions de jeu
Un match en deux manches gagnantes compte jusqu’à trois sessions, une par partie. Quand un match est prêt, nous envoyons create_game_session avec les deux compositions (avec le compte de jeu de chaque joueur qui l’a lié), les réglages choisis par l’organisateur et une clé d’idempotence. Traitez une clé répétée comme le même salon : nous rappelons jusqu’à ce que vous répondiez created ou plus loin.
{ "session_id": "your-lobby-42", "state": "created",
"join": { "room_code": "ROOM42", "room_password": "pw" } }Un room_code dans join est montré aux deux camps dans la salle du match, sauf s’ils en ont saisi un eux-mêmes.
Les états n’avancent que dans un sens : creating, created, starting, started, finished, ou cancelled. Envoyez chaque changement, signé, à l’adresse de rappel donnée par l’appel de création. Une partie qui plante est annulée sans résultat, et V-ENT ouvre l’essai suivant de la même partie, jusqu’à trois.
curl -X POST https://api.v-ent.co/api/v1/studio/sessions/gs_<ref>/ \
-H "Authorization: Bearer vent_pk_<key id>.<secret>" \
-H "V-Ent-Timestamp: <unix seconds>" \
-H "V-Ent-Content-SHA256: <base64 sha256 of the body>" \
-H "V-Ent-Signature: v1=<signature>" \
-H "V-Ent-Delivery: <a new id for every request>" \
-d '{"state": "finished", "result": { }}'Si une partie commencée reste silencieuse cinq minutes, nous vous interrogeons avec get_game_session ; répondez avec le même corps que celui que vous enverriez. Quand un match est réglé autrement, nous envoyons cancel_game_session. Nous n’annulons jamais une partie terminée.
Résultats et score
Un résultat a deux compositions, une pour le camp 1 et une pour le camp 2, une liste facultative de compétiteurs (chacun avec un camp) et un objet facultatif pour toute la partie. À l’intérieur, les champs sont les vôtres, vérifiés avec le schéma de résultat de votre jeu.
{
"lineups": [ { "side": 1, "score": 13, "won": true },
{ "side": 2, "score": 7, "won": false } ],
"competitors": [ { "account_id": "player-one", "side": 1, "kills": 9 } ],
"general": { "map": "Bermuda" }
}Mots-clés de schéma vérifiés par V-ENT : type, properties, required, items, enum, minimum, maximum, minLength, maxLength, minItems, maxItems, additionalProperties, description, title, $schema. Un schéma qui utilise un autre mot-clé est refusé à l’enregistrement, plutôt que vérifié à moitié.
Expressions de score
Votre expression transforme le résultat brut d’un camp en son score pour cette partie. Elle est calculée une fois par camp, et la réponse doit être un nombre entier de 0 à 999.
team.score
competitors.sum(it.kills)
team.roundsWon + competitors.sum(it.kills)
global.double ? team.roundsWon * 2 : team.roundsWon
settings.mode == 'KillRace' ? competitors.sum(it.kills) : (team.won ? 1 : 0)- Noms
team, opponent, competitors, global, settings- Fonctions
abs, ceil, floor, max, min, round- Sur une liste
.all(it...), .any(it...), .avg(it...), .count(it...), .max(it...), .min(it...), .sum(it...)
L’expression est lue par le petit analyseur de V-ENT, jamais exécutée comme du code. Essayez-la sur un résultat d’exemple depuis la page de votre jeu avant de l’enregistrer.
Le résultat arrive sur le match par les mêmes règles qu’un résultat saisi à la main. Tout ce que ces règles refusent, comme une égalité en élimination directe, attend l’organisateur, qui garde le dernier mot.
Lier les comptes des joueurs
Avec un code : votre serveur en demande un pour son compte, votre jeu l’affiche, et le joueur le saisit sur v-ent.co/link-game une fois connecté. Il voit votre jeu et le nom de son compte avant de confirmer. Un code dure dix minutes et ne sert qu’une fois. Demandez s’il a été utilisé par son code_ref, jamais par le code.
curl -X POST https://api.v-ent.co/api/v1/studio/games/<game>/link-codes/ \
-H "Authorization: Bearer vent_pk_<key id>.<secret>" \
-d '{"account_id": "your-account-id", "display_name": "Ada#1"}'Avec la connexion avec V-ENT : connectez le joueur depuis votre jeu, puis envoyez le jeton d’accès avec votre identifiant de compte à /api/v1/studio/games/<game>/links/. Le jeton doit être le vôtre et porter la portée identity.
Événements
La même liste que lisent les habillages et le conducteur de V-ENT. Choisissez ceux à recevoir sur la page de votre jeu ; n’en choisissez aucun pour tout recevoir. Un événement de test peut être envoyé depuis la page à tout moment.
result_confirmed- Un résultat de match a été enregistré
stage_started- Une phase a commencé
stage_closed- Une phase est terminée
line_sent- Des places ont été envoyées vers une autre phase
tournament_won- Un tournoi a été remporté
account_linked- Un joueur a lié son compte
account_unlinked- Le lien d’un joueur a été retiré
Lire pour vos propres écrans
Affichez un tournoi dans votre jeu avec l’API de lecture : les phases et leurs classements (le même classement que le site), et le prochain match d’un joueur lié. Chaque réponse indique ce qu’il reste de votre limite de requêtes, pour ralentir avant d’être refusé.
GET /api/v1/tournaments/<slug>/structure/
GET /api/v1/tournaments/<slug>/standings/
GET /api/v1/studio/games/<game>/accounts/<account_id>/next-match/
X-Rate-Limit-Limit: 60
X-Rate-Limit-Remaining: 59
X-Rate-Limit-Reset: 42Dès la première session, la page de votre jeu compte les joueurs liés qui ont joué chaque mois et les sessions jouées. Rien n’est facturé pour cela aujourd’hui.