RocketChat/Rocket.Chat · error · Meteor.Error

invalid-channel

invalid-channel

Error message

invalid-channel

What it means

While resolving an incoming-webhook channel target (processWebhookMessage.ts:53-94), channel strings not prefixed with '#' or '@' fall into the default branch: the room is looked up by name/id (with auto-join), then as a direct room containing the webhook user and the value treated as a user id; if neither matches, the server throws Meteor.Error 'invalid-channel'. The same code also surfaces from getRoomByNameOrIdWithOptionToJoin when '#name'/'@user' targets do not exist or have the wrong type.

Source

Thrown at apps/meteor/server/lib/messages/processWebhookMessage.ts:93

				errorOnEmpty: false,
			});
			if (room) {
				return room;
			}

			// We didn't get a room, let's try finding direct messages
			room = await getRoomByNameOrIdWithOptionToJoin({
				user,
				nameOrId: _channelValue,
				tryDirectByUserIdOnly: true,
				type: 'd',
			});
			if (room) {
				return room;
			}

			// No room, so throw an error
			throw new Meteor.Error('invalid-channel');
	}
};

const buildMessage = (messageObj: Payload, defaultValues: DefaultValues) => {
	const message: Partial<IMessage> & { parseUrls?: boolean } = {
		alias: messageObj.username || messageObj.alias || defaultValues.alias,
		msg: trim(messageObj.text || messageObj.msg || ''),
		attachments: messageObj.attachments || [],
		parseUrls: messageObj.parseUrls !== undefined ? messageObj.parseUrls : !messageObj.attachments,
		bot: messageObj.bot,
		groupable: messageObj.groupable !== undefined ? messageObj.groupable : false,
		tmid: messageObj.tmid,
		customFields: messageObj.customFields,
	};

	if (!_.isEmpty(messageObj.icon_url) || !_.isEmpty(messageObj.avatar)) {
		message.avatar = messageObj.icon_url || messageObj.avatar;
	} else if (!_.isEmpty(messageObj.icon_emoji) || !_.isEmpty(messageObj.emoji)) {

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Verify the target exists and spell it exactly: '#channelName' for channels/groups, '@username' for direct messages
  2. Re-create the channel or update the webhook's channel field after renames/deletions
  3. For DMs, prefer the target's username over raw user id - DM rooms are found (or created) via the user record
  4. Set separateResponse: true in the payload so one bad channel returns a per-channel error instead of failing the entire delivery

Example fix

// before: { "channel": "#general-typo", ... } -> invalid-channel
// after: { "channel": "#general", "separateResponse": true, ... }
Defensive patterns

Strategy: validation

Validate before calling

// before sending, verify the target resolves for the integration user
const room = await getRoomByNameOrIdWithOptionToJoin({
  user: botUser,
  nameOrId: channel.replace(/^[#@]/, ''),
  type: channel.startsWith('@') ? 'd' : undefined,
});
if (!room) throw new Error(`Unknown channel ${channel} - fix the webhook config`);

Try / catch

try {
  await processWebhookMessage(msg, user, { ... });
} catch (error: any) {
  if (error instanceof Meteor.Error && error.error === 'invalid-channel') {
    // bad/deleted/renamed channel in payload; fix config, do not blind-retry
    alertChannelConfigError(payload.channel);
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: An incoming webhook payload with channel '#typo-general', '@deleted-user', a raw room id that no longer exists, or an unknown prefix like '!chan'; '@' targets whose DM room doesn't exist yet and whose id doesn't match a user; channel renamed or deleted after the integration was configured.

Common situations: Webhook integrations (CI, alerting, Zapier-style) configured with a channel that was later renamed/deleted; DM targets whose username changed; sending to private rooms by name from a bot that cannot resolve them; separateResponse=false so the whole request aborts on one bad channel.

Related errors


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