Using the identify method you can create new Plainflow user profiles and record traits about them.

Each user can be linked to one or more actions that you track on your product.

👉 See here how to import all your historica data.

You must provide a unique User ID for each profile. We suggest not to use the email address because it can change over time.

You should call the identify method:

  • After a user registers
  • After a user logs in
  • When a user updates their info (eg changes or adds a new address)
  • Upon loading any pages that are accessible by a logged in user (optional)

How to make an identify call

Calling identify with one of our libraries is really easy.

Here’s the payload of a typical identify call:

  "type": "identify",
  "traits": {
    "name": "Mark Twain",
    "email": "",
    "plan": "premium",
    "logins": 5
  "user_id": "97980cfea0067"

Let’s imagine you want to send it using the Plainflow.js library for the web. You just need to invoke:

plainflow.identify("97980cfea0067", {
  name: "Mark Twain", 
  email: "", 
  plan: "premium", 
  logins: 5

Identify fields

Beyond the common fields, an identify call has the following fields:

user_id requiredStringUnique identifier for the user in your database. A user_id or anonymous_id is required. See the Identities docs for more detail
traits optionalObjectFree-form dictionary of traits of the user, like email or name. See the Traits field docs for a list of reserved trait names


Here’s a complete example of the payload of an identify call (it also includes the common fields):

  "anonymousId": "59f75a945b289f44d6e8aca5",
  "context": {
    "ip": "",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11) AppleWebKit/601.1.32 (KHTML, like Gecko) Version/8.1 Safari/601.1.32"
  "message_id": "aaf06138-2ceb-4881-9c73-79a1c782f370",
  "received_at": "2017-02-09T12:08:32.387Z",
  "sent_at": "2017-02-09T12:08:32.111Z",
  "timestamp": "2017-02-09T12:08:32.111Z",
  "traits": {
    "name": "Mark Twain",
    "email": "",
    "plan": "premium",
    "logins": 5,
    "address": {
      "street": "351 Farmington Avenue",
      "city": "Hartford",
      "state": "CT",
      "postal_code": "06105",
      "country": "USA"
  "type": "identify",
  "user_id": "97980cfea0067",
  "version": "1.2"


The identify call specifies a customer identity that you can reference across the customer’s whole lifetime.

Every identify call must have a User ID or an Anonymous ID, depending on how much you know about the user in question.

Anonymous ID

There are certain cases where you don’t actually know who the user is according to your database, but you still want to be able tie them to traits, events or page views. For example, you may not know who a user is when tracking newsletter signups or anonymous page views.

In these cases, you should use an Anonymous ID.

The Anonymous ID can be any pseudo-unique identifier. For example, on your servers you can use a session id. If you don’t have any readily available identifier, you can always generate a new random one—we recommend UUIDs.

Note: Plainflow’s browser and mobile libraries automatically use Anonymous IDs under the covers to keep track of users as they navigate around your website or app, so you don’t need to worry about them when using those libraries.

☝ Keep in mind! Currently, we only merge events – not traits – of an Anonymous user with the identity of known user. For more checkout the alias section.

User ID

User IDs are a more permanent and robust identifier, like a database ID. Since these IDs are consistent across a customer’s lifetime, identify calls should include a User ID as often as possible.

A User ID is usually the unique identifier that you recognize a user by in your own database.

We recommend using database IDs instead of simple email addresses or usernames, because database IDs never change. That guarantees that even if the user changes their email address, you can still recognize them as the same person in all of your analytics tools. And even better, you’ll be able to correlate analytics data with your own internal database.

Instead of using an email address or a username as a User ID, send them along as [traits].

Traits and reserved keywords

Traits are pieces of information you know about a user that are included in an identify call. These could be demographics like age or gender, account-specific like plan, or even things like whether a user has seen a particular A/B test variation.

We’ve reserved some traits that have semantic meanings for users, and we handle them in special ways. For example, we always expect email to be a string of the user’s email address.

You must only use reserved traits for their intended meaning.

Reserved traits we’ve standardized:

addressObjectStreet address of a user optionally containing: city, country, postal_code, state or street
ageNumberAge of a user
avatarStringURL to an avatar image for the user
birthdayDateUser’s birthday
created_atDateDate the user’s account was first created. Data must be sent as ISO-8601
descriptionStringDescription of the user
emailStringEmail address of a user
first_nameStringFirst name of a user
genderStringGender of a user
idStringUnique ID in your database for a user
last_nameStringLast name of a user
nameStringFull name of a user. If you only pass a first and last name we’ll automatically fill in the full name for you.
phoneStringPhone number of a user
titleStringTitle of a user, usually related to their position at a specific companyExample: “VP of Engineering”
usernameStringUser’s username. This should be unique to each user, like the usernames of Twitter or GitHub.
websiteStringWebsite of a user
companyObjectCompany the user represents, optionally containing: name (a String), id (a String or Number), industry (a String), employee_count (a Number) or plan (a String)
subscription.plan_nameStringThe subscription plan name.
subscription.plan_idStringThe unique identifier of the subscription plan.
subscription.statusStringThe subscription status can be: never subscribed, trial active, trial canceled, trial expired, active, blocked, canceled, expired, unknown
subscription.payment_statusStringThe last payment status can be: pending, paid, dunning, past due, unpaid, canceled, disputed, refunded, unknown.
subscription.plan_interval_countNumberThe number of intervals (specified in the interval property) between subscription billings. For example, plan_interval_count=month and plan_interval_unit=3 bills every 3 months.
subscription.plan_interval_unitStringOne of day, week, month or year. The frequency with which a subscription should be billed.
subscription.service_trial_period_start_atDateIf the subscription has a trial, the beginning of that trial.
subscription.service_trial_period_end_atDateIf the subscription has a trial, the end of that trial.
subscription.service_period_start_atDateWhen the subscription period started.
subscription.service_period_end_atDateWhen the subscription will end.
subscription.service_period_cancelled_atDateIf the subscription has been canceled, the date of that cancellation.
subscription.due_dateDateThe date on which payment for this subscription is due.
subscription.amount_in_centsStringThe amount in cents to be charged on the interval specified.
subscription.currencyStringThree-letter ISO currency code, in lowercase.
subscription.discount_codeStringHash describing the coupon applied to create this discount.
subscription.discount_amount_in_centsStringThe amount in cents to be charged on the interval specified.
subscription.tax_amount_in_centsStringThe amount in cents related to the tax rate.

You can pass these reserved traits using camelCase or snake_case, so in Javascript you can match the rest of your camel-case code by sending firstName, while in Ruby you can match your snake-case code by sending first_name. That way the API never seems alien to your code base.

💡 Profile properties or event attributes MUST NOT contain any space or full-stop.

Historical Import

You can import historical data by adding the timestamp to add argument to any of your method calls. This can be helpful if you’ve just switched to Plainflow.

Note: If you’re tracking things that are happening right now, leave out thetimestampand our servers will timestamp the requests for you.

See how to import historical data using one of the following libraries:

Not using Plainflow yet? Get your free account here. 👈