RocketChat/Rocket.Chat · error · Meteor.Error

not_authorized

not_authorized

Error message

not_authorized

What it means

addOutgoingIntegration (creating an outgoing webhook integration) throws bare not_authorized when there is no userId on the connection, or when the caller holds neither 'manage-outgoing-integrations' nor 'manage-own-outgoing-integrations' — either single permission is sufficient. The check runs after check() validation of the payload and before validateOutgoingIntegration.

Source

Thrown at apps/meteor/server/meteor-methods/integrations/outgoing/addOutgoingIntegration.ts:55

			triggerWords: Match.Maybe([String]),
			avatar: Match.Maybe(String),
			token: Match.Maybe(String),
			impersonateUser: Match.Maybe(Boolean),
			retryCount: Match.Maybe(Number),
			retryDelay: Match.Maybe(String),
			retryFailedCalls: Match.Maybe(Boolean),
			runOnEdits: Match.Maybe(Boolean),
			targetRoom: Match.Maybe(String),
			triggerWordAnywhere: Match.Maybe(Boolean),
		}),
	);

	if (
		!userId ||
		(!(await hasPermissionAsync(userId, 'manage-outgoing-integrations')) &&
			!(await hasPermissionAsync(userId, 'manage-own-outgoing-integrations')))
	) {
		throw new Meteor.Error('not_authorized');
	}

	if (integration.script?.trim()) {
		validateScriptEngine(integration.scriptEngine ?? 'isolated-vm');
	}

	const integrationData = await validateOutgoingIntegration(integration, userId);

	const { insertedId } = await Integrations.insertOne(removeEmpty(integrationData));

	const integrationStored = await Integrations.findOne({ _id: insertedId });

	if (!integrationStored) {
		throw new Error('Error inserting integration');
	}

	void notifyOnIntegrationChanged({ ...integrationStored, _id: insertedId }, 'inserted');

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Authenticate the client before invoking the method
  2. Grant 'manage-own-outgoing-integrations' (self-service) or 'manage-outgoing-integrations' (full admin) to the caller's role in Administration > Permissions
  3. Have the user log out and back in (or refresh) so permission changes take effect
  4. Use POST /v1/integrations.create with an authorized user's token instead of DDP

Example fix

// before: button always visible, call fails for unprivileged users
await Meteor.callAsync('addOutgoingIntegration', integration);

// after: gate the UI on permission
const canAdd = usePermission('manage-outgoing-integrations') || usePermission('manage-own-outgoing-integrations');
{canAdd && <AddOutgoingIntegrationButton />}
Defensive patterns

Strategy: validation

Validate before calling

// client
const canAdd =
  usePermission('manage-outgoing-integrations') ||
  usePermission('manage-own-outgoing-integrations');
// server
const canAdd =
  !!userId &&
  ((await hasPermissionAsync(userId, 'manage-outgoing-integrations')) ||
    (await hasPermissionAsync(userId, 'manage-own-outgoing-integrations')));

Try / catch

try {
  await Meteor.callAsync('addOutgoingIntegration', integration);
} catch (err) {
  if (err instanceof Meteor.Error && err.error === 'not_authorized') {
    // request login or the integration permission, then stop
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: Logged-out client calling addOutgoingIntegration, or a logged-in user whose roles lack both integration permissions; also when the workspace has integrations enabled but the admin never granted manage-own-outgoing-integrations to regular roles.

Common situations: Non-admin users attempting self-service outgoing webhook creation on hardened workspaces; custom scripts invoking the DDP method without login; role changes not yet reflected because the session is stale.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/d7baea5e81dc66ba. Report an issue: GitHub.