Embeds · Arcade
Put VPZONE Arcade games on your website. Players sign in with VPZONE before they play, and their scores count on the same daily and weekly leaderboards — and Pulse pots — as on vpzone.tv. Free for you and for them.
embed.js and a <div> to your page. It becomes an iframe served by vpzone.tv.Your page never sees the player's password or token: sign-in happens on vpzone.tv, and the token stays inside the VPZONE iframe, limited to playing the Arcade. Already signing users in with VPZONE yourself? Skip the popup with your own sign-in.
In Developers → OAuth apps → New:
https://your-site.com, https://www.your-site.com, http://localhost:5173 for testing. Ports must match exactly.arcade:play (added for you when you declare origins).Copy the app's client ID. It is public; no secret ever goes in your page.
<script src="https://vpzone.tv/embed.js" async></script>
<div style="max-width:960px"
data-vpzone-arcade="pulse-arena"
data-vpzone-client-id="YOUR_CLIENT_ID"
data-vpzone-lang="en"></div>const game = new VPZONE.Arcade({
game: "2048",
clientId: "YOUR_CLIENT_ID",
parent: document.getElementById("game"),
lang: "en", // or "fr"
on: {
ready: (p) => console.log("loaded", p.game, p.orientation),
auth: (p) => console.log("playing as", p.username),
logout: () => console.log("signed out"),
runstart: (p) => console.log("ranked run started", p.game),
score: (p) => console.log(p.game, p.score, p.status),
error: (p) => console.warn("arcade error", p.code),
},
});
// later: game.destroy();| Slug | Game | Orientation | Pulse pots |
|---|---|---|---|
| pulse-arena | Pulse Arena | landscape | yes |
| pulse-pilot | Pulse Pilot | portrait | yes |
| 2048 | 2048 | portrait | yes |
| radius-raid | Radius Raid | landscape | leaderboard only |
| pulse-bastion | Pulse Bastion | landscape | yes |
| pulse-lancer | Pulse Lancer | landscape | yes |
| pulse-orbit | Pulse Orbit | landscape | yes |
| pulse-spiral | Pulse Spiral | portrait | yes |
| Event | Payload | When |
|---|---|---|
| ready | { game, orientation } | The embed loaded. orientation is portrait or landscape; the SDK sizes an unsized container for you. |
| auth | { username, display_name } | A player signed in. This is all the embed shares about them. |
| logout | {} | The player signed out, or their session ended. |
| runstart | { game } | A ranked run started. |
| score | { game, score, status } | A run ended. status: finished (counted), flagged (held for review, score null) or expired. |
| error | { code, message? } | See error codes below. |
A plain iframe works too. Pass your page origin as parent_origin (URL-encoded) to receive events.
<iframe
src="https://vpzone.tv/embed/arcade/2048?client_id=YOUR_CLIENT_ID&parent_origin=https%3A%2F%2Fyour-site.com&lang=en"
allow="autoplay; fullscreen; gamepad"
allowfullscreen
style="border:0; width:100%; max-width:480px; aspect-ratio:3/4">
</iframe>
<script>
window.addEventListener("message", (ev) => {
if (ev.origin !== "https://vpzone.tv" || ev.data?.source !== "vpzone") return;
console.log(ev.data.event, ev.data.payload); // same events as the SDK
});
</script>If your site already signs users in with VPZONE OAuth, players don't need the popup. Request arcade:play in your own flow, then let your server trade the user's access token for a one-time ticket. Your token never goes to the browser; the ticket is single use, valid 120 seconds and only works for your app.
// Your server — Node/Express. You already sign users in with VPZONE OAuth
// (scope arcade:play) and keep their access token server-side.
app.post("/vpzone/arcade-ticket", requireLogin, async (req, res) => {
const r = await fetch("https://vpzone.tv/api/v1/arcade/embed-tickets", {
method: "POST",
headers: { Authorization: `Bearer ${req.user.vpzoneAccessToken}` },
});
const body = await r.json();
if (!r.ok) return res.status(r.status).json(body);
res.json({ ticket: body.data.ticket }); // single use, valid 120 s
});new VPZONE.Arcade({
game: "pulse-pilot",
clientId: "YOUR_CLIENT_ID",
parent: document.getElementById("game"),
// Called whenever the embed needs a player: first load, and after its
// 2-hour session ends. Return the ticket string.
getTicket: async () => {
const r = await fetch("/vpzone/arcade-ticket", { method: "POST", credentials: "include" });
if (!r.ok) throw new Error("not signed in");
return (await r.json()).ticket;
},
});The embed turns the ticket into a 2-hour Arcade session kept in memory, and calls getTicket again when it needs a new one. Endpoint reference: POST /api/v1/arcade/embed-tickets in the API docs.
allow="autoplay; fullscreen; gamepad". If you sandbox the iframe, allow allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox, or sign-in cannot open.auth event carries a username and display name, nothing else. For more, request profile:read in your own OAuth flow.| unknown_client | No OAuth app has this client_id. |
| no_embed_origins | The app has no Arcade embed origins yet. |
| missing_scope | The app does not allow the arcade:play scope. |
| client_revoked | The app was revoked in the developer portal. |
| client_blocked | VPZONE suspended Arcade embeds for this app or its developer. |
| embeds_disabled / arcade_disabled | Embeds or the whole Arcade are paused on VPZONE. |
| game_not_found | Wrong slug, or a game that cannot be embedded. |
| parent_origin_required | getTicket needs the SDK on a page from one of your embed origins. |
| get_ticket_failed / invalid_ticket | Your getTicket threw, returned nothing, or the ticket was used, expired or minted for another app. |
| token_exchange_failed | The sign-in could not be completed; the player can try again. |
| rate_limited | Too many runs for this player, or for your app in the last hour. |
Add your dev server's origin (for example http://localhost:5173) to the app's embed origins, load the production embed.js on your local page and sign in with a real VPZONE account. Changes to origins apply within a minute. Scores from test runs count like any other, so play fair.