RocketChat/Rocket.Chat · error · Meteor.Error

error-invite-expired

error-invite-expired

Error message

The invite token has expired.

What it means

Thrown by validateInviteToken when inviteData.expires is set and its timestamp is <= Date.now() (validateInviteToken.ts:38). This is the time-based half of invite expiry; details { field: 'expires' } distinguish it from the usage-limit case. The comparison is wall-clock, so server clock skew matters.

Source

Thrown at apps/meteor/server/lib/rooms/invites/validateInviteToken.ts:38

	}

	const room = await Rooms.findOneById(inviteData.rid);
	if (!room) {
		throw new Meteor.Error('error-invalid-room', 'The invite token is invalid.', {
			method: 'validateInviteToken',
			field: 'rid',
		});
	}

	if (settings.get('ABAC_Enabled') && room?.abacAttributes?.length) {
		throw new Meteor.Error('error-invalid-room', 'Room is ABAC managed', {
			method: 'validateInviteToken',
			field: 'rid',
		});
	}

	if (inviteData.expires && new Date(inviteData.expires).getTime() <= Date.now()) {
		throw new Meteor.Error('error-invite-expired', 'The invite token has expired.', {
			method: 'validateInviteToken',
			field: 'expires',
		});
	}

	if (inviteData.maxUses > 0 && inviteData.uses >= inviteData.maxUses) {
		throw new Meteor.Error('error-invite-expired', 'The invite token has expired.', {
			method: 'validateInviteToken',
			field: 'maxUses',
		});
	}

	return {
		inviteData,
		room,
	};
};

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Have a room admin generate a new invite (or one with a longer/no expiration)
  2. Verify server time (date, NTP status) if seemingly fresh invites are rejected
  3. Pre-check server-side: if inviteData.expires && new Date(inviteData.expires).getTime() <= Date.now(), skip the call and surface a friendly 'link expired' message
  4. Use details.field === 'expires' to differentiate from 'maxUses' exhaustion

Example fix

// before
const { inviteData, room } = await validateInviteToken(token);

// after
const inviteData = await Invites.findOneById(token);
if (inviteData?.expires && new Date(inviteData.expires).getTime() <= Date.now()) {
	throw new Meteor.Error('error-invite-expired', 'This invite link has expired. Ask for a new one.');
}
const { inviteData: valid, room } = await validateInviteToken(token);
Defensive patterns

Strategy: validation

Validate before calling

const invite = await Invites.findOneById(token);
if (invite?.expires && new Date(invite.expires).getTime() <= Date.now()) {
	throw new Meteor.Error('error-invite-expired', 'This invite link has expired — ask for a new one');
}
const result = await validateInviteToken(token);

Try / catch

try {
	await validateInviteToken(token);
} catch (e) {
	if (e instanceof Meteor.Error && e.error === 'error-invite-expired' && e.details?.field === 'expires') {
		// time-based expiry: prompt for a fresh invite
	}
}

Prevention

When it happens

Trigger: An invite created with a finite expiration (days setting) whose deadline has passed; server clock drifted forward making fresh invites appear expired; long-lived invite links in documentation that outlived their expires value.

Common situations: Admin created a 1-day invite and the user clicks it on day 2; NTP misconfiguration on the server; invites generated during testing kept being reused after their window closed.

Related errors


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