RocketChat/Rocket.Chat · error · Error

room-closed

Error message

room-closed

What it means

POST /api/v1/livechat/message rejects with 'room-closed' when the token and rid resolve to a room whose `open` flag is false. The conversation was already closed (agent closed it, visitor closed it via Omnichannel_allow_visitors_to_close_conversation, or auto-close/inactivity) and the server refuses new messages on closed rooms.

Solutions

  1. React to the chat-closed callback (widget receives onChatClosed / the finish event) and stop further sends; show the finished/registration state instead.
  2. If the visitor should continue, start a new chat (POST /api/v1/livechat/room with the token) and send to the new rid.
  3. If closing was unintended, the agent can reopen the conversation, after which sends succeed.

Example fix

// before
widget.on('send', (msg) => postMessage(rid, msg)); // fires after close -> room-closed

// after
let open = true;
widget.on('chat-closed', () => { open = false; });
widget.on('send', async (msg) => {
  if (!open) {
    ({ rid } = await post('/api/v1/livechat/room', { token })); // new conversation
    open = true;
  }
  await postMessage(rid, msg);
});
Defensive patterns

Strategy: validation

Validate before calling

// Track room open state and re-open before sending
let roomOpen = true;
onChatClosed(() => { roomOpen = false; });
if (!roomOpen) {
  const { room } = await post('/api/v1/livechat/room', { token });
  rid = room._id;
  roomOpen = true;
}
await post('/api/v1/livechat/message', { token, rid, msg });

Type guard

function isOpenRoom(room: unknown): room is { open: true; _id: string } {
  return typeof room === 'object' && room !== null && (room as any).open === true;
}

Try / catch

try { await postMessage(token, rid, msg); } catch (e) { if (e.message !== 'room-closed') throw e; const { room } = await post('/api/v1/livechat/room', { token }); rid = room._id; await postMessage(token, rid, msg); }

Prevention

When it happens

Trigger: Widget sends a message queued offline after the conversation ended; race where the close event arrives while a send is in flight; integration posting to a rid captured before the chat was closed.

Common situations: Visitor types a message just as the agent closes the chat; widget state not updated on the chat-close callback; automated flows posting to closed rooms during off-hours.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/826742716d8d3e5f. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/server/api/v1/omnichannel/message.ts:42

API.v1.addRoute(
	'livechat/message',
	{ validateParams: isPOSTLivechatMessageParams },
	{
		async post() {
			const { token, rid, agent, msg } = this.bodyParams;

			const guest = await findGuest(token);
			if (!guest) {
				throw new Error('invalid-token');
			}

			const room = await findRoom(token, rid);
			if (!room) {
				throw new Error('invalid-room');
			}

			if (!room.open) {
				throw new Error('room-closed');
			}

			if (
				settings.get('Livechat_enable_message_character_limit') &&
				msg.length > parseInt(settings.get('Livechat_message_character_limit'))
			) {
				throw new Error('message-length-exceeds-character-limit');
			}

			const _id = this.bodyParams._id || Random.id();

			const messageToSend = {
				guest,
				message: {
					_id,
					rid,
					msg,
					token,

View on GitHub (pinned to b2c16d5842)