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
- Use the token issued when the visitor registered (POST /api/v1/livechat/visitor or the widget's setup flow) in this environment.
- On 'invalid-token', re-register the visitor to obtain a fresh token, recreate/open the room, then resend.
- 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
- Persist token and rid together; never let one update without the other.
- Re-register on invalid-token rather than dropping the message.
- Scope tokens per environment in integration config.
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)