Guides

Email change and session management

Let end users update their login email address and revoke all active sessions from any device or browser.

Both features are available to end users through your app, and the session-termination endpoint has a server-side variant so your backend can sign out a specific user when needed (for example, after an account suspension or a security event).

Change email address

The email-change flow uses a two-step verification to protect against account takeover: the user must know their current password, and they must prove access to the new address before the change takes effect. The old address receives a notice with a cancel link so the account owner can abort if the request was not theirs.

The flow has three endpoints:

  1. Request (sends the code and the cancel notice)
  2. Confirm (validates the code and swaps the email)
  3. Cancel (abort link in the old-address notice email)

Step 1: Request the change

Call this endpoint with the user's Bearer token. The user must supply their current password. If the password is correct and the new address is not already taken in the same project, Mudbase:

  • Sends a 6-digit confirmation code to the new address (expires in 10 minutes).
  • Sends a cancel notice with a single-use link to the current address (valid for 25 hours).
http
POST /api/users/email-change/request
Authorization: Bearer <userToken>
Content-Type: application/json

{
  "currentPassword": "MyCurrentP@ss1",
  "newEmail": "new-address@example.com"
}

Success response (200):

json
{
  "message": "A confirmation code has been sent to your new email address."
}
Note
Only one pending change request exists per user at a time. Submitting a new request cancels the previous one and sends a fresh code.

Rate limit: 3 requests per user (or IP) per hour.

Step 2: Confirm with the code

The user enters the 6-digit code that arrived at the new address. Use the same Bearer token as the request step.

http
POST /api/users/email-change/confirm
Authorization: Bearer <userToken>
Content-Type: application/json

{
  "code": "481923"
}

Success response (200):

json
{
  "message": "Email address updated successfully. Other sessions have been signed out.",
  "token": "<newJwt>",
  "refreshToken": "<newRefreshToken>",
  "expiresIn": 1800
}

On success, Mudbase updates the user record to the new email address, invalidates all existing sessions (including tokens on other devices), and returns a fresh token pair for the calling session so it keeps working. Store the new token and refreshToken immediately and discard the old ones.

Warning
The code expires 10 minutes after the request. After 5 wrong submissions the pending request is voided, and the user must start over with a new request.

Cancel (old-address abort link)

If the account owner did not initiate the change request, they can cancel it by clicking the link in the notice email sent to their current address. The link calls this endpoint automatically:

http
GET /api/users/email-change/cancel?token=<rawCancelToken>

This endpoint requires no authentication. The token is single-use and expires 25 hours after the original request. The response is always a plain message and never reveals account details.

json
// Cancel succeeded - HTTP 200
{ "message": "Your email change has been cancelled. No change was made to your account." }

// Request was already cancelled (idempotent) - HTTP 200
{ "message": "This email change request has already been cancelled." }

// Token not found or already used - HTTP 200
{ "message": "This cancellation link is not valid or has already been used." }

// Cancel link expired (request also expired; no change was made) - HTTP 200
{ "message": "This cancellation link has expired. The change request also expired and no change was made." }

// Token parameter missing from the request - HTTP 400
{ "error": "Cancel token is required." }

Error codes

Errors from the request and confirm endpoints include a code field alongside the HTTP status and error message.

CodeHTTP statusWhen it occurs
WRONG_PASSWORD400The supplied currentPassword does not match the account's stored password.
NO_PASSWORD400The account uses a linked provider (SSO or OAuth) and has no local password. See SSO accounts below.
SAME_EMAIL400The requested new email is identical to the current one.
EMAIL_TAKEN409Another user in the same project already uses that email address. Email uniqueness is scoped per project.
NO_PENDING_REQUEST404The confirm endpoint was called but no active request exists for this user (none was created, or it was already confirmed). A request voided by MAX_ATTEMPTS keeps its record and returns MAX_ATTEMPTS (400) on subsequent calls, not this code.
CODE_EXPIRED400The 10-minute code TTL has passed. Submit a new request to get a fresh code.
WRONG_CODE400The submitted code is incorrect. The response includes how many attempts remain.
MAX_ATTEMPTS4005 incorrect code submissions. The pending request is voided. Start over with a new request.

SSO and OAuth-only accounts

Users who signed up exclusively through an OAuth provider or SSO (no local password set) cannot use this email-change flow. The request endpoint returns NO_PASSWORD (400) for those accounts.

If you allow mixed authentication (some users have a local password, others do not), show the email-change UI only when the user has a local password. You can detect this from GET /api/users/me: the response includes a hasPassword boolean that is true when the account has a local password set and false for OAuth-only accounts. Show the email-change option only when user.hasPassword === true.

Interaction with pending-account gate

If your project uses blockPendingLogin: true (see Securing end-user data), a user whose accountStatus is "pending" cannot sign in and therefore cannot reach the email-change endpoints at all: they have no valid session token. There is no special interaction to handle.

A user whose status is "active" is unaffected by this setting and can use the email-change flow normally.

Sign out all sessions

Users can revoke every active session across all their devices and browsers with a single call. The calling session stays alive and receives a fresh token pair.

http
POST /api/users/sessions/end-all
Authorization: Bearer <userToken>
Content-Type: application/json

Success response (200):

json
{
  "message": "All other sessions have been signed out.",
  "token": "<newJwt>",
  "refreshToken": "<newRefreshToken>",
  "expiresIn": 1800
}

Replace the stored token and refresh token with the returned ones. Any token that was issued before this call is rejected on the next authenticated request.

This action writes an audit log entry (user.sessions_ended_all) but does not fire a webhook.

Server-side: sign out a specific user

Your backend can end all sessions for a specific end-user using a server API key scoped to the project. This is useful for security events (for example, after detecting suspicious activity or revoking access for a terminated employee).

http
POST /api/users/{userId}/sessions/end-all
X-API-Key: <serverApiKey>
Content-Type: application/json

Success response (200):

json
{
  "message": "User sessions ended."
}
Warning
The API key must be scoped to the project the user belongs to. Attempting to sign out a user from a different project returns 404 without revealing whether the user exists in that other project.

JWT caller tokens are not accepted for this endpoint. It is a server-side operation only. To end your own sessions as an end user, use POST /api/users/sessions/end-all above.

Webhook: user.email.changed

When a user successfully confirms an email change, Mudbase fires a user.email.changed event to your configured webhook endpoint. The event is fired only for project-scoped users (not for org-level accounts that have no associated project).

Mudbase delivers this event to your configured webhook endpoint. Projects with no event filter set receive all events by default, which includes this one. To receive only a specific set of events, select user.email.changed in Console → Webhooks → Events, or include it in your webhook configuration via the API. See Handling Webhooks for how to configure an endpoint and verify signatures.

Full delivery envelope:

json
{
  "event": "user.email.changed",
  "data": {
    "userId": "685acbe0e129932fbb7a0fc2",
    "previousEmail": "old@example.com",
    "newEmail": "new@example.com",
    "changedAt": "2026-10-13T19:30:00.000Z"
  },
  "timestamp": "2026-10-13T19:30:00.123Z",
  "projectId": "64fa3c2b1d3e4a0012345678"
}

In your handler, read the event data from the data key: payload.data.userId, payload.data.previousEmail, payload.data.newEmail, and payload.data.changedAt.

FieldTypeDescription
userIdstringThe ID of the user whose email changed.
previousEmailstringThe email address before the change.
newEmailstringThe newly confirmed email address.
changedAtISO 8601 stringTimestamp of when the email was updated (UTC).

Use this event to sync user records in your own database, update mailing list subscriptions, or invalidate any cached user profile that stores the old address. Make your handler idempotent using userId and changedAt as a composite key.

Note
The sign-out-all endpoints (POST /api/users/sessions/end-all and its server-side variant) do not fire a webhook. They write an audit log entry only.
Chat with us