RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-token

error-invalid-token

Error message

Token cannot be empty

What it means

Thrown by POST /api/v1/livechat/visitor (the widget's visitor registration endpoint, no auth required) when bodyParams.visitor.token is missing, empty, or whitespace-only. The token is the visitor's client-generated identity across chats; the endpoint explicitly rejects tokenless payloads with code error-invalid-token and details {method: 'livechat/visitor'}.

Solutions

  1. Generate and persist a unique token client-side (e.g. random 43-char string) and send it inside visitor: {"visitor":{"token":"iNKE7a6k6c2qKxtX","name":"John"}}
  2. Keep using the same token for return visitors so their conversation history is linked
  3. Guard with a trim() check before posting so the user never sees the 400

Example fix

// before
await fetch(`${server}/api/v1/livechat/visitor`, { method: 'POST', body: JSON.stringify({ visitor: { name } }) });
// after
const token = localStorage.rcToken || (localStorage.rcToken = crypto.randomUUID().replace(/-/g, ''));
await fetch(`${server}/api/v1/livechat/visitor`, { method: 'POST', body: JSON.stringify({ visitor: { token, name } }) });
Defensive patterns

Strategy: validation

Validate before calling

const token = getStoredVisitorToken() ?? (setStoredVisitorToken(randomToken()), getStoredVisitorToken());
if (!token?.trim()) throw new Error('visitor token missing');
await fetch(`${server}/api/v1/livechat/visitor`, { method: 'POST', body: JSON.stringify({ visitor: { token, ...profile } }) });

Type guard

const hasVisitorToken = (v: unknown): v is { token: string } =>
  typeof v === 'object' && v !== null && typeof (v as any).token === 'string' && (v as any).token.trim().length > 0;

Try / catch

const body = await (await fetch(url, { method: 'POST', body })).json();
if (!body.success && body.error === 'Token cannot be empty') { /* generate token, persist, re-register */ }

Prevention

When it happens

Trigger: POST /api/v1/livechat/visitor with {"visitor":{}} or {"visitor":{"name":"John"}} (no token), token: '' or token: ' '. Also sending the payload as {"token":"..."} at the top level instead of nested under visitor.

Common situations: Custom widget implementations that forget to generate/persist a token before registering; headless API tests that reuse a template body and drop the token field; integrations that clear localStorage (where the widget keeps the token) between sessions and send a fresh-but-empty value.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — 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/b0c1a6c810845978. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/server/api/v1/omnichannel/visitor.ts:47

					name: Match.Maybe(String),
					email: Match.Maybe(String),
					department: Match.Maybe(String),
					phone: Match.Maybe(String),
					username: Match.Maybe(String),
					customFields: Match.Maybe([
						Match.ObjectIncluding({
							key: String,
							value: String,
							overwrite: Boolean,
						}),
					]),
				}),
			});

			const { customFields, id, token, name, email, department, phone, username, connectionData } = this.bodyParams.visitor;

			if (!token?.trim()) {
				throw new Meteor.Error('error-invalid-token', 'Token cannot be empty', { method: 'livechat/visitor' });
			}

			const guest = {
				token,
				...(id && { id }),
				...(name && { name }),
				...(email && { email }),
				...(department && { department }),
				...(username && { username }),
				...(connectionData && { connectionData }),
				...(phone && typeof phone === 'string' && { phone: { number: phone as string } }),
				connectionData: normalizeHttpHeaderData(this.request.headers),
			};

			const visitor = await registerGuest(guest, {
				shouldConsiderIdleAgent: settings.get<boolean>('Livechat_enabled_when_agent_idle'),
				shouldConsiderOfflineAgent: settings.get<boolean>('Livechat_accept_chats_with_no_agents'),
			});

View on GitHub (pinned to b2c16d5842)