{"record":{"id":"c423a78e8fcba902","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-token-c423a7","errorCode":"error-invalid-token","errorMessage":"Invalid token","messagePattern":"Invalid token","errorType":"exception","errorClass":"Meteor.Error","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/meteor-methods/integrations/outgoing/updateOutgoingIntegration.ts","lineNumber":31,"sourceCode":"declare module '@rocket.chat/ddp-client' {\n\t// eslint-disable-next-line @typescript-eslint/naming-convention\n\tinterface ServerMethods {\n\t\tupdateOutgoingIntegration(\n\t\t\tintegrationId: string,\n\t\t\tintegration: INewOutgoingIntegration | IUpdateOutgoingIntegration,\n\t\t): IIntegration | null;\n\t}\n}\n\nexport const updateOutgoingIntegration = async (\n\tuserId: string,\n\tintegrationId: string,\n\t_integration: INewOutgoingIntegration | IUpdateOutgoingIntegration,\n): Promise<IIntegration | null> => {\n\tconst integration = await validateOutgoingIntegration(_integration, userId);\n\n\tif (!integration.token || integration.token.trim() === '') {\n\t\tthrow new Meteor.Error('error-invalid-token', 'Invalid token', {\n\t\t\tmethod: 'updateOutgoingIntegration',\n\t\t});\n\t}\n\n\tlet currentIntegration: IIntegration | null;\n\n\tif (await hasPermissionAsync(userId, 'manage-outgoing-integrations')) {\n\t\tcurrentIntegration = await Integrations.findOneById(integrationId);\n\t} else if (await hasPermissionAsync(userId, 'manage-own-outgoing-integrations')) {\n\t\tcurrentIntegration = await Integrations.findOne({\n\t\t\t'_id': integrationId,\n\t\t\t'_createdBy._id': userId,\n\t\t});\n\t} else {\n\t\tthrow new Meteor.Error('not_authorized', 'Unauthorized', {\n\t\t\tmethod: 'updateOutgoingIntegration',\n\t\t});\n\t}","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/meteor-methods/integrations/outgoing/updateOutgoingIntegration.ts#L13-L49","documentation":"updateOutgoingIntegration first normalizes the payload through validateOutgoingIntegration, which spreads the caller's fields without generating a token; if the result has no token (or only whitespace), the update is rejected with error-invalid-token before any lookup or permission branch runs. The same path serves both the DDP method and REST PUT /v1/integrations.update, which forwards bodyParams verbatim — so both require a non-empty token in the update payload.","triggerScenarios":"Sending an update payload that omits the token field or sends token: '' — e.g. a partial-update client assuming unspecified fields keep their stored values.","commonSituations":"Scripts ported from the UI that only send changed fields; API clients modeled on incoming-integration updates where the token is optional.","solutions":["Include a non-empty token in the update payload — reuse the integration's existing token or generate a new one to rotate it","Fetch the current token first (GET /v1/integrations.list) if you want to preserve it","Rotate deliberately when needed: sending a fresh token string updates the secret used to authenticate outgoing-webhook handshakes"],"exampleFix":"// before\nawait Meteor.callAsync('updateOutgoingIntegration', integrationId, {\n  name: 'new-name',\n  channel: '#general', // token omitted -> error-invalid-token\n});\n\n// after\nawait Meteor.callAsync('updateOutgoingIntegration', integrationId, {\n  name: 'new-name',\n  channel: '#general',\n  token: existingIntegration.token, // or a freshly generated value to rotate\n});","handlingStrategy":"validation","validationCode":"// token is mandatory on every update payload\nif (!payload.token || payload.token.trim() === '') {\n  payload.token = existingIntegration.token ?? generateToken();\n}","typeGuard":"const hasValidToken = (p: {\n  token?: string;\n}): p is { token: string } => typeof p.token === 'string' && p.token.trim() !== '';","tryCatchPattern":"try {\n  await Meteor.callAsync('updateOutgoingIntegration', id, payload);\n} catch (err) {\n  if (err instanceof Meteor.Error && err.error === 'error-invalid-token') {\n    // fill in a non-empty token (existing or rotated) and retry once\n    return;\n  }\n  throw err;\n}","preventionTips":["Always send the token field when updating outgoing integrations — the API does not inherit the stored one","Fetch the current token from integrations.list before partial updates","Treat token rotation as intentional: consumers of the webhook must be updated in lockstep"],"tags":["integrations","outgoing-integrations","validation","api-payload"],"backgroundTag":"missing-required-field","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}