RocketChat/Rocket.Chat · error · Error

invalid-token

Error message

invalid-token

What it means

POST /api/v1/livechat/custom.field sets one custom field for a visitor identified by the body token. findGuest(token) maps to LivechatVisitors.getVisitorByToken(token); when no visitor document carries that token the endpoint throws 'invalid-token' before any field is written.

Solutions

  1. Use the exact token returned when the visitor was created (POST /api/v1/livechat/visitor returns visitor.token) in the same environment.
  2. Verify the token first with the visitor-info-by-token endpoint; if it no longer resolves, re-register the visitor and store the new token.
  3. Never reuse tokens across deployments; make the token part of per-environment configuration.

Example fix

// before
await post('/api/v1/livechat/custom.field', { token: staleToken, key: 'plan', value: 'pro' }); // invalid-token

// after
async function ensureGuest() {
  const info = await get(`/api/v1/livechat/visitor.info/${token}`);
  if (!info?.visitor) {
    const { visitor } = await post('/api/v1/livechat/visitor', { visitor: { name: 'guest' } });
    token = visitor.token; // persist this
  }
  return token;
}
await post('/api/v1/livechat/custom.field', { token: await ensureGuest(), key: 'plan', value: 'pro' });
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify the visitor token before writing a custom field
const info = await get(`/api/v1/livechat/visitor.info/${token}`);
if (!info?.visitor) throw new Error('visitor token no longer valid; re-register guest');
await post('/api/v1/livechat/custom.field', { token, key, value, overwrite });

Try / catch

try {
  await post('/api/v1/livechat/custom.field', { token, key, value });
} catch (e) {
  if (e.message !== 'invalid-token') throw e;
  const { visitor } = await post('/api/v1/livechat/visitor', { visitor: { name: 'guest' } });
  token = visitor.token; // persist new token
  await post('/api/v1/livechat/custom.field', { token, key, value });
}

Prevention

When it happens

Trigger: POSTing livechat/custom.field with a token that was never registered, was mistyped, belongs to another environment (staging vs prod), or whose visitor document was deleted.

Common situations: Widget/integration persists an old token after the visitors collection was reset or the visitor re-registered; copy-paste between servers; token lost from localStorage so an empty/stale value is sent.

Related errors


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

Appendix: source

Thrown at apps/meteor/server/api/v1/omnichannel/customField.ts:30

	validateUnauthorizedErrorResponse,
} from '@rocket.chat/rest-typings';

import { API } from '../..';
import { setCustomFields, setMultipleCustomFields } from '../../../lib/omnichannel/custom-fields';
import type { ExtractRoutesFromAPI } from '../../ApiClass';
import { findLivechatCustomFields, findCustomFieldById } from './lib/customFields';
import { findGuest } from './lib/livechat';
import { getPaginationItems } from '../../lib/getPaginationItems';

API.v1.addRoute(
	'livechat/custom.field',
	{ validateParams: isPOSTLivechatCustomFieldParams },
	{
		async post() {
			const { token, key, value, overwrite } = this.bodyParams;
			const guest = await findGuest(token);
			if (!guest) {
				throw new Error('invalid-token');
			}

			await setCustomFields({ token, key, value, overwrite });

			return API.v1.success({ field: { key, value, overwrite } });
		},
	},
);

API.v1.addRoute(
	'livechat/custom.fields',
	{ validateParams: isPOSTLivechatCustomFieldsParams },
	{
		async post() {
			const { token } = this.bodyParams;
			const visitor = await findGuest(token);
			if (!visitor) {
				throw new Error('invalid-token');

View on GitHub (pinned to b2c16d5842)