{"record":{"id":"b4b1ad0c59b0a3ed","repo":"RocketChat/Rocket.Chat","slug":"invalid-room-b4b1ad","errorCode":null,"errorMessage":"invalid-room","messagePattern":"invalid-room","errorType":"exception","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"apps/meteor/server/api/v1/omnichannel/message.ts","lineNumber":38,"sourceCode":"import { settings } from '../../../settings';\nimport { getPaginationItems } from '../../lib/getPaginationItems';\nimport { isWidget } from '../../lib/isWidget';\n\nAPI.v1.addRoute(\n\t'livechat/message',\n\t{ validateParams: isPOSTLivechatMessageParams },\n\t{\n\t\tasync post() {\n\t\t\tconst { token, rid, agent, msg } = this.bodyParams;\n\n\t\t\tconst guest = await findGuest(token);\n\t\t\tif (!guest) {\n\t\t\t\tthrow new Error('invalid-token');\n\t\t\t}\n\n\t\t\tconst room = await findRoom(token, rid);\n\t\t\tif (!room) {\n\t\t\t\tthrow new Error('invalid-room');\n\t\t\t}\n\n\t\t\tif (!room.open) {\n\t\t\t\tthrow new Error('room-closed');\n\t\t\t}\n\n\t\t\tif (\n\t\t\t\tsettings.get('Livechat_enable_message_character_limit') &&\n\t\t\t\tmsg.length > parseInt(settings.get('Livechat_message_character_limit'))\n\t\t\t) {\n\t\t\t\tthrow new Error('message-length-exceeds-character-limit');\n\t\t\t}\n\n\t\t\tconst _id = this.bodyParams._id || Random.id();\n\n\t\t\tconst messageToSend = {\n\t\t\t\tguest,\n\t\t\t\tmessage: {","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/api/v1/omnichannel/message.ts#L20-L56","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","If the visitor was re-registered, create a new room with the new token instead of reusing the old rid.","Debug by fetching the room with token only (no rid) and comparing the returned room's _id to the rid you sent."],"exampleFix":"// before: rid from an old session, token re-registered -> invalid-room\nawait post('/api/v1/livechat/message', { token, rid: oldRid, msg });\n\n// after: keep token+rid as a pair\nconst { room } = await post('/api/v1/livechat/room', { token }); // room._id matches this token\nconst rid = room._id;\nawait post('/api/v1/livechat/message', { token, rid, msg });","handlingStrategy":"validation","validationCode":"// Ensure rid belongs to this token before sending\nconst room = await getRoom(token); // resolves room by visitor token\nif (!room || room._id !== rid) {\n  throw new Error('rid/token mismatch: re-open a room for this visitor');\n}\nawait post('/api/v1/livechat/message', { token, rid, msg });","typeGuard":"function isRoomForToken(room: unknown, token: string): room is { _id: string; v: { token: string } } {\n  return typeof room === 'object' && room !== null && (room as any)?.v?.token === token;\n}","tryCatchPattern":"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; }","preventionTips":["Remember the check is rid AND token pairing, not just existence.","Always obtain rid from the same registration flow that issued the token.","After re-registering a visitor, create a fresh room instead of reusing the old rid."],"tags":["omnichannel","livechat","message","room","visitor-token","rest-api"],"backgroundTag":"resource-not-found","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}