moymo,Docs
Skip to content
Games: the Moymo SDK

Your game's server ​

If your game has a server (a multiplayer backend, a dedicated server, a service that keeps accounts), it can report achievements and stats to Moymo itself. A player cannot fake what your server sends, so this is the way to report what must be real. See Fair play for when to use it.

You need nothing new in the game except one call: the game asks Moymo for a ticket and gives it to your server, so your server knows which Moymo player it is talking to.

1. Your game ──► Moymo for Windows: "a ticket, please" ──► Moymo
2. Your game ──► your server: the ticket
3. Your server ──► Moymo: "whose ticket is this?"  ──►  player pl_8kq2…
4. Your server ──► Moymo: "pl_8kq2… unlocked champion"
  1. Make a server key. In the Developer Portal, open your game, the Server keys tab, New server key. Name it after the server that will use it ("EU servers"), and pick Sandbox or Live. The key is shown once: Copy it into your server's configuration. A sandbox key starts with mk_test_, a live key with mk_live_.
  2. Ask for a ticket in the game. When the player connects to your server, request a ticket and send it along (see below).
  3. Verify the ticket on your server. Your server sends the ticket to Moymo and gets the player's id back. Keep the id with your own account for that player: it never changes.
  4. Report. Unlock achievements and set stats for that player id, whenever your server decides they are earned.

In the game: request a ticket ​

Moymo for Windows

Players' tickets come from Moymo for Windows, from its next release on. Until then, test with the tickets of moymo dev (below).

The ticket arrives a moment later, on the game thread. It is valid for 5 minutes and can be verified once, so request a new one each time the player connects.

Request a ticket
csharp
using Moymo;

Identity.RequestTicket(result =>
{
    if (result.Status == TicketStatus.Ok)
        myServer.SendMoymoTicket(result.Ticket);   // your own protocol
});
cpp
moymo::Identity::RequestTicket([](const moymo::TicketResult& result) {
    if (result.status == moymo::TicketStatus::Ok) {
        MyServer::SendMoymoTicket(result.ticket);   // your own protocol
    }
});

In Unreal Engine, the Blueprint node Request Moymo Ticket does the same, with On Ticket.

StatusMeans
OkHere is the ticket.
NotSignedInMoymo for Windows is not running, or nobody is signed in to it. Play on without Moymo.
NotAllowedThe player has not allowed any module for your game.
UnavailableMoymo could not be reached. Try again later.

A ticket says nothing about the player but who they are for your game: no name, no e-mail.

On your server: verify the ticket ​

RequestPOST/tickets/verify
bash
curl https://moymo.net/api/server/v1/tickets/verify \
  -H "Authorization: Bearer $MOYMO_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ticket": "mt_2f9c…"}'
js
const response = await fetch('https://moymo.net/api/server/v1/tickets/verify', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.MOYMO_SERVER_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ ticket })
})
if (!response.ok) {
  const problem = await response.json()
  throw new Error(`${response.status} ${problem.code}: ${problem.message}`)
}
const { player } = await response.json()
csharp
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
    new("Bearer", Environment.GetEnvironmentVariable("MOYMO_SERVER_KEY"));

var response = await http.PostAsJsonAsync(
    "https://moymo.net/api/server/v1/tickets/verify", new { ticket });
response.EnsureSuccessStatusCode();
var verified = await response.Content.ReadFromJsonAsync<VerifiedTicket>();
// record VerifiedTicket(string Player, string Environment, DateTimeOffset ExpiresAt);
python
import os
import requests

response = requests.post(
    "https://moymo.net/api/server/v1/tickets/verify",
    headers={"Authorization": f"Bearer {os.environ['MOYMO_SERVER_KEY']}"},
    json={"ticket": ticket},
)
response.raise_for_status()
player = response.json()["player"]
Response200OKJSON
json
{
  "player": "pl_8kq2m4x7t1v9c3b5n6z0r2w4y8",
  "environment": "live",
  "expiresAt": "2026-11-06T12:05:00Z"
}

player is the same for this person in your game every time, and another game sees a different id for them. Store it with your own account for the player. If the player withdraws their consent and later allows your game again, they get a new id.

A ticket that is wrong, expired or already used answers 400 with TICKET_INVALID, TICKET_EXPIRED or TICKET_USED. Ask the game for a new one.

Report an achievement ​

RequestPUT/players/{player}/achievements/
bash
curl -X PUT https://moymo.net/api/server/v1/players/pl_8kq2m4x7t1v9c3b5n6z0r2w4y8/achievements/champion \
  -H "Authorization: Bearer $MOYMO_SERVER_KEY"
js
await fetch(`https://moymo.net/api/server/v1/players/${player}/achievements/champion`, {
  method: 'PUT',
  headers: { Authorization: `Bearer ${process.env.MOYMO_SERVER_KEY}` }
})
csharp
await http.PutAsync(
    $"https://moymo.net/api/server/v1/players/{player}/achievements/champion", null);
python
requests.put(
    f"https://moymo.net/api/server/v1/players/{player}/achievements/champion",
    headers={"Authorization": f"Bearer {os.environ['MOYMO_SERVER_KEY']}"},
)
Response200OKJSON
json
{ "unlocked": true, "firstTime": true, "unlockedAt": "2026-11-06T12:01:13Z" }

Unlocking twice is harmless: the second answer says "firstTime": false. So when you are not sure a call arrived, send it again.

Report a stat ​

RequestPUT/players/{player}/stats/Shell
bash
curl -X PUT https://moymo.net/api/server/v1/players/pl_8kq2m4x7t1v9c3b5n6z0r2w4y8/stats/season_wins \
  -H "Authorization: Bearer $MOYMO_SERVER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"value": 10}'
Response200OKJSON
json
{ "value": 10, "unlocked": ["champion"] }

Send the stat's whole value as your server knows it, not the change. Moymo keeps it as its kind says: a counter never goes down, a max keeps the highest, a min the lowest, a flag stays true. unlocked lists the achievements whose rules the new value met.

A set stat takes one member at a time: POST /players/{player}/stats/{stat}/members with {"symbol": "smithy"}.

When the player has not allowed it ​

If the player has not allowed achievements for your game (stats count as part of achievements), the answer is 403 with NOT_ALLOWED. Nothing is kept. Send it again the next time the player connects: every call is safe to repeat, and your server still knows what the player earned.

Testing without Moymo for Windows ​

moymo dev hands out tickets too. They work only with an mk_test_ key, and their player ids start with pl_dev_. For such a player, Moymo checks every call as it would for a real one (the key, the names, the values, the rules) and answers the same way, but keeps nothing. So you can test your server before anyone has Moymo for Windows.

Server SDKs ​

The calls above are plain HTTPS, so any language can make them. For Node.js and .NET there are small libraries that also retry politely after 429 and 503. They are in the SDK's repository, in servers/node and servers/dotnet; they are not on npm or NuGet yet.

Unlock with a server SDK
js
import { MoymoServer } from '@moymo/server'

const moymo = new MoymoServer(process.env.MOYMO_SERVER_KEY)  // mk_test_… or mk_live_…
const { player } = await moymo.verifyTicket(ticket)
await moymo.unlockAchievement(player, 'champion')
await moymo.setStat(player, 'season_wins', 10)
csharp
using Moymo.Server;

var moymo = new MoymoServer(Environment.GetEnvironmentVariable("MOYMO_SERVER_KEY")
    ?? throw new InvalidOperationException("MOYMO_SERVER_KEY is not set"));
var verified = await moymo.VerifyTicketAsync(ticket);
await moymo.UnlockAchievementAsync(verified.Player, "champion");
await moymo.SetStatAsync(verified.Player, "season_wins", 10);

Every call is in the Server API reference.

The wiki is for Moymo's members and developers. Something wrong or missing? Use "Suggest a change" on any page.