---
schema_version: "1"
id: b4e0c718bd2166432ff8381265f58da4ea2abfd9466ce5a34f2abc839b0aaa90
title: OTP Email Verification with SLAS
collection: developer
version: current
locale: en-us
portal_url: https://docs.salesforce.com/developer/current/en-us/commerce-cloud/commerce-api/guides/slas-otp-email-verification.md
canonical_url: https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/slas-otp-email-verification.html
source_site: developer.salesforce.com
content_type: documentation
content_hash: 2d9ffc00e65de89535dd854e9057c47c13e7b73c6e405be8697f89f001f05f9f
products:
  - Composable Storefront
  - Salesforce APIs
---
# OTP Email Verification with SLAS

Starting with B2C Commerce 26.6, use the Shopper Login and API Access Service (SLAS) to verify registered shopper identities by using a one-time password (OTP). This feature enables email verification during shopper registration, email updates, and other identity confirmation flows. Integrate OTP email verification with SLAS from any headless application. Currently, only registered user flows support SLAS OTP email verification.

OTPs provide a time-limited, single-use code delivered to a shopper's email address. When `/otp/request` is called, SLAS fetches the shopper's profile from B2C Commerce, checks the `emailVerified` status, and, if verification is valid, generates an OTP and delivers it by email. After the shopper submits the code, a call to `/otp/verify` causes SLAS to validate it and mark the shopper's email as verified in B2C Commerce.

**note:** SLAS doesn't support SMS delivery of OTP. To have the OTP sent by SMS you will need to use `mode=callback` and manage the SMS delivery from your callback. See [Passwordless Login with One-Time Passwords Using Callback URI](https://sfdocs-commercecloud-pr-2334.herokuapp.com/docs/commerce/commerce-api/guide/slas-passwordless-login.html). OTP Email verification only supports `mode=email`.

For SLAS to have the required shopper information to send to the callback, the shopper must have logged in at least one time before attempting OTP email verification.

## Prerequisites

Before you integrate OTP-based email verification, make sure that you have:

- **Email verification site preference enabled**: The B2C Commerce **Enable Email Verification** preference must be turned on from Business Manager. When disabled, `emailVerified` is null on all profiles.

  To enable email verification in Business Manager:

  1. Click **App Launcher**, and then select **Merchant Tools** | **_site_** | **Storefront Login Preferences**.
  2. Turn on **Enable Email Verification**.
  3. Click **Apply**.

  ![Email verification preference toggle in Business Manager](https://developer.salesforce.com/docs/media/commerce-api/slas-email-verification.png)

- **A SLAS private client configured for passwordless login**: OTP email verification requires a SLAS private client. If you don't have one, create it before proceeding. See [Configure a SLAS Private Client](https://developer.salesforce.com/docs/commerce/commerce-api/guide/slas-passwordless-login-email.html#1-configure-a-slas-private-client). Configure the client with `sfcc.pwdless_login` in its allowed scopes (configured in SLAS Admin under **Clients** → **Scopes**). Requests from clients that lack this scope are rejected with a 403.

- **A client channel**: The `channel_id` must be registered to the client. The `channel_id` in every OTP request must match a channel registered to the authenticating client. Configure in the SLAS Admin UI at `https://{short-code}.api.commercecloud.salesforce.com/shopper/auth-admin/v1/ui/`. Go to **Clients** and click **Edit** next to the channel you want to configure.

- **An OTP email template**: Configure an OTP email template for the client channel. The template receives the shopper's first name and the OTP code. See [Configure Email Templates](https://developer.salesforce.com/docs/commerce/commerce-api/guide/slas-passwordless-login-email.html#3-configure-email-templates).

- **A valid shopper access token**: Before you call the OTP email verification endpoints, obtain a SLAS shopper JSON Web Token (JWT) by using the standard authorization code or guest token flow. All OTP endpoints require a SLAS shopper bearer JWT in the authorization header. For more information, see [Private SLAS Client Use Cases](https://developer.salesforce.com/docs/commerce/commerce-api/guide/slas-private-client.html).

## SLAS OTP Email Verification End-to-End Flow

This diagram shows the end-to-end sequence for email verification after shopper registration.

![Complete end-to-end OTP email verification flow from registration through validation](https://developer.salesforce.com/docs/media/commerce-api/slas-otp-email-verification-flow.png '{"class": "image-full"}')

## Request and Verification Flows

The SLAS OTP email verification process has two primary flows: **OTP request** and **OTP verification**.

### OTP Request Flow

The OTP request flow initiates delivery of a one-time password to the shopper. SLAS handles the `emailVerified` check internally — the caller doesn't need to inspect this field before calling the endpoint.

![OTP request flow showing SLAS generating and sending the one-time password](https://developer.salesforce.com/docs/media/commerce-api/slas-otp-request-flow.png '{"class": "image-full"}')

1. Call POST /organizations/{organizationId}/oauth2/otp/request, passing the shopper's `user_id`, `channel_id`, `client_id`, `email`, and a valid shopper bearer token in the authorization header.
2. SLAS authenticates the OAuth client and validates the bearer JWT, confirming that `client_id`, `channel_id`, and `user_id` all match the token's subject claims. A mismatch results in HTTP 400\.
3. SLAS verifies the client has the sfcc.pwdless_login permission scope.
4. SLAS applies a bot-backoff and per-user rate limiting to prevent OTP flooding.
5. SLAS fetches the shopper's customer profile from B2C Commerce and reads the `emailVerified` field:
   - **`null`** — The email verification site preference is disabled. SLAS treats the email as verified, proceeds to generate and send the OTP, and returns 202\.
   - **`true`** — The email is already verified. SLAS proceeds to generate and send the OTP and returns 202\.
   - **`false`** — The email is explicitly unverified. SLAS doesn't send the OTP and returns 400\.
6. SLAS generates an OTP token and computes a hash verifier. The entry expires after 10 minutes.
7. SLAS sends the OTP to the shopper's email address by using an email service with the configured email template.

**note:** SLAS automatically extracts the `usid` (unique shopper ID) from the bearer token's subject claims. You don't need to include it in the request body.

### OTP Verification Flow

The OTP verification flow confirms that the shopper received and submitted the correct code.

![OTP verification flow showing validation and profile update in B2C Commerce](https://developer.salesforce.com/docs/media/commerce-api/slas-otp-verify-flow.png '{"class": "image-full"}')

1. Call POST /organizations/{organizationId}/oauth2/otp/verify, passing the `pwd_action_token` (the code the shopper received), `client_id`, `channel_id`, `user_id`, `usid`, and the shopper bearer token.

2. SLAS authenticates the OAuth client and validates the bearer JWT, confirming that the `client_id`, `channel_id`, and `user_id` match the token claims.
3. SLAS applies a bot-backoff rate limiting to guard against brute-force guessing.
4. SLAS verifies the client has the `sfcc.pwdless_login` permission scope.
5. SLAS looks up the OTP entry by reconstructing the key from the supplied fields. The entry is atomically consumed (deleted) on first successful lookup — the token cannot be reused.
6. SLAS validates the hash verifier. A mismatch results in HTTP 400\.
7. SLAS makes an internal call to the B2C Commerce `update-profile` endpoint to set `emailVerified` to `true`. This call is best-effort: a failure does not block the 204 response from being returned.
8. SLAS responds with **HTTP 204 No Content**.
   - If the hash verifier fails or any parameters are invalid, SLAS responds with **HTTP 400 Bad Request**.
   - If the authorization header, shopper JWT fails validation, or token validation fails**,** SLAS responds with **HTTP 401 Unauthorized.**
   - If the scope validation fails, SLAS responds with **HTTP 403 Forbidden.**

## Sample API Calls

### Obtain a Shopper Access Token

Before calling the OTP endpoints, obtain a SLAS shopper bearer JWT. The shopper must authenticate before making any OTP requests.

Use the `access_token` from the shopper authentication response as the bearer token in the OTP calls below.

### Request an OTP

| Bash                                                                                                                                                                                                                                                                                                                                                  |
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| curl \-X POST \\ "https://{shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/{organizationId}/oauth2/otp/request" \\ -H "Authorization: Bearer {shopper_access_token}" \\ -H "Content-Type: application/x-www-form-urlencoded" \\ -d "client_id={client_id}\&channel_id={channel_id}\&user_id=shopper.example.com\&mode=email |

**Response: 202 Accepted**  
No response body is returned. If emailVerified is true or null on the shopper profile, the OTP is sent to the shopper's email. If emailVerified is false, no email is sent.

### Verify an OTP

| Bash                                                                                                                                                                                                                                                                                                                                                                |
| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| curl \-X POST \\ "https://{shortCode}.api.commercecloud.salesforce.com/shopper/auth/v1/organizations/{organizationId}/oauth2/otp/verify" \\ -H "Authorization: Bearer {shopper_access_token}" \\ -H "Content-Type: application/x-www-form-urlencoded" \\ -d "pwd_action_token=85353215\&client_id={client_id}\&channel_id={channel_id}\&user_id=shopper.example.com |

**Response: 204 No Content**  
No response body is returned. The OTP is valid and the shopper's emailVerified status is updated to true in B2C Commerce.

## Request Field Reference

### POST /otp/request

| Field        | Required | Constraints                                 | Notes                                                                                                                                                       |
| :----------- | :------- | :------------------------------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client_id`  | Yes      | Exactly 36 chars, base64url characters only | Must match a client registered for the tenant                                                                                                               |
| `channel_id` | Yes      | Max 100 chars                               | Must be a channel registered to the client                                                                                                                  |
| `user_id`    | Yes      | Max 128 chars                               | The shopper's login ID or email address. If user_id is an email then user_id will be used to send the email, if not the email query parameter will be used. |
| `mode`       | Yes      | —                                           | Must be set to email                                                                                                                                        |
| `email`      | No       | —                                           | The email address to deliver the OTP to. This email address will be used if the user_id isn't a valid email address.                                        |
| `locale`     | No       | —                                           | Defaults to `en-us`; controls the email template locale                                                                                                     |

### POST /otp/verify

| Field              | Required | Constraints                                 | Notes                                              |
| :----------------- | :------- | :------------------------------------------ | :------------------------------------------------- |
| `pwd_action_token` | Yes      | Passwordless token format                   | The OTP code received by the shopper               |
| `client_id`        | Yes      | Exactly 36 chars, base64url characters only | Must match the `client_id` used in `/otp/request`  |
| `channel_id`       | Yes      | Max 100 chars, base64url characters only    | Must match the `channel_id` used in `/otp/request` |
| `user_id`          | Yes      | Max 128 chars                               | Must match the `user_id` used in `/otp/request`    |

## Security

### Authorization

Both endpoints require a SLAS shopper bearer JWT in the authorization header. All values in the request body must exactly match the corresponding claims in the token. Any mismatch results in HTTP 400\. SLAS also checks that the shopper session hasn't been revoked before proceeding.

### Hash Verifier

After a successful OTP lookup, SLAS validates a hash verifier to confirm the token wasn't tampered with. The verifier is computed during /otp/request and re-validated during /otp/verify using a key. A mismatch results in HTTP 400\.

### Bot Backoff

Both endpoints enforce per-tenant bot-backoff rate limiting to prevent automated flooding and brute-force OTP guessing. When the threshold is exceeded, SLAS returns HTTP 429\.

### OTP Rate Limiting

SLAS checks per-user OTP request rate limits before generating a new code. This prevents resource exhaustion from repeated delivery attempts for the same user.

### OTP Token Expiration

OTP tokens are stored in Redis with a configurable time to live (TTL). The default TTL is 600 seconds (10 minutes). Tokens not consumed within this window expire automatically. Attempting to verify an expired token results in HTTP 403\. Requesting a new OTP token invalidates any pending, unverified tokens for that user.

## Error Responses

| HTTP Status           | Scenario                                                                                                                 |
| :-------------------- | :----------------------------------------------------------------------------------------------------------------------- |
| 400 Bad Request       | Invalid or missing request parameters; `emailVerified` is false; JWT claims mismatch between request body and token      |
| 401 Unauthorized      | Client authentication failed; invalid JWT (expired); OTP validation failed (expired, already used); failed hash verifier |
| 403 Forbidden         | Client doesn't have the sfcc.pwdless_login permission scope                                                              |
| 429 Too Many Requests | Bot-backoff threshold exceeded for the tenant                                                                            |

**warning:** To prevent information leakage, SLAS returns a generic 400 for all OTP verification failures, regardless of the specific reason (invalid/missing parameters, hash mismatch, and emailValidate is false). SLAS returns a generic 401 for client authenticate failure, expired token, failed hash verifier, and expired Shopper JWT. Don't attempt to differentiate failure reasons based on the error response message.
