Developers / Docs
Developer docs
Your first game in about five minutes, then everything else: the launch API, events, tables, the network, webhooks and the SDKs.
Your first game in five minutes
Pick your platform: install, ask your server for a launch link, show it. No developer? The last tab needs no code at all.
Install
npm install playmez-embedOne script tag, or npm install playmez-embed. Any site, any framework.
Ask your server for a launch link
Your API key stays on your server. Each link is for one player and works once, within five minutes.
launch.mjs// Node 18+. The API key stays on your server; the page or app only ever sees launchUrl. const res = await fetch("https://api.playmez.com/partner/launch", { method: "POST", headers: { "Content-Type": "application/json", "X-Api-Key": process.env.PLAYMEZ_API_KEY }, body: JSON.stringify({ game: "ludo", externalUserId: user.id, displayName: user.name }), }); const { launchUrl } = await res.json(); // one use, valid for five minutes // Drop-in helper with error handling: https://playmez.com/sdk/server/launch.mjsShow the game
index.html<!-- launchUrl comes from your server (see step 2). --> <script src="https://playmez.com/sdk/playmez.js"></script> <div id="ludo-game"></div> <script> const game = Playmez.mount(document.getElementById("ludo-game"), { launchUrl, height: 720, title: "Ludo", onEvent: (e) => { if (e.type === "ready") console.log("Ludo open"); if (e.type === "result") console.log(e.won ? "won" : "lost", e.net, "points"); // one event when the match ends if (e.type === "close") showYourOwnScreenAgain(); if (e.type === "error") console.error(e.code, e.message); // fetch a new launchUrl }, }); // game.unmount() removes the frame. </script>
Testing? Your portal's Try it tab launches a real game with your key and shows the events and the server call side by side.
Code for every game, every platform
Choose a game and a platform. Copy the samples, drop in the file, done.
1 · Pick a game
Every sample below is written for the game you choose. The code beside each name is the `game` value you send.
Ludo · vs computer · Classic Indian Ludo for 2 or 4 players. Bring all four tokens home first; free tables or a points pot. Frame height about 720 px.
2 · Your server gets a launch URL
Server to server, with your API key. The URL works once, for five minutes, and opens the same Playmez account for the same externalUserId every time.
// Node 18+. The API key stays on your server; the page or app only ever sees launchUrl.
const res = await fetch("https://api.playmez.com/partner/launch", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": process.env.PLAYMEZ_API_KEY },
body: JSON.stringify({ game: "ludo", externalUserId: user.id, displayName: user.name }),
});
const { launchUrl } = await res.json(); // one use, valid for five minutes
// Drop-in helper with error handling: https://playmez.com/sdk/server/launch.mjs3 · Show the game
The page or app opens the launch URL. The game runs on Playmez; your side never hosts game code, decides outcomes or holds balances.
npm install playmez-embed<!-- launchUrl comes from your server (see step 2). -->
<script src="https://playmez.com/sdk/playmez.js"></script>
<div id="ludo-game"></div>
<script>
const game = Playmez.mount(document.getElementById("ludo-game"), {
launchUrl,
height: 720,
title: "Ludo",
onEvent: (e) => {
if (e.type === "ready") console.log("Ludo open");
if (e.type === "result") console.log(e.won ? "won" : "lost", e.net, "points"); // one event when the match ends
if (e.type === "close") showYourOwnScreenAgain();
if (e.type === "error") console.error(e.code, e.message); // fetch a new launchUrl
},
});
// game.unmount() removes the frame.
</script>Try the same flow live on the demo page with your key.
4 · Listen for events
Events
Every message is { source: "spinline", type, ...fields }. Websites receive it with postMessage; React Native, Flutter, Android and iOS get the same JSON through their bridge.
| type | fields | when |
|---|---|---|
| ready | game | The player is seated and the game is on screen. |
| result | game, won, net | A match, round or reward settled for this player. net is in virtual points. |
| close | — | The player tapped Leave or Close. Hide or remove the frame. |
| error | code, message | The game could not open. Fetch a new launch URL. |
| resize | height | Reserved for pages that grow with their content. |
Errors
HTTP codes come back from POST /partner/launch. Event codes arrive as an `error` event inside the frame; fetch a new launch URL.
| code | where | meaning |
|---|---|---|
| INVALID_API_KEY | 401 | Unknown, rotated or switched-off key. |
| INVALID_GAME | 400 | game is not one of the ids above. |
| ACCOUNT_BANNED | 403 | This player is banned on Playmez. |
| LAUNCH_EXPIRED | event | The launch URL was older than five minutes. |
| LAUNCH_USED | event | The launch URL was already opened once. |
| PARTNER_DISABLED | event | Games are switched off for this partner. |
Shared tables: your players at one table, talking
Create a table from your server, then launch each player with its code. They sit together, and computer players can fill what is left. Table chat and voice (peer to peer, no audio through any server) switch on per partner; ask us and they appear inside your frames.
1. Create a table (server to server)
The host is one of your players; the table closes itself when nobody is there.
const res = await fetch("https://api.playmez.com/partner/tables", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": process.env.PLAYMEZ_API_KEY },
body: JSON.stringify({ game: "callbreak", hostExternalUserId: "user-123", name: "Friday Callbreak", computerFill: false }),
});
const { tableCode } = await res.json(); // e.g. "K7Q2MZ"2. Launch every player with the code
Same launch call as above, plus tableCode. Each player gets their own one-time link.
const launch = await fetch("https://api.playmez.com/partner/launch", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": process.env.PLAYMEZ_API_KEY },
body: JSON.stringify({ game: "callbreak", externalUserId: "user-456", displayName: "Anoop", tableCode }),
}).then((r) => r.json());
// launch.launchUrl -> mount it exactly as before; the player lands at the shared table.
// GET /partner/tables/K7Q2MZ returns { seated, connected, phase, status } while it runs.Chat and voice in your frames
Both are off until you ask. Chat is the table chat (deleted when the table closes). Voice is peer to peer between the players at a private table; the SDK frame already carries allow="microphone", and native wrappers need the WebView microphone permission (notes in each drop-in README).
Players hear only the people at their table, never strangers; nothing is recorded.
The Playmez network: live people across every app
One game build runs inside every SDK and on playmez.com, talking to one server. So a player in your Flutter app, one in your React Native app, one in another partner's Android app and one in Chrome on playmez.com can sit at the same Ludo table, live, with the same board, chat and notices. Tables fill from the first minute because every partner's audience joins one pool; you never have to find the opponents.
Who meets whom
Decided by how you launch, never by the device or the framework.
mode: "online"Your players, playmez.com players and every other partner in the network, matched in one pool. Needs the network switched on in your portal.
Your two stranger switches decide whether your players can talk with people from outside your app.
tableCodeOnly the people launched with that code, from any of your apps (web, React Native, Flutter, Android, iOS), plus anyone you give the playmez.com join link to. Another partner's launch is refused.
Your own-table chat and voice switches.
mode: "local"Offline on the phone: alone against the computer in every game, or people passing the phone (Ludo, Chess, Carrom, 8-Ball Pool). Nobody from outside, and names stay on the phone.
Chat and voice off: nobody else is at the table.
no mode (default)The computer only. Nobody from outside.
Chat and voice off.
Launch a player into the pool
Same call as a solo launch, plus mode. After 25 seconds without a match the player can switch to the computer.
const launch = await fetch("https://api.playmez.com/partner/launch", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": process.env.PLAYMEZ_API_KEY },
body: JSON.stringify({ game: "ludo", externalUserId: "user-456", displayName: "Anoop", mode: "online" }),
}).then((r) => r.json());
// launch.launchUrl -> mount it as usual. Ludo, Callbreak, Rummy, Chess, Carrom, 8-Ball Pool and Teen Patti are network games.What stays yours
Your players are your accounts: points, level and history stay under your partner record, and no partner learns which app, or whether playmez.com, another player came from (unless both opted into cross-promotion, below, which shows a vetted brand and nothing more).
Results reach your webhook for your players only, marked network: true when the match was at a public table. Your private tables and tournaments never appear in Playmez's public lists.
A game never ends because of a bad connection or a Playmez update: a player who drops is played by the computer until they are back (up to three minutes), and every live table carries on across our deploys.
Your brand in every app
Optional, off until you ask, and on your terms.
Network tables show where each player came from: your brand inside the other apps and theirs inside yours, in the waiting lobby and in a strip above the board. You choose what your players see (all apps, Playmez only or none), whether they can open other apps, and the apps or kinds of app you never appear with. See it in a game.
Webhooks: results pushed to your server
No polling. Set a URL and Playmez posts every settled result, new player, opened session and closed table to your server. Each delivery is signed with HMAC-SHA256 and retried for about 35 hours until you answer 2xx.
1. Point us at your URL
https only. The signing secret is shown once.
// Once, from your server. The secret comes back only this time: store it with your API key.
const res = await fetch("https://api.playmez.com/partner/webhook", {
method: "PUT",
headers: { "Content-Type": "application/json", "X-Api-Key": process.env.PLAYMEZ_API_KEY },
body: JSON.stringify({ url: "https://example.com/playmez/webhook" }),
});
const { url, secret } = await res.json(); // secret: "whsec_…" (first time) or null
// POST /partner/webhook/test sends a webhook.test; GET /partner/webhook/deliveries lists what was sent.Events
Body: { id, type, createdAt, partner: { id, slug }, data }
player.createdplayer { id, externalUserId, displayName }One of your user ids opened a game for the first time and got a Playmez account.
session.openedplayer, game, tableCodeA player opened a launch URL and is in the game.
player.resultplayer, game, won, net, withBots, matchId, tableCode, networkA match (turn games) or round (round games) settled for this player. net is in virtual points; network is true for a match on the Playmez network (tableCode is then null).
table.closedtableCode, tableId, gameA table you created with POST /partner/tables closed.
tournament.startedtournament { id, name, game, startsAt, endsAt }A tournament you created began scoring.
tournament.finishedtournament, standings [{ rank, player, netPoints, wins, roundsPlayed, prize }]A tournament you created ended; standings are final.
tournament.prize_awardedtournament, rank, prize { label, code }, player (your own player) or visitor (anyone else)A tournament you sponsor ended and a winner got one of your coupons. player { id, externalUserId, displayName } only for your own players; others are an anonymous visitor tag.
mission.completedmission { id, title, kind, game, target, period }, periodKey, player { id, externalUserId, displayName }, completedAt, sandboxOne of your players finished a mission you set. Give the reward in your app; the same mission, player and period is never sent twice.
webhook.test{}You asked for a test delivery.
2. Verify every delivery, then answer 2xx
Headers: Playmez-Event, Playmez-Delivery, Playmez-Timestamp, Playmez-Signature (v1=hex), Playmez-Attempt.
import { createHmac, timingSafeEqual } from "node:crypto";
// Express: keep the raw body; the signature covers the exact bytes.
app.post("/playmez/webhook", express.raw({ type: "application/json" }), (req, res) => {
const ts = req.header("Playmez-Timestamp");
const expected = createHmac("sha256", process.env.PLAYMEZ_WEBHOOK_SECRET).update(`${ts}.${req.body}`).digest("hex");
const given = (req.header("Playmez-Signature") ?? "").split(",").map((s) => s.trim()).filter((s) => s.startsWith("v1=")).map((s) => s.slice(3));
const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
if (!fresh || !given.some((g) => g.length === expected.length && timingSafeEqual(Buffer.from(g), Buffer.from(expected)))) return res.sendStatus(401);
const event = JSON.parse(req.body); // { id, type, createdAt, partner: { id, slug }, data }
if (event.type === "player.result") {
// data: { player: { id, externalUserId, displayName }, game, won, net, withBots, matchId, tableCode }
}
res.sendStatus(200); // retries reuse event.id, so make handling idempotent
});import hmac, hashlib, time
def verify(raw_body: bytes, signature: str, timestamp: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest()
given = [p.strip()[3:] for p in signature.split(",") if p.strip().startswith("v1=")]
fresh = abs(time.time() - int(timestamp or 0)) < 300
return fresh and any(hmac.compare_digest(g, expected) for g in given)
# Flask: request.get_data() is the raw body; answer 2xx within 10 seconds.Retries and inspection
- No 2xx within 10 seconds, a redirect or a connection error: tried again after 1 min, 5 min, 30 min, 2 h, 8 h and 24 h, then marked failed.
- Retries resend the same body and the same Playmez-Delivery id. Keep handling idempotent.
GET /partner/webhook/deliverieslists status, attempts and the last answer;POST /partner/webhook/deliveries/:id/redeliverqueues one again. Kept 30 days.- The player object carries your externalUserId and the display name only, never the Playmez username or balance.
- Full copy-paste receivers for Node and Python are in the drop-in files below.
Appearance: the frame looks like your product
Accent, background, text, corners and a Google font, applied before the first paint. Set them once in your portal with a live preview, or per launch.
Language
lang on a launch, or a default in your portal. English and Hindi today, covering every game; BCP 47 tags such as hi-IN are accepted.
body: JSON.stringify({ game: "carrom", externalUserId: "user-123", lang: "hi" })
// The embed renders in Hindi from its first paint; the response echoes lang: "hi".Per launch, or once for the partner
Every field is optional. The background must stay dark; text on the accent flips light or dark by itself.
const launch = await fetch("https://api.playmez.com/partner/launch", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": process.env.PLAYMEZ_API_KEY },
body: JSON.stringify({
game: "ludo",
externalUserId: "user-123",
theme: { accent: "#1e90ff", background: "#101418", radius: "large", googleFont: "Poppins" },
}),
}).then((r) => r.json());
// Fields: accent, background, text (hex), radius (none | small | medium | large),
// font (spinline | system | rounded | serif | mono), googleFont (a fonts.google.com family).
// A bad value answers 400 INVALID_THEME with a problems list naming the field.Tournaments for your players
One call opens a scored event across your own players. Every match they finish counts, a strip in the embed shows each player their rank, and the standings reach you in the portal, over the API and by webhook when it ends.
Create, then read the standings
Your tournaments never appear in Playmez's public events; only your players score in them.
const cup = await fetch("https://api.playmez.com/partner/tournaments", {
method: "POST",
headers: { "Content-Type": "application/json", "X-Api-Key": process.env.PLAYMEZ_API_KEY },
body: JSON.stringify({ name: "Friday Carrom Cup", game: "carrom", startsAt: "2026-10-30T14:00:00Z", endsAt: "2026-10-30T18:00:00Z" }),
}).then((r) => r.json());
// Later, or on the tournament.finished webhook:
const { standings } = await fetch("https://api.playmez.com/partner/tournaments/" + cup.id, { headers: { "X-Api-Key": process.env.PLAYMEZ_API_KEY } }).then((r) => r.json());
// standings[0] -> { rank: 1, player: { externalUserId: "user-123", displayName: "Asha" }, netPoints: 240, wins: 6, roundsPlayed: 9, prize: 0 }The games
By default a launch opens the player's own table against the computer; round games open a private one-player table; rewards once a day. Shared tables, chat and voice are the section above.
Ludo
Roll, race, capture.
Classic Indian Ludo for 2 or 4 players. Bring all four tokens home first; free tables or a points pot.
vs computergame: "ludo"720 pxCallbreak
Call it. Make it.
Four players, 13 cards each, spades always trump. Bid your tricks, then make them over 5 rounds.
vs computergame: "callbreak"720 pxRummy
Group it. Declare it.
13-card Indian Rummy for 2 to 6. Make a pure sequence, a second sequence and sets, then declare first. Free points, no cash.
vs computergame: "rummy"720 pxTambola
Housie! Mark, claim, win.
The caller draws 1 to 90, you daub your tickets. Early Five, lines, corners and Full House, up to 100 players a table.
vs computergame: "tambola"720 pxChess
Rated games, real clocks.
Bullet to rapid with increments, Elo ratings, computer opponents at three levels. Free tables or a points pot.
vs computergame: "chess"720 pxTeen Patti
Blind or seen. Chaal or show.
Three cards, 2 to 6 players, blind and seen betting, sideshows of nerve. Virtual points only, never cash.
vs computergame: "teen_patti"720 pxCarrom
Flick. Pocket. Cover the queen.
Two players, nine coins each and the red queen. Place the striker, flick, pocket your colour; cover the queen or she comes back. Free tables or a points pot.
vs computergame: "carrom"720 px8-Ball Pool
Break. Pot your group. Sink the 8.
Two players, solids against stripes. Pot your seven, then the black; a foul hands your opponent the cue ball. Free tables or a points pot.
vs computergame: "pool"720 pxRoulette
Pick a colour, a number, a side.
European single-zero wheel. Everyone watches the same spin.
private tablegame: "roulette"640 pxCrash
Cash out before it busts.
A multiplier climbs from 1.00×. Cash out in time and multiply your stake; hold too long and lose it.
private tablegame: "crash"640 pxTrivia Blitz
Fast answers pay more.
One question per round, four options. Correct doubles your stake, speed adds a bonus.
private tablegame: "trivia"640 pxSpin the Wheel
One free spin a day.
A prize wheel with a published commitment. Free spins per day are set by Playmez; prizes are virtual points.
daily rewardgame: "wheel"560 pxScratch Card
Three of a kind wins.
A scratch card with a fair, revealed seed. Free cards per day are set by Playmez; prizes are virtual points.
daily rewardgame: "scratch"560 px
Packages and files
Packages, version 2.1.0
One version for every platform. Install, import, done.
- Website and Reactplaymez-embed
npm install playmez-embed - React Native and Expoplaymez-embed-react-native
npm install playmez-embed-react-native react-native-webview - Flutterplaymez_embed
flutter pub add playmez_embed - Androidcom.playmez:embed
implementation("com.github.playmez:playmez-android:2.1.0") - iOSPlaymezEmbed
.package(url: "https://github.com/playmez/playmez-swift", from: "2.1.0")
Drop-in files
Generated from the same packages, for projects that cannot add a dependency.
- Index and quick start/sdk/README.md
- Full partner guide/sdk/partners.md
- OpenAPI description/sdk/openapi.yaml
- Website SDK/sdk/playmez.js
- Website SDK types/sdk/playmez.d.ts
- React component/sdk/react/PlaymezGame.tsx
- React Native component/sdk/react-native/PlaymezGame.tsx
- Flutter widget/sdk/flutter/playmez_game.dart
- Android view/sdk/android/PlaymezGameView.kt
- iOS view + SwiftUI/sdk/ios/PlaymezGameView.swift
- Server launch, Node/sdk/server/launch.mjs
- Server launch, Python/sdk/server/launch.py
- Server launch, PHP/sdk/server/launch.php
- Webhook receiver, Node/sdk/server/verify-webhook.mjs
- Webhook receiver, Python/sdk/server/verify_webhook.py
- Live demo page/sdk/demo.html
Security
- The API key is a server secret. Never put it in a page, an app bundle or a public repository.
- A launch URL works once, within five minutes. Fetch a fresh one every time a player opens a game.
- Only the web origins you registered may frame the games. Native apps need no origin.
- Outcomes, balances and timers are decided on Playmez's servers. Every match or round publishes a seed hash first and reveals the seed after.
Limits and rules
- API base
https://api.playmez.com. 600 launches per minute per key. - One game per frame. A player holds one seat per turn game at a time.
- Practice matches against the computer are free. Round games stake virtual points from a welcome bonus and a daily allowance.
- Virtual points only. No cash value. Points cannot be bought, sold, transferred or redeemed. You must not map points to money, prizes or anything of value.