RocketChat/Rocket.Chat · error · MeteorError

error-invalid-contact-data

error-invalid-contact-data

Error message

Invalid visitor token

What it means

registerContact validates the visitor token before doing anything else: a falsy or non-string token throws MeteorError('error-invalid-contact-data', 'Invalid visitor token'). The token is the visitor's stable cross-chat identifier, so it must be supplied as a non-empty string by the widget or integration.

Source

Thrown at apps/meteor/server/lib/omnichannel/contacts/registerContact.ts:29

type RegisterContactProps = {
	_id?: string;
	token: string;
	name: string;
	username?: string;
	email?: string;
	phone?: string;
	customFields?: Record<string, unknown | string>;
	contactManager?: {
		username: string;
	};
};

export async function registerContact(
	{ token, name, email = '', phone, username, customFields = {}, contactManager }: RegisterContactProps,
	userId: string,
): Promise<string> {
	if (!token || typeof token !== 'string') {
		throw new MeteorError('error-invalid-contact-data', 'Invalid visitor token');
	}

	const visitorEmail = email.trim().toLowerCase();

	if (contactManager?.username) {
		// verify if the user exists with this username and has a livechat-agent role
		const manager = await Users.findOneByUsername(contactManager.username, { projection: { roles: 1 } });
		if (!manager) {
			throw new MeteorError('error-contact-manager-not-found', `No user found with username ${contactManager.username}`);
		}
		if (!manager.roles || !Array.isArray(manager.roles) || !manager.roles.includes('livechat-agent')) {
			throw new MeteorError('error-invalid-contact-manager', 'The contact manager must have the role "livechat-agent"');
		}
	}

	const existingUserByToken = await LivechatVisitors.getVisitorByToken(token, { projection: { _id: 1 } });
	let visitorId = existingUserByToken?._id;

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Send a non-empty string token - typically a random id generated per visitor/device and reused thereafter
  2. Validate the payload on the client before calling the API (token: non-empty string)
  3. Keep using the same token across a visitor's sessions so conversations stay linked to one contact

Example fix

// before - token never sent -> 'Invalid visitor token'
await registerContact({ name: 'John', email: 'john@example.com' }, userId);

// after - generate once per visitor and reuse
const token = existingToken || Random.id();
await registerContact({ token, name: 'John', email: 'john@example.com' }, userId);
Defensive patterns

Strategy: validation

Validate before calling

// Widget/integration side, before calling registerContact:
if (typeof token !== 'string' || token.trim().length === 0) {
  token = Random.id(); // generate once per visitor, persist, and reuse thereafter
}
await registerContact({ token, name, email }, userId);

Type guard

const isValidVisitorToken = (token: unknown): token is string =>
  typeof token === 'string' && token.length > 0;

Try / catch

try {
  await registerContact({ token, name, email }, userId);
} catch (err: any) {
  if (err?.error === 'error-invalid-contact-data') {
    return badRequest('Invalid visitor token');
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling the contact registration flow with token missing, empty string, null/undefined, or a non-string value in the payload; note the default parameter does not apply when the field is present but empty.

Common situations: Custom widgets forgetting to generate or persist the token; clients sending token as null explicitly; form-encoded payloads that parse to empty strings; schema drift between widget versions.

Related errors


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