koala73/worldmonitor · error · ConvexError
INVALID_INVITE_TOKEN
INVALID_INVITE_TOKEN
Error message
INVALID_INVITE_TOKEN
What it means
Structured ConvexError thrown by acceptBusinessInvite when the supplied HMAC invite token fails verification against the grant. Tokens are single-use, server-signed secrets; a forged, truncated, or token/grant mismatch fails this guard before any entitlement is granted.
Solutions
- Use the exact token from the invite email link, unmodified and complete
- Do not attempt to reuse a token accepted in another session or for another grant
- Request a new invite if the token may have been regenerated
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at convex/payments/businessSeats.ts:475 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-08-21).
Data as JSON: /api/errors/fe2ce99445e7f076.
Report an issue: GitHub.
Appendix: source
Thrown at convex/payments/businessSeats.ts:471
const identity = await resolveUserIdentity(ctx);
const inviteeEmail = identity?.email?.trim().toLowerCase();
if (!inviteeEmail) {
throw new ConvexError({ kind: "INVITEE_EMAIL_UNAVAILABLE" });
}
const grant = await ctx.db.get(args.grantId);
if (!grant) {
throw new ConvexError({ kind: "GRANT_NOT_FOUND" });
}
if (grant.status !== "pending") {
throw new ConvexError({ kind: "INVITE_ALREADY_USED" });
}
const now = Date.now();
if (grant.expiresAt <= now) {
throw new ConvexError({ kind: "INVITE_EXPIRED" });
}
if (!(await verifyBusinessInviteToken(args.grantId, args.token))) {
throw new ConvexError({ kind: "INVALID_INVITE_TOKEN" });
}
if (grant.inviteeEmail !== inviteeEmail) {
throw new ConvexError({ kind: "INVITE_EMAIL_MISMATCH" });
}
if (!sameDomain(grant.inviteeEmail, inviteeEmail)) {
throw new ConvexError({ kind: "INVITE_EMAIL_MISMATCH" });
}
if (!isCorporateDomain(inviteeEmail)) {
throw new ConvexError({ kind: "INVITEE_DOMAIN_NOT_CORPORATE" });
}
const businessSub = await ctx.db
.query("subscriptions")
.withIndex("by_dodoSubscriptionId", (q) =>
q.eq("dodoSubscriptionId", grant.businessSubscriptionId),
)
.unique();
if (!businessSub || !isBusinessPlan(businessSub.planKey) || !isCoveringAt(businessSub, now)) {View on GitHub (pinned to 7d06c8633d)