RocketChat/Rocket.Chat · error · Error

error-invalid-room

error-invalid-room

Error message

error-invalid-room

What it means

Defensive guard in processWebhookMessage (processWebhookMessage.ts:171-191): after getRoomWithOptionToJoin resolves a channel target, a null room (possible via the errorOnEmpty:false lookup paths, e.g. type mismatch or a room vanishing mid-resolution) throws Error 'error-invalid-room'. In practice this line and the surrounding per-channel try/catch also capture the room-resolution and permission errors ('invalid-channel', message-size, room permission) for each channel: with separateResponse they are recorded per channel; otherwise the first error aborts the whole call.

Source

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

	if (messageObj.attachments && !Array.isArray(messageObj.attachments)) {
		SystemLogger.warn({
			msg: 'Attachments should be Array, ignoring value',
			attachments: messageObj.attachments,
		});
		messageObj.attachments = undefined;
	}

	const message = buildMessage(messageObj, defaultValues);

	for (const channel of channels) {
		const channelType = channel[0];
		const channelValue = channel.slice(1);
		let room: IRoom | null = null;
		try {
			room = await getRoomWithOptionToJoin(channelType, channelValue, user);
			if (!room) {
				throw new Error('error-invalid-room');
			}
			await validateRoomMessagePermissionsAsync(room, { uid: user._id, ...user });
			await validateWebhookMessage(message, room, user);
			rooms.push({ room, channel });
		} catch (_error: any) {
			if (messageObj.separateResponse) {
				const { error, message } = _error || {};
				const errorMessage = error || message || 'unknown-error';
				rooms.push({ error: errorMessage, room, channel });
				continue;
			}
			throw _error;
		}
	}

	for (const roomData of rooms) {
		if ('error' in roomData && roomData.error) {
			if (messageObj.separateResponse) {

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Pre-verify each target channel exists and is postable by the webhook/integration user before sending
  2. Set separateResponse: true so failures are reported per channel in the response items instead of aborting everything
  3. Inspect the per-item error field of the response to find which channel failed and why
  4. Keep channel names stable or update integration configs immediately after renames/deletions

Example fix

// before: single bad channel aborts the whole webhook call
// after: { "channel": "#a,#b,#gone", "separateResponse": true, ... }
// response: [{channel:'#a', message},{channel:'#b', message},{channel:'#gone', error:'error-invalid-room'}]
Defensive patterns

Strategy: try-catch

Try / catch

const result = await processWebhookMessage({ ...payload, separateResponse: true }, user, defaultValue);
for (const item of result) {
  if (item.error) {
    logChannelFailure(item.channel, item.error); // per-channel: continue with the rest
  }
}

Prevention

When it happens

Trigger: The room lookup returns null without throwing (type mismatch paths with errorOnEmpty:false, room deleted between the two lookups); more commonly, a downstream error in the same try block - validateRoomMessagePermissionsAsync denying the webhook user or validateWebhookMessage rejecting size - surfaces through the same handling.

Common situations: Bulk webhook sends to multiple channels where one channel is inaccessible to the integration user; channels deleted mid-send; webhook user removed from private rooms; integrations wanting partial success instead of all-or-nothing.

Related errors


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