Skip to main content

Access and pricing

SMS authentication is available to all Enterprise customers. To enable this feature, please reach out to the Turnkey team (help@turnkey.com). SMS pricing is usage-based and varies depending on the country of the destination phone number and the carrier. Prices are shown in U.S. cents per outbound SMS message segment. Taxes/surcharges separate. Select your country below to view pricing.

Prerequisites

Make sure you have set up your primary Turnkey organization with at least one API user that can programmatically initiate OTP and create sub-organizations. Check out our Quickstart guide if you need help getting started. To allow an API user to initiate email auth, you’ll need the following policy in your main organization:
{
  "effect": "EFFECT_ALLOW",
  "consensus": "approvers.any(user, user.id == '<YOUR_API_USER_ID>')",
  "condition": "(activity.resource == 'AUTH' && activity.action == 'CREATE') || (activity.resource == 'OTP' && activity.action == 'CREATE') || (activity.resource == 'OTP' && activity.action == 'VERIFY') || (activity.resource == 'ORGANIZATION' && activity.action == 'CREATE')"
}

How it works

SMS authentication uses three activities:
  1. INIT_OTP_V3 — initiates a secure OTP flow and sends a 6–9 digit or alphanumeric OTP to the specified phone number. The response includes an otpEncryptionTargetBundle which is used in OTP verification.
  2. VERIFY_OTP_V2 — securely verifies the code and returns a signed verificationToken JWT
  3. OTP_LOGIN_V2 — validates the verificationToken and returns a session (signed with the verification token key)
If you are migrating from a legacy OTP flow to the new updated flow, check out the OTP Migration Guide for details on required changes.

Implementation

Initiating SMS authentication

The flow begins with a new activity of type ACTIVITY_TYPE_INIT_OTP_V3 using the parent organization id with these parameters:
  • otpType: specify "OTP_TYPE_SMS"
  • contact: user’s phone number
  • emailCustomization: optional parameters for customizing emails
  • userIdentifier: optional parameter for rate limiting SMS OTP requests per user. We recommend generating this server-side based on the user’s IP address or public key. See the OTP Rate Limits section below for more details.
  • alphanumeric: optional parameter for making this code bech32 alphanumeric or not. default: true
  • otpLength: optional parameter for selecting the length of the OTP. default: 9
  • expirationSeconds: optional validity window (defaults to 5 minutes)

One-Time Password Sandbox Environment

To test OTP codes in our sandbox environment you can use the following:
  • alphanumeric must be set to false
  • otpLength must be set to 6
  • Phone Number: +1 999-999-9999
  • OTP Code: 000000
In the sandbox environment, SMS delivery is simulated. Use the fixed OTP code 000000 (with the returned otpId) when calling ACTIVITY_TYPE_VERIFY_OTP_V2 with the parent organization ID to obtain a verificationToken JWT:
  • otpId: ID from the init activity
  • encryptedOtpBundle: bundle generated using the otpEncryptionTargetBundle received during ACTIVITY_TYPE_INIT_OTP_V3 and contains the 6-9 digit or alphanumeric code received via SMS, and the public key of a client-side generated keypair.
  • expirationSeconds: optional validity window (defaults to 1 hour)
After receiving the verification token, users complete OTP authentication flow with ACTIVITY_TYPE_OTP_LOGIN_V2 using the sub-organization ID associated with the contact from the first step:
  • publicKey: public key to add to organization data associated with the signing key in IndexedDB or SecureStorage.
  • verificationToken: JWT returned from successful VERIFY_OTP activity
  • clientSignature: This proves authorization for the verification token being used, and is generated using the keypair whose public key was provided in the encryptedOtpBundle during verification.
  • expirationSeconds: optional validity window (defaults to 15 minutes)
  • invalidateExisting: optional boolean to invalidate previous login sessions
If you are migrating from a legacy OTP flow to the new updated flow, check out the OTP Migration Guide for details on required changes.

Authorization

SMS authentication requires proper permissions through policies or parent organization status.

Enabling/disabling SMS auth

For top-level organizations

SMS authentication is disabled by default. Enable it using ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE:
turnkey request --host api.turnkey.com --path /public/v1/submit/set_organization_feature --body '{
        "timestampMs": "'"$(date +%s)"'000",
        "type": "ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE",
        "organizationId": "<YOUR-ORG-ID>",
        "parameters": {
                "name": "FEATURE_NAME_SMS_AUTH"
        }
}' --organization <YOUR-ORG-ID>

For sub-organizations

  • SMS auth is enabled by default
  • Disable during creation using disableSmsAuth: true in the CreateSubOrganizationIntentV7 activity
  • Disable after creation using ACTIVITY_TYPE_REMOVE_ORGANIZATION_FEATURE with feature name FEATURE_NAME_SMS_AUTH

Implementation notes

  • Users are limited to 10 long-lived API keys and 10 expiring API keys
  • When the expiring API key limit is reached, the oldest key is automatically discarded

OTP rate limits

In order to safeguard users, Turnkey enforces rate limits for OTP auth activities. If a userIdentifier parameter is provided, the following limits are enforced:
  • 3 requests per 3 minutes per unique userIdentifier
  • 3 retries max per code, after which point that code will be locked
  • 3 active codes per user, each with a 5 minute TTL