Register-identifier scopes are preformatted: Prelude runs the OTP challenge itself and does not call your delegation hook. You still need to declare the scope in your step-up configuration’s
allowed_scopes with mode: "managed" so the frontend is permitted to request it. The client passes the identifier value on the request, Prelude validates it, runs the OTP challenge, and persists the identifier on the user’s profile when the challenge completes.
How it works
The new identifier is only attached to the user when the OTP step succeeds. If the user abandons the challenge or fails to enter a valid code, no change is made to their profile.Prerequisites
- A Prelude account with access to the Session API
- An Application ID (
appID) — see Applications - Step-up enabled on the application — see Step-Up Authentication. Register scopes reuse the step-up signing keys and JWKS, so step-up must be configured on the app
- The register scope you want to expose declared in
allowed_scopeson the step-up configuration withmode: "managed"(prld:phone:registerand/orprld:email:register). The OTP step and the identifier write are handled by Prelude — no delegation hook is called for these entries - An OTP login configuration matching the identifier type you want to register (an SMS configuration for
prld:phone:register, an email configuration forprld:email:register)
Register scopes must appear in
allowed_scopes on the step-up configuration with mode: "managed". A request for a scope that is not allowed is rejected with scope_not_allowed before any OTP is sent. The managed mode is reserved for these two scopes and rejects any other scope at configuration time.Trigger the flow
The frontend SDK initiates the flow with a regularrequestStepUp call. The new identifier is passed on the request metadata under the key identifier:
status=review with a single OTP step keyed verify_sms or verify_email. From there the SDK drives the OTP step exactly like any other step-up challenge — see the Web SDK Step-Up guide for the full code path.
What Prelude does
- Parse the value carried under
metadata.identifier. Phone numbers are normalized to E.164; emails are lowercased and normalized. - Reject malformed values with a
bad_requesterror before any OTP is sent. - Check uniqueness: if the identifier is already attached to a user (the requesting user or anyone else), the request is rejected with
identifier_already_exists(HTTP 409). The OTP is never sent in this case. - Synthesize a single-use challenge with one OTP step. The grant lifetime and the step expiration are both 10 minutes (
granted_for: 600). - Embed the canonical identifier on the challenge token so the value cannot be substituted by the client between
stepup/requestand the OTP completion. - On OTP success, attach the identifier to the user via the same path as the Add Identifier management endpoint. A
user.identifier.createdwebhook event is emitted.
stepup/request and the OTP completion, the OTP check fails with identifier_already_exists and the identifier is not attached.
Constraints
Errors
All other step-up errors (expired challenge, invalid token, replay, etc.) apply unchanged.
What’s next?
Step-Up Authentication
Full step-up overview, including custom scopes and the delegation hook.
Change Password
Companion preformatted flow for the
prld:pwd:write scope.