Customer sign-up and email verification

Learn how to implement the complete Customer sign-up and email verification flow, including creating global and Store-specific Customers.

After completing this page, you should be able to:

  • Implement a Customer sign-up process that creates global or Store-specific Customers based on business requirements.

  • Implement the complete two-step email verification flow, including token creation and confirmation.

The Customer sign-up process involves two main steps:

  1. Customer sign-up: your application collects the user's details and sends a request to the Composable Commerce API to create a Customer record.
  2. Email verification: the user verifies ownership of their email address by clicking a unique link sent to them. This activates the account and enables features such as password recovery.

Customer sign-up flow

To create a Customer, your application collects the user's information and sends it to the Composable Commerce API.

The primary endpoint for creating Customers is POST /{projectKey}/customers. The following example shows a request to create a Customer account for a user named Alice, including her address information and a custom field for marketing preferences.

Endpoint and key request fields

  • Endpoint: POST /{projectKey}/customers
  • Key request fields:
    • email: the Customer's email address, which serves as their unique identifier for login.
    • password: the Customer's chosen password. Your backend for frontend (BFF) must enforce password complexity rules. Composable Commerce securely hashes and stores the password.
    • firstName: the Customer's first name.
    • lastName: the Customer's last name.
    • addresses: an array of address objects.
    • defaultShippingAddress: the index of the address in the addresses array to be used as the default for shipping.
    • defaultBillingAddress: the index of the address in the addresses array to be used as the default for billing.
    • stores (optional): for multi-Store projects, this array of Store references associates the Customer with specific Stores. If omitted, this means that the Customer is global.
    • custom: an object for custom fields defined in your Composable Commerce project. In this example, a boolean field marketingOptIn captures marketing consent.
  • Outcome:
    • After a successful POST /{projectKey}/customers request, Composable Commerce creates a Customer record with the isEmailVerified flag set as false. This initiates the email verification process.

How Stores affect Customer sign-up

For multi-Store operations, the stores array in the Customer creation request is critical for managing brand separation and data segregation.

Store architecture

A project can contain multiple Stores, such as Electronics High Tech for smartphones and Zenith Living for smart home devices. The Customer-to-Store relationship determines authentication, data access, and user experience.

  • Customer type: Global Customer
    • stores array value: omitted or empty ([]).
    • Login behavior: can log in via the global /login endpoint or any Store-specific /in-store/key={storeKey}/login endpoint. This is suitable for Customers who shop across all brands.
  • Customer type: Store-specific Customer.
    • stores array value: contains one Store reference. For example, [{"typeId": "store", "key": "electronics-high-tech-store"}].
    • Login behavior: can log in only via the /in-store/key={storeKey}/login endpoint for their assigned Store. They can't log in via the global /login endpoint or an in-Store endpoint for a Store to which they aren't assigned.
  • Customer type: Multi-Store Customer
    • stores array value: contains multiple Store references. For example, [{"typeId": "store", "key": "electronics-high-tech-store"}, {"typeId": "store", "key": "zenith-living-store"}].
    • Login behavior: can log in via the in-Store endpoint for any of their assigned Stores, but can't use the global /login endpoint.

This separation is essential to maintain distinct Customer bases for different brands or regions.

Why use Stores?

Using Stores provides the following advantages:

  • Multi-brand management: operate distinct brands with separate Customer bases, catalogs, and pricing.
  • Regional variations: manage different legal entities, currencies, and languages for international expansion.
  • B2B vs. B2C: run separate storefronts for different Customer segments.
  • Data segmentation: segment Customer data for targeted analysis and marketing.
  • Access control: inherently control authentication against specific storefronts.

Proper implementation of the Customer-to-Store relationship enables a flexible, scalable, and secure e-commerce platform.

Example - Create a global Customer account

The following example creates a global Customer account, including address details and a custom field for marketing opt-in.

For more information about setting up the TypeScript SDK, see the Prepare your work environment module.
import { CustomerDraft, Customer } from "@commercetools/platform-sdk";
import { apiRoot } from "../../client";

// Function to create a new customer
async function createCustomer(customerData: CustomerDraft): Promise<Customer> {
  try {
    const customer = await apiRoot
      .customers()
      .post({ body: customerData })
      .execute();
    console.log("Customer created successfully:", customer.body);
    return customer.body;
  } catch (error) {
    console.error("Error creating customer:", error);
    throw error;
  }
}

// Alice's sign-up data
const aliceSignUpData: CustomerDraft = {
  email: "alice.smith@example.com",
  password: "StrongPassword123!", // Ensure your BFF enforces password compliance e.g. min. password length, usage of special characters, etc.
  firstName: "Alice",
  lastName: "Smith",
  addresses: [
    {
      streetName: "Main St",
      streetNumber: "123",
      postalCode: "90210",
      city: "Beverly Hills",
      state: "CA",
      country: "US",
    },
    {
      streetName: "Oak Ave",
      streetNumber: "45",
      postalCode: "10001",
      city: "New York",
      state: "NY",
      country: "US",
    },
  ],
  defaultShippingAddress: 0, // Index of the first address
  defaultBillingAddress: 0, // Index of the first address
  custom: {
    type: {
      key: "customer-marketing-opt-in", // Assuming you have a Custom Type defined with this key
      typeId: "type",
    },
    fields: {
      marketingOptIn: true,
    },
  },
};

// Execute the customer creation
(async () => {
  try {
    const newCustomer = (await createCustomer(aliceSignUpData)).customer;
    // At this point, newCustomer.isEmailVerified will be false
    console.log(
      `Customer account created. Email verification status: ${newCustomer.isEmailVerified}`
    );
  } catch (err) {
    console.error("Failed to create customer account.");
  }
})();

Key takeaways

  • Use POST /{projectKey}/customers to create a Customer.
  • The isEmailVerified flag is false by default upon creation.
  • The stores array determines if a Customer is global or Store-specific.
  • Global Customers can log in anywhere; Store-specific Customers are restricted to their assigned Store's login endpoint.
  • Stores are a powerful feature for managing multiple brands, regions, and business models within a single Project.

Email verification flow

After a Customer record is created, their email address must be verified. This is a critical step for security and user experience.

Why email verification is crucial

  • Data accuracy: ensures that the email address is valid and owned by the user.
  • Spam prevention: reduces fake or bot accounts, maintaining a clean Customer database.
  • Password recovery: a verified email address is essential for secure password resets.
  • Communication channel: establishes a trusted channel for Order updates, notifications, and marketing.
  • Account security: prevents unauthorized users from registering with an email address they don't own.

The email verification process involves creating a verification token and then confirming the email with that token.

Create the verification token

After sign-up, your application must generate a unique token to be sent to the Customer's email.

  • Endpoint: POST /{projectKey}/customers/email-token
  • Request: provide the Customer id and ttlMinutes (Time To Live) details for the token.
    • id: unique identifier of the Customer.
    • ttlMinutes: the token's validity duration in minutes. A short TTL (for example, 120 minutes) is recommended for security.
  • Response: the API returns a value, which is the token.
  • Action: your backend or an integrated email service provider (ESP) sends this token to the Customer's email, typically embedded in a verification link. For example, https://yourshop.com/verify-email?token=TOKEN_VALUE. When the Customer clicks this link, they are redirected to your application to process the verification.
Important: the token must be sent via a secure channel, such as email. Never expose it in the client-side response or insecure logs.

Example - Generate an email verification token

This example generates a verification token for a Customer, assuming that you have their customerId.
import { apiRoot } from "../../client";

// Assuming newCustomer.id from the previous step
const aliceCustomerId = "customer-id-of-alice"; // Replace with actual ID obtained after creation

// Function to create an email verification token
async function createEmailVerificationToken(
  customerId: string,
  ttlMinutes: number = 120
) {
  try {
    const tokenResponse = await apiRoot
      .customers()
      .emailToken()
      .post({
        body: {
          id: customerId,
          ttlMinutes: ttlMinutes,
        },
      })
      .execute();
    // Note to learner: Avoid logging tokens in the logs, specially on production env
    if (process.env.NODE_ENV !== "production") {
      console.log(
        "Email verification token generated:",
        tokenResponse.body.value
      );
    }
    return tokenResponse.body.value;
  } catch (error) {
    // Note to learner: Implement proper error handling
    console.error("Error generating email verification token:", error);
    throw error;
  }
}

// Execute token generation and simulate sending verification email
(async () => {
  try {
    const verificationToken = await createEmailVerificationToken(
      aliceCustomerId
    );
    const verificationLink = `https://www.zenelectron.com/verify-email?token=${verificationToken}`;
    const emailData = {
      toName: "Alice",
      toEmail: "alice@example.com",
      subject: "Verify your Zen Electron Account",
      body: `
         Dear Alice,

         Thank you for signing up with Zen Electron!
         Please click the following link to verify your email address:
         ${verificationLink}

         This link expires in 2 hours.
         If you did not create an account, please ignore this email.
      `.trim(),
    };

    // Note to learner: Implement actual email sending logic inside sendVerificationEmail()
    sendVerificationEmail(emailData);
  } catch (err) {
    console.error("Failed to generate or send verification email.");
  }
})();

Confirm the email address

When the Customer clicks the verification link, your application's backend extracts the token and sends it to Composable Commerce for confirmation.

  • Endpoint: POST /{projectKey}/customers/email/confirm
  • Request: provide the tokenValue from the verification link.
  • Outcome: If the token is valid and not expired, then Composable Commerce sets the Customer's isEmailVerified flag as true. The account is now fully activated. If the token is invalid or expired, then the API returns an error message.

Example - Confirm a Customer's email address

This example shows how your backend confirms the email address by using a token received from the frontend.

import { apiRoot } from "../../client";

// Assuming `tokenValue` is received from the URL parameter (e.g., from the front-end)
const receivedTokenValue = "GENERATED_TOKEN_FROM_PREVIOUS_STEP"; // Replace with the actual token

// Function to confirm email verification
async function confirmEmailVerification(token: string) {
  try {
    const customer = await apiRoot
      .customers()
      .emailConfirm()
      .post({
        body: {
          tokenValue: token,
        },
      })
      .execute();
    console.log(
      "Email successfully confirmed for customer:",
      customer.body.email
    );
    console.log("isEmailVerified status:", customer.body.isEmailVerified);
    return customer.body;
  } catch (error) {
    console.error("Error confirming email:", error);
    throw error;
  }
}

// Execute email confirmation
(async () => {
  try {
    const verifiedCustomer = await confirmEmailVerification(receivedTokenValue);
    // You can now proceed with logging Alice in or directing her to a welcome page
  } catch (err) {
    console.error("Email verification failed.");
  }
})();

Considerations for email verification

  • Token expiry (TTL): set a reasonable TTL for tokens (1-2 hours is secure). Your application should handle expired tokens by prompting the user to request a new one.
  • User experience: provide clear instructions. After sign-up, inform the user to check their email. Offer a resend verification email option, which triggers a new POST /{projectKey}/customers/email-token request.
  • Email templates: email templates are managed by an external email service provider (ESP) like SendGrid, not by Composable Commerce. Customize these templates to match your brand.
When using Subscriptions for asynchronous flows, the CustomerEmailTokenCreated message contains the tokenValue only if the ttlMinutes is 60 minutes or fewer. For longer token validity, the tokenValue is omitted due to security reasons. For more information, see the CustomerEmailTokenCreated message documentation.

Considerations for migrating verified customers

When migrating existing, verified Customers from another system, you can create Customer profiles with the isEmailVerified flag set as true to bypass the verification flow.
If you use an external identity provider (IDP) like Auth0 or Okta, then the IDP typically manages email verification. In this scenario, your application relies on the IDP's verification status and the isEmailVerified flag in Composable Commerce might be redundant.

Key takeaways

  • Email verification uses a two-step process: token creation and email confirmation.
  • Use POST /{projectKey}/customers/email-token to generate a secure, time-limited token for a Customer.
  • Your application is responsible for sending the verification link with the token to the Customer's email.
  • Use POST /{projectKey}/customers/email/confirm with the tokenValue to verify the email address.
  • A successful verification sets the isEmailVerified flag for the Customer as true.

Sign-up and email verification flow diagram

The following diagram illustrates the sign-up and email verification flow:

You've now learned how to create Customer accounts and implement email verification. Next, we'll cover how Customers can securely log in to their accounts.

Test your knowledge