Skip to content
Partner SDK

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

  1. 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.

  2. 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.

  3. 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.

  4. Hear back

    Set a webhook URL and Playmez posts every result, new player and session to your server, signed and retried for a day.

  5. 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.

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.mjs
Download the full helper (server/launch.mjs)

3 · 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
Needs: One script tag, or npm install playmez-embed. Any site, any framework. playmez.js
ludo.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>

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.

typefieldswhen
readygameThe player is seated and the game is on screen.
resultgame, won, netA 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.
errorcode, messageThe game could not open. Fetch a new launch URL.
resizeheightReserved 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.

codewheremeaning
INVALID_API_KEY401Unknown, rotated or switched-off key.
INVALID_GAME400game is not one of the ids above.
ACCOUNT_BANNED403This player is banned on Playmez.
LAUNCH_EXPIREDeventThe launch URL was older than five minutes.
LAUNCH_USEDeventThe launch URL was already opened once.
PARTNER_DISABLEDeventGames 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.

create-table.js
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.

launch-at-table.js
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.

set-webhook.js
// 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, tableCode

    A player opened a launch URL and is in the game.

  • player.resultplayer, game, won, net, withBots, matchId, tableCode

    A match (turn games) or round (round games) settled for this player. net is in virtual points.

  • table.closedtableCode, tableId, game

    A 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.

webhook.js
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
});
webhook.py
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/deliveries lists status, attempts and the last answer; POST /partner/webhook/deliveries/:id/redeliver queues 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.

launch-in-hindi.js
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.

launch-with-theme.js
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.

tournament.js
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 px
  • Callbreak

    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 px
  • Rummy

    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 px
  • Tambola

    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 px
  • Chess

    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 px
  • Teen 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 px
  • Carrom

    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 px
  • Roulette

    Pick a colour, a number, a side.

    European single-zero wheel. Everyone watches the same spin.

    private tablegame: "roulette"640 px
  • Crash

    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 px
  • Trivia Blitz

    Fast answers pay more.

    One question per round, four options. Correct doubles your stake, speed adds a bonus.

    private tablegame: "trivia"640 px
  • Spin 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 px
  • Scratch 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.

Drop-in files

Generated from the same packages, for projects that cannot add a dependency.

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.

Create your free account

Your points, history and level come with you. Pick a username and a password; it takes ten seconds.