RocketChat/Rocket.Chat · error · Error

invalid-token

Error message

invalid-token

What it means

POST /api/v1/livechat/message (visitor sends a message) first resolves findGuest(token) → LivechatVisitors.getVisitorByToken(token). When no visitor carries that token it throws 'invalid-token' before any room or message work happens.

Solutions

  1. Use the token issued when the visitor registered (POST /api/v1/livechat/visitor or the widget's setup flow) in this environment.
  2. On 'invalid-token', re-register the visitor to obtain a fresh token, recreate/open the room, then resend.
  3. Keep token and rid as an atomic pair in client state so they can never drift apart.

Example fix

// before
await post('/api/v1/livechat/message', { token: staleToken, rid, msg: 'hi' }); // invalid-token

// after
async function sendVisitorMessage(rid, msg) {
  let { token } = getState();
  try {
    return await post('/api/v1/livechat/message', { token, rid, msg });
  } catch (e) {
    if (e.message !== 'invalid-token') throw e;
    const { visitor } = await post('/api/v1/livechat/visitor', { visitor: { name: 'guest' } });
    token = visitor.token; // persist token+rid pair together
    return post('/api/v1/livechat/message', { token, rid: await openRoom(token), msg });
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Cheap pre-check via the token-based visitor info endpoint
const info = await get(`/api/v1/livechat/visitor.info/${token}`);
if (!info?.visitor) throw new Error('re-register visitor before sending');
await post('/api/v1/livechat/message', { token, rid, msg });

Try / catch

try {
  await post('/api/v1/livechat/message', { token, rid, msg });
} catch (e) {
  if (e.message !== 'invalid-token') throw e;
  const { visitor } = await post('/api/v1/livechat/visitor', { visitor: { name: 'guest' } });
  token = visitor.token;
  const { room } = await post('/api/v1/livechat/room', { token }); // rid must match the new token
  await post('/api/v1/livechat/message', { token, rid: room._id, msg });
}

Prevention

When it happens

Trigger: Sending a livechat message with a stale/typo'd token, a token from another server, or an empty token; visitor re-registered (new token) while the sender still uses the old one.

Common situations: Widget localStorage cleared/reset causing token mismatch; environment migration without clearing visitor state; integrations replaying old tokens.

Related errors


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

Appendix: source

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

import { findGuest, findRoom, normalizeHttpHeaderData } from './lib/livechat';
import { callbacks } from '../../../lib/callbacks';
import { loadMessageHistory } from '../../../lib/messages/loadMessageHistory';
import { updateMessage, deleteMessage, sendMessage } from '../../../lib/omnichannel/messages';
import { normalizeMessageFileUpload } from '../../../lib/utils/functions/normalizeMessageFileUpload';
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');
			}

View on GitHub (pinned to b2c16d5842)