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.
Solutions
- GET /api/v1/omnichannel/contact with the same contactId to confirm it exists and is enabled
- Make sure you pass the contact _id (from the register/list response), not the visitor token or visitorId
- 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
- Store the contactId returned by registerContact, distinct from visitor token/id
- Validate id shape client-side (17-hex ObjectID)
- Refresh contact ids after merges or imports
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
- error-contact-not-found
- error-contact-manager-not-found
- error-contact-manager-not-found
- error-contact-not-found
- error-contact-not-found
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/fcdfd66cd3bb0c67.
Report an issue: GitHub.
Appendix: 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)