Skip to content

Customers

A customer (an “external user”) is an end user of your application, identified by your stable ID. Customers are separate from dashboard users, who manage projects. The userId is the key that ties everything together: the profile, the wallet, the events your app sends, referral records and workflow history.

  • You choose the userId (e.g. your database primary key cus_8f2k). Storelayer never generates or changes it.
  • IDs are strings compared exactly: "123" and " 123" are different customers.
  • Wallets, events and referrals use the userId without checking that a profile exists. A profile is optional for a customer to earn points, but it is what rules read for personalised conditions (name, birthday, metadata).

Use the same userId everywhere. Do not swap in an email or phone number. If a customer changes their email, update the profile field; the ID stays.

FieldNotes
userIdRequired, your ID. Returned as id
firstName, lastNameRequired when registering. Stored as empty strings when a profile is created by upsert without them
emailOptional, must be a valid email
phone, birthday, genderOptional free-form strings. Use ISO dates (1990-04-21) for birthday so conditions can compare them
metadataOptional object for your own attributes (tier, signupSource, marketingOptIn, …)

Storelayer also adds projectId, createdAt and updatedAt.

Updating metadata merges the keys you send into the existing object: other keys are kept. To delete a key, send it with value null. Over the REST API, metadata comes back as a JSON-encoded string, so parse it. Inside rules it is already an object: {{ $('customer').metadata.tier }}.

You wantUseBehavior for an existing userIdBehavior for an unknown userId
Create a profile exactly once (signup)POST /projects/{projectId}/users409, nothing changesCreated (201)
Keep a profile in sync from your systemPATCH /projects/{projectId}/users/{userId}Fields you send are updated (200)Created (201)
Import many profilesPOST /projects/{projectId}/users/bulkWhole batch rejected (409)All created
Check whether a customer existsGET /projects/{projectId}/users/{userId}200 with the profile404

PATCH creates customers. Never use it to check whether someone exists: a typo creates a new, empty profile.

DELETE /projects/{projectId}/users/{userId} removes only the profile. The customer’s wallet balances and ledger, referral codes and referral records, and workflow history remain under the same userId. Re-registering the same ID reconnects the new profile to that existing data. If you need everything gone, zero the wallet with a modify to 0 per asset type, deactivate the referral code, and keep your own deletion record.

Rules read profiles through an internal resource, e.g. external_users.get_user with userId taken from the event. If the profile does not exist, the resource resolves to an object with an _error field instead of failing the rule, so exists/notExists checks on profile fields behave predictably. Rules can also write profiles with the update_user action (same merge rules as PATCH). See conditions and resources.