RocketChat/Rocket.Chat · error · Error

invalid-room

Error message

invalid-room

What it means

POST /api/v1/livechat/message resolves the room via findRoom(token, rid) → LivechatRooms.findOneByIdAndVisitorToken(rid, token). It returns null not only when the rid doesn't exist but also when the room exists and belongs to a different visitor token; either case throws 'invalid-room'. The check is a pairing check: rid AND token must match the same room.

Solutions

  1. Always take rid and token from the same flow: the response of POST /api/v1/livechat/room gives the rid bound to that visitor's token.
  2. If the visitor was re-registered, create a new room with the new token instead of reusing the old rid.
  3. Debug by fetching the room with token only (no rid) and comparing the returned room's _id to the rid you sent.

Example fix

// before: rid from an old session, token re-registered -> invalid-room
await post('/api/v1/livechat/message', { token, rid: oldRid, msg });

// after: keep token+rid as a pair
const { room } = await post('/api/v1/livechat/room', { token }); // room._id matches this token
const rid = room._id;
await post('/api/v1/livechat/message', { token, rid, msg });
Defensive patterns

Strategy: validation

Validate before calling

// Ensure rid belongs to this token before sending
const room = await getRoom(token); // resolves room by visitor token
if (!room || room._id !== rid) {
  throw new Error('rid/token mismatch: re-open a room for this visitor');
}
await post('/api/v1/livechat/message', { token, rid, msg });

Type guard

function isRoomForToken(room: unknown, token: string): room is { _id: string; v: { token: string } } {
  return typeof room === 'object' && room !== null && (room as any)?.v?.token === token;
}

Try / catch

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

Prevention

When it happens

Trigger: Correct rid with a token from another visitor/session (e.g. after re-registering the visitor but keeping the old rid); mistyped rid; room deleted; token/rid taken from different environments.

Common situations: Visitor re-registration produced a new token while the client kept the previous room's rid; multiple tabs/widgets sharing a rid but not a token; hand-assembled requests mixing fixture ids.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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

Appendix: source

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

import { settings } from '../../../settings';
import { getPaginationItems } from '../../lib/getPaginationItems';
import { isWidget } from '../../lib/isWidget';

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: {

View on GitHub (pinned to b2c16d5842)