How to fix invite_not_found: Invite has expired or already been used in Supabase
Supabase returns 'invite_not_found: Invite has expired or already been used' when a user attempts to accept an email invitation that is no longer valid. This occurs when the invitation token has expired (default 24-48 hours), has already been redeemed, or was manually revoked. The fix involves resending a fresh invitation, extending the token expiry, or checking for duplicate acceptances.
Quick fix
Fixes invite_not_found
// Using the Supabase Management API
import { createClient } from '@supabase/supabase-js'
const supabaseAdmin = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
// Resend invitation to user email
const { data, error } = await supabaseAdmin.auth.admin.inviteUserByEmail(
'user@example.com',
{
redirectTo: 'https://yourapp.com/invite-accepted',
data: { role: 'member' } // Optional metadata
}
)
if (error) {
console.error('Failed to resend invitation:', error)
} else {
console.log('New invitation sent:', data)
}- Resend a fresh invitation
- Check invitation status and expiry
- Extend invitation expiry (if possible)
- Handle duplicate acceptance attempts
// Using the Supabase Management APIimport { createClient } from '@supabase/supabase-js'const supabaseAdmin = createClient(process.env.SUPABASE_URL!,process.env.SUPABASE_SERVICE_ROLE_KEY!)// Resend invitation to user emailconst { data, error } = await supabaseAdmin.auth.admin.inviteUserByEmail('user@example.com',{redirectTo: 'https://yourapp.com/invite-accepted',data: { role: 'member' } // Optional metadata})if (error) {console.error('Failed to resend invitation:', error)} else {console.log('New invitation sent:', data)}invite_not_found: Invite has expired or already been used
What this error means
Supabase's email invitation system creates a time-limited, single-use token that allows users to join your organization or project. When someone clicks the invitation link, Supabase validates the token against the `auth.invitations` table (or similar internal tracking). The "invite_not_found" error means: 1. **Token expired**: Invitations typically expire after 24-48 hours for security reasons 2. **Already redeemed**: The same invitation link was used previously 3. **Revoked manually**: An admin removed the invitation before acceptance 4. **Database mismatch**: The invitation record was deleted or belongs to a different Supabase project This is a security feature preventing stale or reused invitations from granting access. The error appears during the final acceptance step, not when sending the invitation.
Typical symptoms
- User clicks an invitation email link and sees HTTP 400 with "invite_not_found: Invite has expired or already been used"
- The Supabase dashboard shows the invitation as 'pending' but the acceptance fails
- Organization member list does not update after the user attempts to join
- Browser console shows failed POST to `/auth/v1/invite/accept` endpoint
- User remains outside the organization/project despite clicking the invitation link
Common causes
- The invitation was sent more than 48 hours ago and the token expired automatically
- The same invitation link was clicked twice (by the same user or different users)
- An organization admin manually revoked the invitation before acceptance
- The invitation was created in a different Supabase project/environment
- Database row was deleted from `auth.invitations` table (if using custom tracking)
- Clock skew between servers causes premature token expiration
How to fix "invite_not_found: Invite has expired or already been used"
The simplest fix is to send a new invitation from the Supabase dashboard or via the Management API:
// Using the Supabase Management API
import { createClient } from '@supabase/supabase-js'
const supabaseAdmin = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
// Resend invitation to user email
const { data, error } = await supabaseAdmin.auth.admin.inviteUserByEmail(
'user@example.com',
{
redirectTo: 'https://yourapp.com/invite-accepted',
data: { role: 'member' } // Optional metadata
}
)
if (error) {
console.error('Failed to resend invitation:', error)
} else {
console.log('New invitation sent:', data)
}In the Supabase dashboard:
1. Go to Authentication → Users
2. Find the user or click Invite User
3. Enter the email and customize the redirect URL
4. Click Send Invitation
The new token will be valid for another 24-48 hours.
Verify the current state of invitations using the Management API:
// List all pending invitations
const { data: invitations, error } = await supabaseAdmin
.from('auth.invitations') // Internal table name may vary
.select('*')
.eq('email', 'user@example.com')
.eq('status', 'pending')
if (error) {
console.error('Error checking invitations:', error)
} else if (invitations.length === 0) {
console.log('No pending invitations found')
} else {
console.log('Found invitations:', invitations)
// Check expiry
invitations.forEach(invite => {
const createdAt = new Date(invite.created_at)
const now = new Date()
const hoursDiff = (now.getTime() - createdAt.getTime()) / (1000 * 60 * 60)
if (hoursDiff > 48) {
console.log('Invitation expired ' + hoursDiff.toFixed(1) + ' hours ago')
}
})
}Note: The exact table name and schema may vary. Supabase may store invitations in an internal system table not directly queryable.
For self-hosted or enterprise setups, you can adjust the invitation token lifetime:
-- If using custom invitation tracking
UPDATE auth.invitations
SET expires_at = NOW() + INTERVAL '7 days'
WHERE email = 'user@example.com'
AND status = 'pending';
-- For Supabase hosted, check project settings
-- Go to Project Settings → Authentication → Email Templates
-- Look for "Invitation validity period" or similarImportant: Extending expiry reduces security. Consider:
- Business needs vs security trade-offs
- Whether to implement manual approval workflows
- Adding MFA for invitation acceptance
Prevent users from clicking invitation links multiple times:
// Frontend: disable button after click
function handleInviteAccept(inviteToken) {
const acceptButton = document.getElementById('accept-invite')
acceptButton.disabled = true
acceptButton.textContent = 'Processing...'
// Call Supabase accept endpoint
fetch('https://' + process.env.NEXT_PUBLIC_SUPABASE_URL + '/auth/v1/invite/accept', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ token: inviteToken }),
})
.then(response => {
if (!response.ok) {
if (response.status === 400) {
// Show user-friendly message
showError('This invitation has already been used. Please contact an administrator for a new one.')
}
acceptButton.disabled = false
acceptButton.textContent = 'Accept Invitation'
}
return response.json()
})
}Also implement server-side checks to prevent race conditions.
Advanced notes
### How Supabase invitation tokens work
Supabase generates a cryptographically signed JWT token for each invitation, containing:
- Target email address
- Inviter's user ID
- Organization/project ID
- Expiration timestamp (typically 24-48 hours)
- Single-use flag
When the user clicks the link, Supabase:
1. Validates the JWT signature
2. Checks expiration timestamp against current time
3. Verifies the token hasn't been used before
4. Creates the user account and adds them to the organization
### Security considerations
Short expiry periods (24-48 hours) are a security feature:
- Prevents stolen email links from being used later
- Limits damage from accidental sharing
- Encourages prompt acceptance
For critical systems, consider:
- Manual approval workflows instead of auto-accept
- Requiring MFA before organization join
- Audit logs for all invitation events
### Custom invitation systems
If Supabase's built-in invitations don't meet your needs, you can build a custom system:
- Store invitations in your own table with longer expiry
- Implement your own token generation/validation
- Add additional metadata (roles, permissions, etc.)
- Create admin interfaces for managing invitations
Frequently asked questions
- What does "invite_not_found" mean?
- Supabase's email invitation system creates a time-limited, single-use token that allows users to join your organization or project. invitations table (or similar internal tracking). The "invite_not_found" error means: 1.
- How do I fix "invite_not_found"?
- Supabase returns 'invite_not_found: Invite has expired or already been used' when a user attempts to accept an email invitation that is no longer valid. This occurs when the invitation token has expired (default 24-48 hours), has already been redeemed, or was manually revoked. The fix involves resending a fresh invitation, extending the token expiry, or checking for duplicate acceptances.
- What causes "invite_not_found"?
- The invitation was sent more than 48 hours ago and the token expired automatically The same invitation link was clicked twice (by the same user or different users) An organization admin manually revoked the invitation before acceptance
- Is the "invite_not_found" error serious?
- It is a medium-severity error — more of a blocker than a risk. The steps above resolve it.
Official resources & further reading
- 📚Supabase Official Documentation
- 📖Supabase Invitations guide(Official Docs)
- 📖Supabase Management API: inviteUserByEmail(Official Docs)
- 🐙