Skip to main content
Turnkey’s OTP authentication flows (email and SMS) have been upgraded to pass OTP code attempts as encrypted bundles rather than in plaintext. When encryption and signing happen on the client, an application backend that relays OTP requests cannot read the OTP code or use the resulting verification token to log in or sign up without the matching client private key. The new flow is recommended for its security improvements, and we’ve included a migration guide from legacy flows to the new flow below.

Migration guide

For implementations using Turnkey’s legacy OTP flows, migrating to the updated OTP flows is a breaking change and will require some tweaks to implementations beyond just upgrading SDK versions.
If you have policies written against specific versions of the activities mentioned below, you will need to update those policies to reference the new activity versions.

Updated OTP flow activity types

The following activities (and any subsequent versions) use the updated OTP flow: NOTE: Older activity versions such as ACTIVITY_TYPE_INIT_OTP_V2, ACTIVITY_TYPE_VERIFY_OTP use our legacy OTP flow and are not interchangeable with the new ones due to the new encryption protocol. Notably, for sub organization creation, which accepts OTP verification tokens, ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V7 can be used as part of the legacy flow and the new updated flow. Current SDK signup flows use ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V8, which is not compatible with legacy OTP verification tokens.

Minimum SDK versions

The encrypted OTP flow was introduced in the releases below. Upgrade related Turnkey packages together:

Required updates to implementations

The following updates are required to legacy OTP implementations to migrate to the updated OTP flow, after bumping SDK versions:
  • The response shape for ACTIVITY_TYPE_INIT_OTP_V3 now includes an otpEncryptionTargetBundle which is to be used during OTP verification. The client performing verification must keep the otpId and otpEncryptionTargetBundle from the same OTP attempt. If a backend initiates the OTP, return both values to the app client; do not send the plaintext OTP code to the backend.
  • The request shape for ACTIVITY_TYPE_VERIFY_OTP_V2 now includes an encryptedOtpBundle which is generated using the otpEncryptionTargetBundle received from ACTIVITY_TYPE_INIT_OTP_V3. This bundle will include a client-generated public key and the OTP code attempt. Use the encryptOtpCodeToBundle helper function from @turnkey/crypto on the client to generate the encryptedOtpBundle used during OTP verification.
  • The verification token received in the response of ACTIVITY_TYPE_VERIFY_OTP_V2 is signed by a different key than legacy OTP verification tokens. This key is included in our updated SDKs.
  • Login and signup flows initiated using OTP verification tokens now require client signature objects, generated using the client-generated keypair whose public key was used during OTP verification. The OTP helpers and components in @turnkey/core, Wallet Kit, and @turnkey/sdk-react handle this automatically. Integrations using low-level clients such as @turnkey/sdk-browser, @turnkey/sdk-server, or @turnkey/http must generate the clientSignature on the app client and include it in the login or signup request. If the backend submits that request, pass the signature to the backend. See the otp-auth/with-backend example for the complete flow.
  • Finally, users managing OTP flows with policies will need to update any policies referencing a specific OTP-related activity type so they point to the new activity versions.
For more information and a diagram of the new OTP flow, please refer to additional documentation here.