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
- Pre-verify each target channel exists and is postable by the webhook/integration user before sending
- Set separateResponse: true so failures are reported per channel in the response items instead of aborting everything
- Inspect the per-item error field of the response to find which channel failed and why
- 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
- Prefer separateResponse:true so one bad channel never aborts a batch
- Always inspect per-item error fields in webhook responses
- Keep the integration user's room memberships in sync with target channels
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
- invalid-channel
- Invalid status code
- error-invalid-webhook-response
- Integration payload must be a JSON object, not an array or p
- Attachments should be Array, ignoring value
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/81679c40246fe37c.
Report an issue: GitHub.