-
Notifications
You must be signed in to change notification settings - Fork 2
Notifications
The server is capable of sending notifications. These notifications can be various, but mostly they contain information about certain events happening and registered on the server side such as player has completed a daily task or a new voting has been started.
For the notifications mechanism to work a MQTT protocol is used. The MQTT protocol has 2 key components: publisher (=server) and subscriber (=client). The publisher can publish a message (= send notification) and the subscribers can listen and wait for the message to come.
The subscribers can listen to messages by using topics (= endpoints). When some message come to a specific topic, subscriber gets it. Topic is a unique string usually containing slashes (/) - separators. The separators are used for organizing the structure of the topics overall.
Subscriber can listen to a specific topic and also it can use a wildcard, for example to subscribe to all of the topics starting with a specific string, i.e. with "/clan" to listen to all notifications related to clans.
The messages coming from a topics can have any format, there are not restrictions for that.
More detailed description about MQTT can be found here
The notifications mechanism with topics structure and more built on the server can be found here
Below are presented all the currently implemented topics (more to come).
Topic URL: /clan/{clanId}/jukebox/song/update
Where:
- {clandId} represents the clanId
When triggers
- player adds a song to an empty Jukebox, it starts playing immediately
- the current song finishes playing and the next song is pulled from the queue
Payload example
{
topic: `/clan/7a019315ee393ee3d344e197/jukebox/song/update`,
song: {
songId: "67dee567c34dc94aa4f09277", // Client-side identifier for the song
startedAt: 1761207021503 // timestamp in milliseconds
}
}
Subscription: /clan/{clanId}/jukebox/song/update
Topic URL: /clan/{clanId}/jukebox/playlist/update
When triggers
- a player adds a new song (it is either appended to the queue or becomes the current song)
- a song starts playing (updating the queue and the current playing song)
- a player removes a song they added from the queue
- the last song finishes playing, clearing the playlist
Payload example
{
topic: "/clan/7a019315ee393ee3d344e197/jukebox/playlist/update",
playlist: {
clanId: "68e7777139519eaf400a0e3d",
currentSong: {
id: "67dee567c34dc94aa4f09275",
songId: "67dee567c34dc94aa4f09277",
songDurationSeconds: 123,
playerId: "6a043d92aaacc61e8681495a",
startedAt: 1761207021503
},
songQueue: [
{
id: "67dee567c34dc94aa4f09271",
songId: "67dee567c34dc94aa4f09277",
songDurationSeconds: 123,
playerId: "6a043d92aaacc61e8681495a"
}
]
}
}
Subscription: /clan/${clanId}/jukebox/playlist/update
Topic URL: /clan/{clanId}/voting/{votingType}/{status}
Where:
-
{clanId}isvoting.organizer.clan_id -
{votingType}is one value fromVotingType -
{status}is one ofnew,update,end
Current voting types:
flea_market_sell_itemflea_market_buy_itemchange_item_priceshop_buy_itemset_clan_roleclan_governance_update
When triggers
- when a new voting begins
- when voting is updated (a new vote is given)
- when voting is completed
- if there's an error in voting
Payload
All voting notifications use this payload shape:
{
topic: `/clan/${clanId}/voting/${votingId}`,
status: 'new' | 'update' | 'end',
voting_id: string,
type: VotingType,
entity: unknown,
organizer?: PlayerDto,
voter?: PlayerDto,
votes?: Vote[],
endedAt?: Date
}
Recommended frontend flow:
- Subscribe to
/clan/{clanId}/voting/+/+. - Use
payload.statusto route lifecycle behavior. - Use
payload.voting_idas the stable voting id. - Use
payload.typeto narrowpayload.entity. - Treat
payload.topicas the voting-instance channel/id:/clan/{clanId}/voting/{votingId}.
payload.topic is a voting-instance identifier for the frontend. It is not the
MQTT broker topic. The MQTT broker topic remains
/clan/{clanId}/voting/{votingType}/{status}.
Topic URLs:
Method | MQTT topic path | Description | Payload
-------------------------------------------------------------------------------------------------------------------------------------------
taskReceived | /player/{player_id}/daily_task/{task_type}/new | Player got new daily task. | TTask
taskUpdated | /player/{player_id}/daily_task/{task_type}/update | Daily task was advanced (some part of a task). | TTask
taskError | /player/{player_id}/daily_task/{task_type}/error | Error in task. | APIError
taskCompleted | /player/{player_id}/daily_task/{task_type}/end | Player completed a task. | TTask
taskCompletedForClan | /clan/{clan_id}/daily_task/{task_type}/end | Member of a clan completed a clan's task | { task: TTask, completedByPlayerId: string }`
milestoneReached | /clan/{clan_id}/daily_task/milestone/update | A clan achieved new milestone | { task: TTask, completedByPlayerId: string, reachedMilestones: number[] }
Where {task_type} is a task from the following enum:
When triggers
- when a player successfully reserves a new daily task from the clan's available tasks
- when a player progresses in reserved task (e.g. increments progress but does not finish it yet)
- if an error occurs during task execution or validation
- when player's task is fully completed (
amountLeftreaches0) - when a clan member successfully completes an active task
- when clan's progression milestones are unlocked as a result of completing a daily task
Payload examples
- reserveTask:
{
"task": {
"_id": "665af23e5e982f0013aa334b",
"clan_id": "665af23e5e982f0013aa1122",
"player_id": "665af23e5e982f0013aa4455",
"title": { "fi": "Kirjoita klaanichattiin viesti" },
"type": "write_chat_message_clan",
"points": 50,
"coins": 100,
"startedAt": "2026-06-08T07:44:24.000Z",
"amount": 1,
"amountLeft": 0,
"timeLimitMinutes": 2
},
"completedByPlayerId": "665af23e5e982f0013aa4455"
}
- a milestone reached:
{
"task": {
"_id": "665af23e5e982f0013aa334b",
"clan_id": "665af23e5e982f0013aa1122",
"player_id": "665af23e5e982f0013aa4455",
"title": { "fi": "Kirjoita klaanichattiin viesti" },
"type": "write_chat_message_clan",
"points": 50,
"coins": 100,
"startedAt": "2026-06-08T07:44:24.000Z",
"amount": 1,
"amountLeft": 0,
"timeLimitMinutes": 2
},
"completedByPlayerId": "665af23e5e982f0013aa4455",
"reachedMilestones": [1, 2] // List of milestones reached
}
- error case:
{
"name": "APIError",
"reason": "Unexpected error",
"statusCode": 500,
"message": "Error description",
"field": null,
"value": null,
"additional": null
}
- successfully completed task:
{
"task": {
"_id": "665af23e5e982f0013aa334b",
"clan_id": "665af23e5e982f0013aa1122",
"player_id": "665af23e5e982f0013aa4455",
"title": { "fi": "Kirjoita klaanichattiin viesti" },
"type": "write_chat_message_clan",
"points": 50,
"coins": 100,
"startedAt": "2026-06-08T07:44:24.000Z",
"amount": 1,
"amountLeft": 0,
"timeLimitMinutes": 2
},
"completedByPlayerId": "665af23e5e982f0013aa4455"
}
-
Subscriptions:
- Subscribe to
/player/{playerId}/daily_task/+/+on player profile/app initialization. - Subscribe to
/clan/{clanId}/daily_task/+/+upon entering the clan lobby or relevant dashboard.
- Subscribe to
-
Personal HUD & Notifications:
- On
/new, add the task to the player's active daily task slot. - On
/update, animate the task's progress bar in the UI. - On
/end, trigger a completion celebration/reward overlay and clear the active task slot.
- On
-
Clan Activity Feed:
- On
/clan/{clanId}/daily_task/{taskType}/end, display a notification in the clan feed (e.g., "User X completed a task!"). - On
/clan/{clanId}/daily_task/milestone/update, update the clan progression progress bar and show milestone-unlocked popups.
- On
Topic URL: /clan/{clanId}/member/{event}
Where:
-
clanIdis the MongoDB id of the clan - event is
joinorleave
When triggers
- when a new member joins a clan
- when a member leaves a clan
- when a member is removed from a clan
Payload example: (join)
{
"topic": "/clan/6a19adf07c176746090d52a1/member/join",
"playerId": "6a19adf07c176746090d52a2",
"event": "join",
"ts": 1719320000000
}
Subscription: /clan/{clanId}/member/+
The backend publishes matchmaking invite and match lifecycle events through MQTT.
The implementation lives in src/matchmaking/matchmaking.notifier.ts, and the
state changes that trigger the events are handled by
src/matchmaking/matchmaking.service.ts.
Matchmaking topics currently do not start with /. Frontend subscriptions must
use the exact topic strings shown below.
A frontend client should subscribe to player-specific channels for the logged-in player and to match-specific channels after a match has been created.
/matchmaking/invites/player/{playerId}
/matchmaking/matches/player/{playerId}
/match/{matchId}
Where:
-
{playerId}is the authenticated player's player id. -
{matchId}is the match id returned in aMATCH_FOUNDorMATCH_STARTEDpayload.
All matchmaking MQTT messages use the same wrapper shape:
{
type: string,
payload: unknown
}The type field identifies the event. The payload field contains either a
MatchmakingInviteDto or a MatchmakingMatchDto, depending on the event.
/matchmaking/invites/player/{playerId}
INVITE_UPDATED
- A player creates a matchmaking invite.
- A player joins an invite.
- An invite is moved forward by matchmaking and its status changes.
- An invite is matched into an active match.
- An invite is cancelled by its owner.
The notification is sent to every real player currently attached to the invite. Bots do not receive MQTT notifications.
{
type: 'INVITE_UPDATED',
payload: {
id: string,
matchType: 'RANDOM' | 'CLAN' | 'CUSTOM',
status: 'OPEN' | 'READY' | 'QUEUED' | 'MATCHED' | 'CANCELLED',
ownerPlayerId: string,
clanId?: string,
roomId?: string,
players: string[],
bots: [
{
botId: string,
displayName: string,
isBot: true
}
],
teamSize: 1 | 2,
allowBots: boolean,
createdAt: string,
updatedAt: string,
readyAt?: string,
matchId?: string
}
}/matchmaking/matches/player/{playerId}
MATCH_FOUND
- Two READY
RANDOMinvites are paired. - Two READY
CLANinvites from different clans are paired. - A
CLANinvite reaches the opponent timeout and receives a bot opponent team. - A
CUSTOMinvite starts a match from its room settings.
This notification is sent once to each real player in the created match. Bots are included in the match payload but do not receive player-specific notifications.
{
type: 'MATCH_FOUND',
payload: MatchmakingMatchDto
}MatchmakingMatchDto has this shape:
{
id: string,
matchType: 'RANDOM' | 'CLAN' | 'CUSTOM',
status: 'ACTIVE' | 'FINISHED',
teamSize: 1 | 2,
teams: [
{
side: 'A' | 'B',
clanId?: string,
players: [
{
playerId: string,
isBot: false
}
],
bots: [
{
botId: string,
displayName: string,
isBot: true
}
]
}
],
startedAt: string,
finishedAt?: string,
result?: {
winningSide: 'A' | 'B'
}
}/match/{matchId}
MATCH_STARTED
- Any active match is created and persisted in Redis.
The payload is the same MatchmakingMatchDto that is sent with MATCH_FOUND.
This topic is useful for clients that have already switched from the player-level
matchmaking channel to a match-level game channel.
{
type: 'MATCH_STARTED',
payload: MatchmakingMatchDto
}/match/{matchId}
MATCH_FINISHED
- A participant finishes an active match through
POST /matchmaking/matches/{matchId}/finish. - The backend accepts the finish request, updates leaderboards, stores the finished match with the finished-match TTL, and invalidates leaderboard caches.
The payload contains the finished match with status: 'FINISHED', finishedAt,
and result.
{
type: 'MATCH_FINISHED',
payload: MatchmakingMatchDto
}Recommended frontend flow:
- Subscribe to
/matchmaking/invites/player/{playerId}after login. - Subscribe to
/matchmaking/matches/player/{playerId}while the player is in matchmaking. - Use
INVITE_UPDATEDto keep lobby and invite UI synchronized. - When
MATCH_FOUNDis received, readpayload.idand subscribe to/match/{matchId}. - Use
MATCH_STARTEDto initialize the match scene if the client did not enter from the player-specificMATCH_FOUNDevent. - Use
MATCH_FINISHEDto show the result screen and refresh leaderboard views.
- MQTT payloads are JSON strings produced with
JSON.stringify. - Invite and match data are stored in Redis, so clients should treat these events as real-time state notifications rather than durable history.
-
CLANmatch finishes update both clan leaderboards and personal leaderboards for participating clan players.RANDOMandCUSTOMfinishes update only personal leaderboards.