mssgs Game SDK
Laat je game de mssgs-client op dezelfde computer vinden, tonen wat de speler speelt met een Meedoen-knop, en controleren of ze lid zijn van jouw community — zonder ze hun vriendenlijst te laten weggeven.
Webhooks & Integraties
Berichten sturen en interactieve bots bouwen met webhooks en triggers.
MCP
Geef AI-assistenten lokale, gecontroleerde toegang tot je workspace.
Overzicht
De mssgs desktop-app draait een kleine lokale HTTP-bridge waar een game op dezelfde computer mee praat. Je game praat nooit met onze servers, ziet nooit een wachtwoord of token van het account, en kan nooit namens de speler iets posten. Hij praat met de kopie van mssgs waar de speler al is ingelogd, en die kopie bepaalt wat er wordt geantwoord.
Wat je ermee kunt:
- Detecteren dat mssgs geïnstalleerd is en er iemand is ingelogd.
- Uitlezen wie de speler is: user_guid, gebruikersnaam, avatar.
- Vragen "zit deze speler in community X?" en welke rol ze daar hebben.
- Een "Speelt …"-status publiceren met een Meedoen-knop voor anderen.
- Een join-overdracht ontvangen wanneer iemand die knop indrukt.
Zo min mogelijk prijsgeven, standaard
De scopes zijn met opzet ongelijk. Wil je alleen weten of iemand lid is van jouw community, dan vraag je membership.query en noem je zelf de server_guid: je krijgt ja/nee plus hun rollen daar, en leert niets over de rest van hun communities. De volledige lijst zit achter een aparte, hogere scope die de speler los moet goedkeuren.
De client vinden
De bridge luistert alleen op 127.0.0.1, op de eerste vrije poort in een klein bereik. Probeer ze op volgorde tot er één antwoordt: 7440, 7441, 7442, 7443. Ontwikkelbuilds van mssgs luisteren op 7540–7543, zodat een testbuild nooit de aanroepen van een echte game beantwoordt.
http://127.0.0.1:7440/mssgs/v1/hello
Geen token nodig, en het antwoord zegt niets over de speler — alleen dat mssgs er is en of iemand is ingelogd.
{
"product": "mssgs",
"api": 1,
"client": "desktop",
"version": "14.2.20015",
"platform": "darwin",
"signed_in": true,
"scopes": ["identity", "staff", "membership.query", "servers.list", "presence.write"]
}
Controleer product === "mssgs" en api voordat je verder gaat. Krijg je geen antwoord op alle vier de poorten, dan draait mssgs niet — bied dan gewoon je normale ervaring aan in plaats van de speler te laten wachten.
Scopes & privacy
De vijf scopes geven heel verschillende hoeveelheden weg. Dat is geen toeval: het is de hele opzet. Vraag van boven naar beneden zo min mogelijk.
| Scope | Wat het toestaat | Wat de speler prijsgeeft |
|---|---|---|
presence.write |
Tonen wat ze spelen | Niets. Deze scope schrijft alleen en leest geen enkel accountgegeven. |
identity |
Wie de speler is | user_guid, gebruikersnaam, weergavenaam, avatar-URL. |
staff |
Staff- / moderatorvlaggen | Twee booleans, bovenop identity. Apart, omdat een game die een naam toont niet hoeft te weten dat de speler communities modereert. |
membership.query |
Een community controleren die je al kent | Voor een server_guid die jij noemt: ja/nee, de naam, en de rollen die de speler daar heeft. Niets over andere communities. |
servers.list |
Alle communities waar ze in zitten | De volledige lijst: guids, namen, iconen en rollen. Dit is de dure — vraag hem alleen als je hem echt nodig hebt. |
De meeste games hebben er twee nodig
identity en presence.write dekken "wie ben je" en "laat zien wat je speelt" — samen goed voor vrijwel elke integratie. Voeg membership.query toe als je een beloning wilt koppelen aan lidmaatschap van jouw community. servers.list heb je vrijwel nooit nodig, en de speler ziet hem in het rood.
Lidmaatschap checken
Dit is het alternatief voor "geef me de hele lijst". Jij noemt de server_guid van jouw eigen community — die je toch al kent — en krijgt alleen daarover antwoord.
curl -H "Authorization: Bearer $TOKEN" \
"http://127.0.0.1:7440/mssgs/v1/membership?server_guid=ca94ecc…f4g02"
# lid:
# {"server_guid":"ca94ecc…","member":true,"name":"Acme Fans","is_owner":false,
# "roles":[{"guid":"0aa32…","name":"Pro"}]}
# geen lid — en verder niets:
# {"server_guid":"…","member":false}
Een "nee" is precies dat en niets meer. Je kunt tot 10 guids per aanroep meegeven (herhaal server_guid of gebruik komma's); dan krijg je een results-array terug. De @everyone-groep zit nooit in roles: die geldt voor iedereen en zegt dus niets.
Een speelstatus tonen
Eén PUT zet de "Speelt …"-regel onder de naam van de speler, overal waar hun communities ze zien.
{
"name": "Space Raiders",
"details": "Sector 7",
"state": "In a raid",
"role": "Gunner",
"started_at": 1755859200000,
"party": { "size": 3, "max": 4, "kind": "party" },
"join": { "secret": "raid-42" }
}
Alleen name is verplicht. Het antwoord vertelt je hoe lang de status blijft staan en hoe vaak je moet heartbeaten:
{ "ok": true, "expires_in_ms": 90000, "heartbeat_every_ms": 30000 }
Heartbeat, of de status verdwijnt
Een status die 90 seconden geen teken van leven geeft, wordt vanzelf gewist. Dat is met opzet: crasht je game, dan blijft de speler niet uren "aan het spelen". Stuur elke 30 seconden een POST /mssgs/v1/activity/heartbeat, en DELETE /mssgs/v1/activity als je netjes afsluit.
Aantal spelers en rol
party.kind bepaalt welke zin er komt te staan, want dezelfde twee getallen betekenen niet hetzelfde. Een squad van vier is geen server met vier spelers erop.
kind |
Wordt getoond als | Waarvoor |
|---|---|---|
party (standaard) | 3 van 4 in de groep | een squad, crew of groep |
server | 4/100 spelers | een game server (FiveM, een community-server) |
lobby | 4/100 spelers | een lobby voor de match begint |
match | 4/100 spelers | een lopende match of ronde |
role (max 48 tekens) is waar de speler als speelt: een job, klasse of personage. Het krijgt een eigen veld in plaats van nog een zin in state, omdat het als label naast het aantal spelers wordt getoond.
details en state zijn elk maximaal 128 tekens, name maximaal 64. Regeleindes en stuurtekens worden eruit gehaald. Een icoon-URL wordt bewust niet ondersteund: die zou door elke client worden opgehaald die de regel toont, en dat maakt van een statusregel een baken dat elk lid van elke community van de speler bij jouw server meldt.
De Meedoen-knop
Zet een join-blok in je activiteit en anderen krijgen een Meedoen-knop naast de status. Er zijn twee manieren, en je kunt ze combineren.
1. Een secret — voor native games
Zet {"join":{"secret":"raid-42"}}. Drukt iemand op Meedoen, dan wordt dat secret afgeleverd bij hun eigen kopie van jouw game, op hun eigen computer — herkend aan hetzelfde game_id. Er wordt geen URL geopend en geen schema-handler aangeroepen. Jouw game haalt het op met:
{
"events": [
{ "seq": 1, "type": "join", "secret": "raid-42",
"from": { "user_guid": "62e377…", "username": "mssgs-test-1" } }
],
"cursor": 1
}
Poll met ?since=<cursor> zodat je elk event één keer ziet. Draait de game van de drukker niet, dan wordt er niets afgeleverd — bied dan gerust ook een URL aan.
2. Een https-URL — voor webgames en lobby-links
Zet {"join":{"url":"https://play.example.com/s/abc"}} en de knop opent die link. Alleen https wordt geaccepteerd. Een eigen schema (steam://, mygame://, file://) wordt geweigerd: dat blok komt op het scherm van elk lid terecht, en zo'n URL is een manier om op andermans computer een lokale handler aan te roepen met argumenten die jij hebt gekozen.
Alles in join is publiek
Het join-blok wordt uitgezonden naar iedereen die de status van de speler kan zien — dat is precies de bedoeling van een Meedoen-knop. Het is dus een lobbycode, geen inloggegeven. Zet er nooit iets in dat geheim moet blijven, en laat codes verlopen.
FiveM
FiveM heeft geen HTTP in de client-side Lua-runtime, dus een resource praat via NUI met de bridge — dat is een CEF-view, en die stuurt een Origin mee. De bridge accepteert die origins expliciet: https://cfx-nui-<resource> en het oudere nui://<resource>. Gewone webpagina's blijven geweigerd, en een pagina op het open web kan die origin niet claimen — de browser zet hem zelf.
-- De NUI-pagina doet het HTTP-werk; Lua stuurt alleen de gegevens.
CreateThread(function()
while true do
SendNUIMessage({
action = 'mssgs:publish',
players = GetActivePlayers and #GetActivePlayers() or 0,
maxPlayers = GetConvarInt('sv_maxclients', 100),
job = exports['qb-core'] and 'Police' or nil
})
Wait(30000) -- heartbeat: de status vervalt na 90 s
end
end)
const BASE = 'http://127.0.0.1:7440/mssgs/v1'; // probeer 7440-7443
let token = null;
async function authorize () {
const res = await fetch(`${BASE}/authorize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
game_id: 'fivem.lossantos.rp',
name: 'Los Santos Roleplay',
scopes: ['presence.write'] // meer heb je hier niet nodig
})
});
const started = await res.json();
if (started.status === 'approved') { return started.token; }
// De speler ziet nu het toestemmingsvenster in mssgs.
for (let i = 0; i < 180; i += 1) {
await new Promise((r) => { setTimeout(r, 1000); });
const poll = await (await fetch(`${BASE}/authorize/${started.request_id}`)).json();
if (poll.status === 'approved') { return poll.token; }
if (poll.status !== 'pending') { return null; }
}
return null;
}
window.addEventListener('message', async (event) => {
if (event.data.action !== 'mssgs:publish') { return; }
if (!token) { token = await authorize(); }
if (!token) { return; }
await fetch(`${BASE}/activity`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
body: JSON.stringify({
name: 'FiveM',
details: 'Los Santos Roleplay',
role: event.data.job, // "Police"
party: { size: event.data.players, max: event.data.maxPlayers, kind: 'server' },
join: { url: 'https://cfx.re/join/abc123' } // jullie cfx.re-link
})
});
});
Resultaat: Speelt FiveM — Los Santos Roleplay — 4/100 spelers — Police, met een Meedoen-knop die je cfx.re-link opent.
Vraag alleen presence.write
Voor een speelstatus heb je verder niets nodig — die scope leest helemaal niets. Wil je een in-game beloning koppelen aan lidmaatschap van jullie mssgs-community, voeg dan membership.query toe en noem je eigen server_guid; je komt dan nog steeds niets te weten over de andere communities van de speler.
Een server waar je op speelt is niet vanzelf te vertrouwen
Elke FiveM-server kan client-resources draaien, dus elke server waar iemand op komt kan om toestemming vragen. Dat is precies waarom er een venster tussen zit met de naam van de resource erin: de speler beslist, niet de server.
Endpoint-referentie
Basis-URL http://127.0.0.1:<poort>. Alles behalve de eerste drie vereist Authorization: Bearer <token>.
| Methode | Pad | Scope | Wat het doet |
|---|---|---|---|
| GET | /mssgs/v1/hello |
geen | Is mssgs aanwezig, wat spreekt het, en is er iemand ingelogd. De enige route zonder token, en hij zegt niets over de speler. |
| POST | /mssgs/v1/authorize |
geen | Vraag de speler om toestemming. Opent een venster in de app en geeft een request_id terug om te pollen. |
| GET | /mssgs/v1/authorize/:request_id |
geen | pending, approved (met het token), denied of expired. |
| GET | /mssgs/v1/me |
identity |
De ingelogde speler. Voegt is_staff / is_moderator alleen toe met de staff-scope. |
| GET | /mssgs/v1/membership |
membership.query |
Lidmaatschap van de server_guid-waarden die je meegeeft (maximaal 10, herhaald of komma-gescheiden). |
| GET | /mssgs/v1/servers |
servers.list |
Alle communities waar de speler in zit, met hun rollen. Directe berichten zitten er nooit bij. |
| PUT | /mssgs/v1/activity |
presence.write |
Publiceer het "Speelt …"-blok. Geeft de TTL terug en hoe vaak je moet heartbeaten. |
| POST | /mssgs/v1/activity/heartbeat |
presence.write |
Houd de gepubliceerde activiteit in leven zonder hem opnieuw te sturen. |
| DELETE | /mssgs/v1/activity |
presence.write |
Wis hem meteen, voor een nette afsluiting. |
| GET | /mssgs/v1/events |
presence.write |
Join-overdrachten bedoeld voor jouw game. Poll met ?since=<cursor>. |
| GET | /mssgs/v1/session |
geen | Wat dit token heeft: game_id, verleende scopes, en of er iemand is ingelogd. |
| DELETE | /mssgs/v1/session |
geen | Geef de toestemming terug. Zelfde effect als wanneer de speler hem intrekt in Instellingen. |
Foutcodes
Fouten komen terug als {"error":"CODE","message":"…"} met een passende HTTP-status.
| Code | Betekenis |
|---|---|
401 UNAUTHORIZED | Ontbrekend of onbekend token — autoriseer eerst. |
403 MISSING_SCOPE | De speler heeft die toestemming niet gegeven. Mogelijk heeft hij hem uitgevinkt. |
403 ORIGIN_NOT_ALLOWED | Het verzoek had een browser-Origin. Zie "Alleen native games" hieronder. |
409 NOT_SIGNED_IN | mssgs draait, maar er is niemand ingelogd. |
429 RATE_LIMITED | Meer dan 120 verzoeken per minuut van één game. |
400 INVALID_GAME_ID | game_id mag alleen letters, cijfers, punt, streepje of underscore bevatten. |
400 TOO_MANY_GUIDS | Maximaal 10 server_guid-waarden per membership-aanroep. |
Beveiliging
Alleen native games
Verzoeken met de Origin van een webpagina worden geweigerd met 403 ORIGIN_NOT_ALLOWED. Een willekeurige webpagina die kan detecteren dat je mssgs draait en een toestemmingsvenster kan openen, is een fingerprint- en phishingoppervlak — geen functie. Een native game stuurt helemaal geen Origin en heeft hier dus geen last van, en de ingebouwde browser van een game wordt met naam toegelaten — zie FiveM. Bouw je een browsergame, laat de speler dan meedoen via een join.url in plaats van rechtstreeks met de bridge te praten.
Wat de speler in handen houdt
- De speler kan de bridge uitzetten in Instellingen → Spelactiviteit; daarna kan geen enkele game mssgs meer zien.
- Elke goedgekeurde game staat daar met precies de rechten die hij heeft, wanneer hij voor het laatst actief was, en een Verwijderen-knop. Verwijderen werkt direct: het token is meteen dood.
- De bridge luistert alleen op 127.0.0.1 en nooit op het netwerk.
- Directe berichten worden nooit prijsgegeven — ook niet met servers.list.
- Er is per game een limiet van 120 verzoeken per minuut.
Goed burgerschap
- Vraag scopes pas wanneer je ze nodig hebt, niet allemaal bij de eerste start.
- Werkt zonder mssgs: de speler hoeft het niet te hebben.
- Wis je status als het spelen stopt, in plaats van te wachten tot de TTL verloopt.
- Behandel een geweigerde scope als een normale uitkomst, niet als een fout.
Vragen?
Bouw je iets met de Game SDK en loop je vast? Laat het ons weten via de contactpagina.