{"record":{"id":"826742716d8d3e5f","repo":"RocketChat/Rocket.Chat","slug":"room-closed","errorCode":null,"errorMessage":"room-closed","messagePattern":"room-closed","errorType":"exception","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"apps/meteor/server/api/v1/omnichannel/message.ts","lineNumber":42,"sourceCode":"API.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: {\n\t\t\t\t\t_id,\n\t\t\t\t\trid,\n\t\t\t\t\tmsg,\n\t\t\t\t\ttoken,","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/api/v1/omnichannel/message.ts#L24-L60","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["React to the chat-closed callback (widget receives onChatClosed / the finish event) and stop further sends; show the finished/registration state instead.","If the visitor should continue, start a new chat (POST /api/v1/livechat/room with the token) and send to the new rid.","If closing was unintended, the agent can reopen the conversation, after which sends succeed."],"exampleFix":"// before\nwidget.on('send', (msg) => postMessage(rid, msg)); // fires after close -> room-closed\n\n// after\nlet open = true;\nwidget.on('chat-closed', () => { open = false; });\nwidget.on('send', async (msg) => {\n  if (!open) {\n    ({ rid } = await post('/api/v1/livechat/room', { token })); // new conversation\n    open = true;\n  }\n  await postMessage(rid, msg);\n});","handlingStrategy":"validation","validationCode":"// Track room open state and re-open before sending\nlet roomOpen = true;\nonChatClosed(() => { roomOpen = false; });\nif (!roomOpen) {\n  const { room } = await post('/api/v1/livechat/room', { token });\n  rid = room._id;\n  roomOpen = true;\n}\nawait post('/api/v1/livechat/message', { token, rid, msg });","typeGuard":"function isOpenRoom(room: unknown): room is { open: true; _id: string } {\n  return typeof room === 'object' && room !== null && (room as any).open === true;\n}","tryCatchPattern":"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); }","preventionTips":["Handle the chat-closed/finish callback and flip UI to a finished state that blocks sends.","Disable the composer immediately when the close event arrives to avoid in-flight races.","Treat room-closed as a signal to start a new conversation, never as a retry-the-same-request error."],"tags":["omnichannel","livechat","message","room-state","rest-api"],"backgroundTag":"resource-state-conflict","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"}