Messaging APIs enable you to publish messages to live game servers from external services, allowing for real-time communication and coordination across your experience.
Overview
The Messaging Service allows you to:
- Send announcements to all servers
- Trigger events across your experience
- Coordinate server shutdowns or updates
- Broadcast real-time data updates
Messages can be up to 1,024 characters (1 KB) in size.
Import
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
Authentication
Messaging operations require the universe-messaging-service:publish scope in your API key:
import { configureServer } from 'rozod';
configureServer({
cloudKey: 'your_api_key_with_messaging_scope'
});
Publish a message
Send a message to all live servers subscribed to a topic:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
// Publish to a topic
await fetchApi(
v2.postCloudV2UniversesUniverseIdPublishMessage,
{
universe_id: '123456789',
},
{
topic: 'announcements',
message: 'Server maintenance in 5 minutes',
}
);
Topics
Topics are string identifiers up to 80 characters that servers subscribe to. Use descriptive topic names:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
// System announcements
await fetchApi(
v2.postCloudV2UniversesUniverseIdPublishMessage,
{ universe_id: '123456789' },
{
topic: 'system-announcements',
message: JSON.stringify({ type: 'maintenance', minutes: 5 }),
}
);
// Moderation events
await fetchApi(
v2.postCloudV2UniversesUniverseIdPublishMessage,
{ universe_id: '123456789' },
{
topic: 'moderation-events',
message: JSON.stringify({ action: 'ban', userId: 156 }),
}
);
// Economy updates
await fetchApi(
v2.postCloudV2UniversesUniverseIdPublishMessage,
{ universe_id: '123456789' },
{
topic: 'economy-updates',
message: JSON.stringify({ itemId: 'sword_001', newPrice: 500 }),
}
);
Receiving messages in-game
Servers subscribe to topics using Roblox’s MessagingService:
local MessagingService = game:GetService("MessagingService")
-- Subscribe to a topic
local connection = MessagingService:SubscribeAsync("announcements", function(message)
print("Received message:", message.Data)
-- Broadcast to all players
for _, player in pairs(game.Players:GetPlayers()) do
player:Kick(message.Data)
end
end)
Common use cases
Server announcements
Notify all players across all servers:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
async function broadcastAnnouncement(message: string) {
await fetchApi(
v2.postCloudV2UniversesUniverseIdPublishMessage,
{ universe_id: '123456789' },
{
topic: 'announcements',
message: JSON.stringify({
type: 'announcement',
text: message,
timestamp: Date.now(),
}),
}
);
}
await broadcastAnnouncement('New event starting now!');
Coordinated server shutdown
Gracefully shut down all servers:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
async function scheduleShutdown(minutes: number) {
// Warn players
await fetchApi(
v2.postCloudV2UniversesUniverseIdPublishMessage,
{ universe_id: '123456789' },
{
topic: 'shutdown',
message: JSON.stringify({
action: 'warning',
minutes: minutes,
}),
}
);
// Wait and then shut down
setTimeout(async () => {
await fetchApi(
v2.postCloudV2UniversesUniverseIdPublishMessage,
{ universe_id: '123456789' },
{
topic: 'shutdown',
message: JSON.stringify({
action: 'shutdown',
}),
}
);
}, minutes * 60 * 1000);
}
await scheduleShutdown(5);
Real-time data sync
Sync external data to all servers:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
async function syncGameData(data: any) {
await fetchApi(
v2.postCloudV2UniversesUniverseIdPublishMessage,
{ universe_id: '123456789' },
{
topic: 'data-sync',
message: JSON.stringify({
type: 'update',
data: data,
version: '1.0.0',
}),
}
);
}
const gameConfig = {
maxPlayers: 50,
eventActive: true,
specialItems: ['item1', 'item2'],
};
await syncGameData(gameConfig);
Player moderation
Notify servers about moderation actions:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
async function banPlayer(userId: number, reason: string) {
await fetchApi(
v2.postCloudV2UniversesUniverseIdPublishMessage,
{ universe_id: '123456789' },
{
topic: 'moderation',
message: JSON.stringify({
action: 'ban',
userId: userId,
reason: reason,
timestamp: Date.now(),
}),
}
);
}
await banPlayer(156, 'Violation of terms of service');
Messages are strings, but you can send structured data using JSON:
import { fetchApi } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
interface GameEvent {
type: 'boss-spawn' | 'treasure-drop' | 'double-xp';
data: any;
duration?: number;
}
async function triggerEvent(event: GameEvent) {
await fetchApi(
v2.postCloudV2UniversesUniverseIdPublishMessage,
{ universe_id: '123456789' },
{
topic: 'game-events',
message: JSON.stringify(event),
}
);
}
await triggerEvent({
type: 'boss-spawn',
data: {
bossId: 'dragon_001',
location: 'volcano',
health: 10000,
},
duration: 300,
});
Error handling
Handle common errors when publishing messages:
import { fetchApi, isAnyErrorResponse } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
async function publishMessageSafely(topic: string, message: string) {
// Validate message size
if (message.length > 1024) {
throw new Error('Message exceeds 1KB limit');
}
// Validate topic length
if (topic.length > 80) {
throw new Error('Topic exceeds 80 character limit');
}
const result = await fetchApi(
v2.postCloudV2UniversesUniverseIdPublishMessage,
{ universe_id: '123456789' },
{ topic, message }
);
if (isAnyErrorResponse(result)) {
console.error('Failed to publish message:', result.message);
throw new Error(result.message);
}
console.log('Message published successfully');
}
Rate limits
Messaging Service has the following limits:
- Published messages: 150 + 60 * (number of servers) per minute
- Message size: 1,024 characters (1 KB)
- Topic length: 80 characters
Exceeding rate limits will result in 429 Too Many Requests errors. Implement exponential backoff for retries.
Best practices
Use structured messages
// Good: Structured JSON
const message = JSON.stringify({
type: 'announcement',
priority: 'high',
text: 'Server restarting soon',
});
// Bad: Unstructured string
const message = 'Server restarting soon';
Include timestamps
const message = JSON.stringify({
event: 'update',
timestamp: Date.now(),
data: { version: '2.0' },
});
Handle failures gracefully
import { fetchApi, isAnyErrorResponse } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
async function publishWithRetry(
topic: string,
message: string,
maxRetries = 3
) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const result = await fetchApi(
v2.postCloudV2UniversesUniverseIdPublishMessage,
{ universe_id: '123456789' },
{ topic, message }
);
if (!isAnyErrorResponse(result)) {
return; // Success
}
console.warn(`Attempt ${attempt} failed:`, result.message);
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error(`Failed to publish after ${maxRetries} attempts`);
}
Use topic namespacing
// Good: Namespaced topics
const topics = {
system: 'system.announcements',
events: 'game.events',
moderation: 'moderation.actions',
economy: 'economy.updates',
};
// Bad: Generic topics
const topics = ['topic1', 'topic2', 'topic3'];
Complete example
import { fetchApi, configureServer } from 'rozod';
import { v2 } from 'rozod/lib/opencloud';
// Setup
configureServer({ cloudKey: process.env.ROBLOX_CLOUD_KEY });
interface ServerMessage {
type: string;
data: any;
timestamp: number;
}
class MessagingClient {
constructor(private universeId: string) {}
async publish(topic: string, data: any): Promise<void> {
const message: ServerMessage = {
type: topic,
data: data,
timestamp: Date.now(),
};
const messageStr = JSON.stringify(message);
if (messageStr.length > 1024) {
throw new Error('Message too large');
}
await fetchApi(
v2.postCloudV2UniversesUniverseIdPublishMessage,
{ universe_id: this.universeId },
{ topic, message: messageStr }
);
}
async broadcastAnnouncement(text: string): Promise<void> {
await this.publish('announcements', { text });
}
async triggerEvent(eventType: string, eventData: any): Promise<void> {
await this.publish('game-events', { type: eventType, ...eventData });
}
}
// Usage
const client = new MessagingClient('123456789');
await client.broadcastAnnouncement('New feature available!');
await client.triggerEvent('boss-spawn', { bossId: 'dragon', location: 'cave' });