RocketChat/Rocket.Chat · error · Error

error-invalid-visitor

error-invalid-visitor

Error message

error-invalid-visitor

What it means

Thrown by `saveGuest(guestData, userId)` when `LivechatVisitors.findOneById(_id, { projection: { _id: 1 } })` returns null. The visitor must already exist — saveGuest only patches name/email/phone/livechatData on a known visitor record; it never creates one.

Source

Thrown at apps/meteor/server/lib/omnichannel/guests.ts:34

import { parseAgentCustomFields } from './Helper';
import type { ICRMData } from './localTypes';
import { livechatLogger } from './logger';
import { trim } from '../../../lib/utils/stringUtils';
import { hasPermissionAsync } from '../authorization/hasPermission';
import { i18n } from '../i18n';
import { FileUpload } from '../media/file-upload';
import { notifyOnSubscriptionChanged, notifyOnLivechatInquiryChanged, notifyOnLivechatInquiryChangedByToken } from '../notifyListener';

export async function saveGuest(
	guestData: Pick<ILivechatVisitor, '_id' | 'name' | 'livechatData'> & { email?: string; phone?: string },
	userId: string,
) {
	const { _id, name, email, phone, livechatData = {} } = guestData;

	const visitor = await LivechatVisitors.findOneById(_id, { projection: { _id: 1 } });
	if (!visitor) {
		throw new Error('error-invalid-visitor');
	}

	livechatLogger.debug({ msg: 'Saving guest', guestData });
	const updateData = {
		...(name && { name }),
		...(email && { email }),
		...(phone && { phone }),
		livechatData: {},
	};

	const customFields: Record<string, any> = {};

	if ((!userId || (await hasPermissionAsync(userId, 'edit-livechat-room-customfields'))) && Object.keys(livechatData).length) {
		livechatLogger.debug({ msg: 'Saving custom fields for visitor', visitorId: _id, livechatData });
		for await (const field of LivechatCustomField.findByScope('visitor')) {
			if (!livechatData.hasOwnProperty(field._id)) {
				continue;
			}

View on GitHub (pinned to 2a7de45707)

Solutions

  1. Verify the visitor exists (e.g. `GET /v1/livechat/visitor/:token`) before saving
  2. If the visitor was removed, create a new visitor and re-associate instead of patching a dead id
  3. Keep visitor ids server-resolved from the session token rather than client-supplied

Example fix

// before
await saveGuest({ _id: 'staleVisitor', name: 'John' }, userId);

// after
const visitor = await LivechatVisitors.findOneById('staleVisitor', { projection: { _id: 1 } });
if (!visitor) throw new Error('Visitor session expired — create a new visitor');
await saveGuest({ _id: visitor._id, name: 'John' }, userId);
Defensive patterns

Strategy: validation

Validate before calling

const visitor = await LivechatVisitors.findOneById(visitorId, { projection: { _id: 1 } });
if (!visitor) throw new Error('Visitor session no longer valid');
await saveGuest({ _id: visitor._id, ...updates }, userId);

Type guard

const visitorExists = async (id: string): Promise<boolean> =>
  (await LivechatVisitors.findOneById(id, { projection: { _id: 1 } })) != null;

Try / catch

try {
  await saveGuest(guestData, userId);
} catch (e) {
  if (e instanceof Error && e.message === 'error-invalid-visitor') {
    // recreate visitor, rebind session, then retry save
  }
}

Prevention

When it happens

Trigger: Updating visitor attributes via the livechat guest-saving flow with a visitor `_id` that does not exist: stale token-derived ids, ids from another environment, or a visitor purged by GDPR/retention cleanup.

Common situations: Long-lived widget sessions after the visitor record was erased; imports that reference visitors never migrated; test data created against a different database.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


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