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
- Verify the visitor exists (e.g. `GET /v1/livechat/visitor/:token`) before saving
- If the visitor was removed, create a new visitor and re-associate instead of patching a dead id
- 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
- Derive visitor ids from the session token server-side
- Re-validate visitor after long-lived sessions (records can be purged)
- Treat invalid-visitor as a session-reset signal in widgets
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
- invalid-department
- error-contact-not-found
- error-visitor-not-found
- error-invalid-sla
- error-room-does-not-exist
AI-assisted analysis of RocketChat/Rocket.Chat@2a7de45707 (2026-08-18).
Data as JSON: /api/errors/69e0d6b64b5ef227.
Report an issue: GitHub.