RocketChat/Rocket.Chat · error · Error

error-contact-not-found

error-contact-not-found

Error message

error-contact-not-found

What it means

updateContact (PUT /api/v1/omnichannel/contact) loads the contact via LivechatContacts.findOneEnabledById with only _id, name, customFields, conflictingFields projected. If contactId does not match an enabled contact, this error is thrown before any validation of the update payload. Both unknown ids and disabled contacts land here.

Source

Thrown at apps/meteor/server/lib/omnichannel/contacts/updateContact.ts:37

	phones?: string[];
	customFields?: Record<string, unknown>;
	contactManager?: string;
	channels?: ILivechatContactChannel[];
	wipeConflicts?: boolean;
};

export async function updateContact(params: UpdateContactParams): Promise<ILivechatContact> {
	const { contactId, name, emails, phones, customFields: receivedCustomFields, contactManager, channels, wipeConflicts } = params;

	const contact = await LivechatContacts.findOneEnabledById<Pick<ILivechatContact, '_id' | 'name' | 'customFields' | 'conflictingFields'>>(
		contactId,
		{
			projection: { _id: 1, name: 1, customFields: 1, conflictingFields: 1 },
		},
	);

	if (!contact) {
		throw new Error('error-contact-not-found');
	}

	if (contactManager) {
		await validateContactManager(contactManager);
	}

	if (wipeConflicts && contact.conflictingFields?.length) {
		const value = await Settings.incrementValueById('Resolved_Conflicts_Count', contact.conflictingFields.length, {
			returnDocument: 'after',
		});
		if (value) {
			void notifyOnSettingChanged(value);
		}
	}

	const workspaceAllowedCustomFields = await getAllowedCustomFields();
	const workspaceAllowedCustomFieldsIds = workspaceAllowedCustomFields.map((customField) => customField._id);
	const currentCustomFieldsIds = Object.keys(contact.customFields || {});

View on GitHub (pinned to b2c16d5842)

Solutions

  1. GET /api/v1/omnichannel/contact with the same contactId to confirm it exists and is enabled
  2. Make sure you pass the contact _id (from the register/list response), not the visitor token or visitorId
  3. If the contact was disabled, find and update the active contact that replaced it
Defensive patterns

Strategy: validation

Validate before calling

const contact = await LivechatContacts.findOneEnabledById(contactId, { projection: { _id: 1 } });
if (!contact) throw new Error(`No enabled contact ${contactId}`);
await updateContact(params);

Type guard

const isContactId = (v: unknown): v is string => typeof v === 'string' && /^[0-9a-f]{17}$/i.test(v) && v !== token && v !== visitorId;

Try / catch

try {
  await updateContact(params);
} catch (err) {
  if (err instanceof Error && err.message === 'error-contact-not-found') {
    // verify id source: must be contact._id, not token/visitorId; check contact not disabled
  }
  throw err;
}

Prevention

When it happens

Trigger: PUT /omnichannel/contact with a contactId that is wrong, stale, or refers to a contact with enabled:false; also triggered when a client sends the visitor token or room id where the contact _id is expected.

Common situations: Client persists a contactId across restarts and the contact was since deleted; contact disabled by a merge process; confusion between visitorId/token and contactId in integrations.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — 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/fcdfd66cd3bb0c67. Report an issue: GitHub.