{"record":{"id":"b0c1a6c810845978","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-token-b0c1a6","errorCode":"error-invalid-token","errorMessage":"Token cannot be empty","messagePattern":"Token cannot be empty","errorType":"error_code","errorClass":"Meteor.Error","httpStatus":400,"severity":"error","filePath":"apps/meteor/server/api/v1/omnichannel/visitor.ts","lineNumber":47,"sourceCode":"\t\t\t\t\tname: Match.Maybe(String),\n\t\t\t\t\temail: Match.Maybe(String),\n\t\t\t\t\tdepartment: Match.Maybe(String),\n\t\t\t\t\tphone: Match.Maybe(String),\n\t\t\t\t\tusername: Match.Maybe(String),\n\t\t\t\t\tcustomFields: Match.Maybe([\n\t\t\t\t\t\tMatch.ObjectIncluding({\n\t\t\t\t\t\t\tkey: String,\n\t\t\t\t\t\t\tvalue: String,\n\t\t\t\t\t\t\toverwrite: Boolean,\n\t\t\t\t\t\t}),\n\t\t\t\t\t]),\n\t\t\t\t}),\n\t\t\t});\n\n\t\t\tconst { customFields, id, token, name, email, department, phone, username, connectionData } = this.bodyParams.visitor;\n\n\t\t\tif (!token?.trim()) {\n\t\t\t\tthrow new Meteor.Error('error-invalid-token', 'Token cannot be empty', { method: 'livechat/visitor' });\n\t\t\t}\n\n\t\t\tconst guest = {\n\t\t\t\ttoken,\n\t\t\t\t...(id && { id }),\n\t\t\t\t...(name && { name }),\n\t\t\t\t...(email && { email }),\n\t\t\t\t...(department && { department }),\n\t\t\t\t...(username && { username }),\n\t\t\t\t...(connectionData && { connectionData }),\n\t\t\t\t...(phone && typeof phone === 'string' && { phone: { number: phone as string } }),\n\t\t\t\tconnectionData: normalizeHttpHeaderData(this.request.headers),\n\t\t\t};\n\n\t\t\tconst visitor = await registerGuest(guest, {\n\t\t\t\tshouldConsiderIdleAgent: settings.get<boolean>('Livechat_enabled_when_agent_idle'),\n\t\t\t\tshouldConsiderOfflineAgent: settings.get<boolean>('Livechat_accept_chats_with_no_agents'),\n\t\t\t});","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/api/v1/omnichannel/visitor.ts#L29-L65","documentation":"Thrown by POST /api/v1/livechat/visitor (the widget's visitor registration endpoint, no auth required) when bodyParams.visitor.token is missing, empty, or whitespace-only. The token is the visitor's client-generated identity across chats; the endpoint explicitly rejects tokenless payloads with code error-invalid-token and details {method: 'livechat/visitor'}.","triggerScenarios":"POST /api/v1/livechat/visitor with {\"visitor\":{}} or {\"visitor\":{\"name\":\"John\"}} (no token), token: '' or token: '   '. Also sending the payload as {\"token\":\"...\"} at the top level instead of nested under visitor.","commonSituations":"Custom widget implementations that forget to generate/persist a token before registering; headless API tests that reuse a template body and drop the token field; integrations that clear localStorage (where the widget keeps the token) between sessions and send a fresh-but-empty value.","solutions":["Generate and persist a unique token client-side (e.g. random 43-char string) and send it inside visitor: {\"visitor\":{\"token\":\"iNKE7a6k6c2qKxtX\",\"name\":\"John\"}}","Keep using the same token for return visitors so their conversation history is linked","Guard with a trim() check before posting so the user never sees the 400"],"exampleFix":"// before\nawait fetch(`${server}/api/v1/livechat/visitor`, { method: 'POST', body: JSON.stringify({ visitor: { name } }) });\n// after\nconst token = localStorage.rcToken || (localStorage.rcToken = crypto.randomUUID().replace(/-/g, ''));\nawait fetch(`${server}/api/v1/livechat/visitor`, { method: 'POST', body: JSON.stringify({ visitor: { token, name } }) });","handlingStrategy":"validation","validationCode":"const token = getStoredVisitorToken() ?? (setStoredVisitorToken(randomToken()), getStoredVisitorToken());\nif (!token?.trim()) throw new Error('visitor token missing');\nawait fetch(`${server}/api/v1/livechat/visitor`, { method: 'POST', body: JSON.stringify({ visitor: { token, ...profile } }) });","typeGuard":"const hasVisitorToken = (v: unknown): v is { token: string } =>\n  typeof v === 'object' && v !== null && typeof (v as any).token === 'string' && (v as any).token.trim().length > 0;","tryCatchPattern":"const body = await (await fetch(url, { method: 'POST', body })).json();\nif (!body.success && body.error === 'Token cannot be empty') { /* generate token, persist, re-register */ }","preventionTips":["Generate and persist the token in localStorage BEFORE first registration","Reuse one token per end user so conversations link correctly","Never send registration payloads without the nested visitor object"],"tags":["livechat-widget","visitor","rest-api","token","validation"],"backgroundTag":"missing-required-argument","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}