Buyer accounts: two-step signup, login, the current-customer session and bookings, addresses, and marketing consent. Previous: Guide 19
Reference - This guide is a tutorial. For exhaustive, always-current signatures and field lists, see the auto-generated reference (it regenerates on every
docs:sync, so it never goes stale):
SDK:
CustomersServiceTypes: SDK Types
API: Storefront: Commerce
Prerequisites
Lynkow SDK installed (Quick Start)
A publishable key on the client (see Guide 18). Customer endpoints reject a call without one.
import { createClient } from 'lynkow'
const lynkow = createClient({
siteId: '550e8400-e29b-41d4-a716-446655440000',
publishableKey: 'lkw_pk_9f8g7h6j5k4w3m2n1p0qrstv',
})The customer session is a separate identity track from the publishable key: the key identifies your app, the session token identifies the buyer. login and the activation step below return that token and store it in the client's in-memory state; authenticated methods then send it as Authorization: Bearer <token> alongside the key.
Sign up (two steps)
Signup is deliberately two steps, and no session exists between them. This is what stops someone from setting a password on an address they do not control.
Step 1: request an activation link
lynkow.customers.register({ email }) takes the email and nothing else. It creates no session, does not sign anyone in, and only asks the API to mail an activation link. It resolves to void.
await lynkow.customers.register({ email: '[email protected]' })
// Show ONE message on every outcome. Never branch on whether the email is known.
show('Check your inbox. If this address can be registered, a link is on its way.')register resolves identically whether or not the email already has an account. Do not branch your UI on the result, and do not write a catch expecting a "already exists" rejection: doing either re-creates client-side the account-enumeration signal the API removed. A password, terms, or profile passed here has no effect, the server strips them.
Step 2: activate and set the password
The emailed link lands on a page in your app (by default /account/activate/[token], where the token is the last path segment). On that page, collect the password, terms acceptance, and any profile fields, then call lynkow.customers.activation.confirm(...). This is the only point where the buyer has proven they control the address, so it is where credentials and consent are collected.
// app/account/activate/[token]/page.tsx (client component)
// Returns CustomerAuthResult: { customer, token }
const { customer, token } = await lynkow.customers.activation.confirm({
token: tokenFromTheUrl, // the last path segment of the activation link
password: 'a-strong-passphrase', // 8 to 100 chars
acceptsTerms: true, // MUST be true, ticked on THIS page by this buyer
firstName: 'Alice', // optional
marketingOptIn: true, // optional, unchecked by default
})
saveToken(token) // persist it (see "Keep the buyer signed in") - activation signs them inacceptsTerms must be true or the server rejects the call and the account is not activated. Collect it (and the password) on this page, from the person completing it, never carried over from the form that called register. Activation signs the buyer in, so no follow-up login is needed, and the returned token is stored automatically on the client.
A link works exactly once. On any failure, activation.confirm throws a single uniform LynkowError that deliberately does not distinguish "never valid" from "already used". Route the buyer to your login page with wording that fits both, for example "This link is invalid or has already been used. If you already set your password, sign in." Do not tell the buyer the account does not exist, you cannot know that.
Log in
lynkow.customers.login({ email, password }) authenticates a returning buyer and returns { customer, token }, storing the token on the client. Any failure (wrong password, unknown email) surfaces as one uniform LynkowError with code 'UNAUTHORIZED', with nothing that reveals whether the email exists.
const { customer, token } = await lynkow.customers.login({
email: '[email protected]',
password: 'a-strong-passphrase',
})
saveToken(token)Keep the buyer signed in
The SDK holds the token in memory only, so a page reload or an SSR request starts without it. Persist the returned token in your own storage (for example an HttpOnly cookie) and seed it back when you create the client:
const lynkow = createClient({
siteId: '550e8400-e29b-41d4-a716-446655440000',
publishableKey: 'lkw_pk_9f8g7h6j5k4w3m2n1p0qrstv',
customerToken: savedToken, // restores the signed-in buyer
})The SDK never persists or logs the token; that is your app's responsibility.
The current buyer and their bookings
lynkow.customers.me() returns the signed-in buyer plus their most recent bookings. It requires a stored token and is not cached, since the booking list changes as the buyer books.
// Returns CustomerSession: { customer, bookings }
// customer (partial): { id, email, firstName, emailVerified }
// each booking (partial): { reference, startsAt, status }
const { customer, bookings } = await lynkow.customers.me()
console.log(`${customer.email} has ${bookings.length} booking(s).`)
for (const booking of bookings) {
console.log(`${booking.reference}: ${booking.status} at ${booking.startsAt}`)
}Guest bookings placed with the same email before signup are retro-linked and appear here only once customer.emailVerified is true. Any reservation created while this session is active (see Guide 19) shows up here immediately.
Log out
await lynkow.customers.logout() // discard the token on this client only (no network call)
await lynkow.customers.logout({ everywhere: true }) // also revoke every server-side sessionThe default is a local token discard, so later calls go out without a Bearer header. { everywhere: true } requires a stored token and first revokes every session issued to the buyer server-side. Clear your own persisted copy of the token at the same time.
Address book
The nested lynkow.customers.addresses surface manages the signed-in buyer's saved addresses. All of it requires a stored token.
// CustomerAddress (partial): { id, firstName, city, isDefaultShipping }
const address = await lynkow.customers.addresses.create({
firstName: 'Alice',
lastName: 'Martin',
address1: '12 rue des Lilas',
city: 'Lyon',
postalCode: '69003',
countryCode: 'FR', // ISO 3166-1 alpha-2
isDefaultShipping: true, // atomically unsets the previous default
})
const addresses = await lynkow.customers.addresses.list()
await lynkow.customers.addresses.update(address.id, { isDefaultShipping: true })
await lynkow.customers.addresses.remove(address.id)list() follows every server page and returns a flat array. Setting a default atomically unsets the prior one, so re-read list() after an update to reflect both rows. Anti-spam fields are injected automatically on the writes.
Marketing consent
The nested lynkow.customers.marketingConsent surface reads and updates the buyer's per-channel opt-in state. Each entry names a generic channel (for example 'whatsapp' or 'email') with an optedIn boolean; opt-out always wins, and a channel with no record reads false.
// Each entry: { channel, optedIn }
const { channels } = await lynkow.customers.marketingConsent.list()
// Opt out of one channel, or pass channel: 'all' to change every channel at once.
const after = await lynkow.customers.marketingConsent.update({
channel: 'email',
optIn: false,
})
console.log(after.channels)Forgotten password
The nested lynkow.customers.passwordReset surface handles a reset without a session. request mails a link (and, like register, always resolves the same way whether or not the email exists), and confirm sets the new password from the token in that link.
await lynkow.customers.passwordReset.request({ email: '[email protected]' })
// ... buyer opens the emailed link ...
await lynkow.customers.passwordReset.confirm({
token: tokenFromEmailLink,
password: 'a-new-strong-passphrase',
})Confirming does not sign the buyer in; they log in afterward with the new password.
Reference
Do not treat this guide as the field list. For every method, signature, and property, link the source of truth:
SDK service:
CustomersService- every method and signature.Types: SDK Types - the full
Customer,CustomerActivationConfirmInput,CustomerSession,CustomerAddress, and marketing-consent shapes.API: Storefront: Commerce - request and response schemas.
Next Steps
Guide 18: Product Catalog - browse the products a signed-in buyer can reserve.
Guide 19: Reservations and Deposits - create a booking that appears in the buyer's account.