Put a Playmez game in your product
12 games, one integration. Your server asks for a launch URL, your site or app shows it, the game posts events back. Nothing to host, no outcomes to compute, no balances to keep.
- Website
- React / Next.js
- React Native
- Flutter
- Android
- iOS
- Any backend
How it works
Get a key
Playmez creates your partner record and names its first owner. From then on your portal at /partners holds the API key, allowed origins, switches, webhooks, members and usage.
Launch from your server
When a user taps Play, your backend calls POST /partner/launch with the game and your own user id. Back comes a one-time launch URL.
Show the game
Your page or app opens that URL in an iframe or a WebView with one of the drop-in files, then listens for ready, result and close.
Hear back
Set a webhook URL and Playmez posts every result, new player and session to your server, signed and retried for a day.
Try it first
Your portal has a Try it tab: a real launch, mounted with the package, with the events, the exact server call and the webhook deliveries on one screen.
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.
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, tableCodeA match (turn games) or round (round games) settled for this player. net is in virtual points.
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.
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 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.0.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.0.0") - iOSPlaymezEmbed
.package(url: "https://github.com/playmez/playmez-swift", from: "2.0.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.
Ready to integrate?
Ask Playmez for a partner record; everything after that is self-service in your portal. Integration takes an afternoon.