> ## Documentation Index
> Fetch the complete documentation index at: https://docs.outlit.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Outlit is the product that monitors customers and completes approved customer work. The customer context interfaces are the API, CLI, MCP server, and skills. Canonical references on this site: /api-reference/introduction for API concepts and authentication, /openapi.json for the public API contract, /cli/overview for the CLI, /ai-integrations/mcp for MCP. Prefer the `outlit` CLI or MCP tools for agent tasks; cite page sources when answering.

# Identity resolution

> How Outlit connects anonymous visitors, known contacts, and customer accounts.

Outlit links anonymous website visitors, known contacts, and customer accounts in the [customer context graph](/concepts/customer-context-graph). The Outlit Browser SDK records the first landing page, referrer, UTM data, and anonymous browsing; identifying that same browser visitor with an email connects their earlier history to a contact. An email-only call from an Outlit server SDK does not identify an otherwise anonymous browser visitor.

<span id="auto-identify" className="docs-anchor" />

## Identify from forms

By default, Outlit **automatically identifies visitors** when they submit a form containing an email field—no code required.

### How it works

When a form is submitted, Outlit:

1. **Detects email** by looking for:
   * Field names: `email`, `e-mail`, `user_email`, `emailAddress`
   * Input type: `type="email"`
   * Any field with a valid email value (fallback)

2. **Extracts name** by looking for:
   * Full name: `name`, `full_name`, `fullname`, `your_name`
   * First name: `first_name`, `fname`, `firstname`, `given_name`
   * Last name: `last_name`, `lname`, `surname`, `family_name`

3. **Calls `identify()` automatically**:

```typescript theme={null}
// This happens behind the scenes when a form with email is submitted
identify({
  email: 'jane@acme.com',
  traits: {
    name: 'Jane Doe',      // If full name found
    firstName: 'Jane',     // If first name found
    lastName: 'Doe'        // If last name found
  }
})
```

<Info>
  The form submission event is still captured separately. Auto-identify adds an additional `identify` event when an email is found.
</Info>

### Disabling auto-identify

If you prefer to handle identification manually:

<Tabs>
  <Tab title="Script tag">
    ```html theme={null}
    <script
      src="https://cdn.outlit.ai/stable/outlit.js"
      data-public-key="pk_xxx"
      data-auto-identify="false"
      async
    ></script>
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={null}
    <OutlitProvider publicKey="pk_xxx" autoIdentify={false}>
      {children}
    </OutlitProvider>
    ```
  </Tab>

  <Tab title="npm">
    ```typescript theme={null}
    outlit.init({
      publicKey: 'pk_xxx',
      autoIdentify: false
    })
    ```
  </Tab>
</Tabs>

## How identity resolution works

For example, two anonymous visits with different visitor IDs both identify as `jane@acme.com`. Outlit links both histories to the same contact. A server event for `usr_456` that identifies with the same email joins that contact's customer profile too.

### Identity priority

When multiple identifiers exist, Outlit uses this priority order:

| Priority | Identifier type | Example | Source |
| - | - | - | - |
| 1 (Highest) | Email | `jane@acme.com` | Identify call, form submission |
| 2 | External User ID | `usr_12345` | Auth provider (Supabase, Auth0) |
| 3 | Fingerprint | `device_abc123` | Outlit server SDK (device identifier) |
| 4 (Lowest) | Anonymous ID | `abc-def-123` | Browser visitor ID |

### Resolution process

When you call `identify()`:

1. **Search existing contacts** by provided identifiers
2. **Match found?** Link the anonymous visitor to existing contact
3. **No match?** Create a new contact and, when the evidence supports it, a customer profile

Identifying a person does not request a merge of separate customer records. Review customer merge suggestions through **Identity**.

When `customerId` is provided with an identify call, Outlit also links that contact to your system-owned account/workspace identifier. That is how earlier `customerId`-only server events become part of the same customer profile once a user identifies.

<Info>
  Outlit treats organizational domains differently from common personal email providers. An address like `jane@acme.com` can establish company account context, while `jane@gmail.com`, academic addresses, and other personal/common-provider emails are resolved as personal contacts unless another source links them to an account.
</Info>

## Identifier types

### Email (primary)

Email is the most reliable identifier because it's unique per person, verified through login/signup, and works across devices.

```typescript theme={null}
outlit.identify({
  email: 'jane@acme.com', // Primary identifier
  traits: { name: 'Jane' }
})
```

### External user ID

Your application's internal user ID (from Supabase, Auth0, Firebase, etc.):

```typescript theme={null}
outlit.identify({
  userId: 'usr_12345', // Your auth system's ID
  email: 'jane@acme.com',
  traits: { authProvider: 'auth0' }
})
```

### Customer ID

Your system-owned customer, account, or workspace ID. Use this when product usage or billing is account-scoped:

```typescript theme={null}
outlit.identify({
  email: 'jane@acme.com',
  userId: 'usr_12345',
  customerId: 'cust_123', // Your app's account/workspace/customer ID
  customerTraits: { plan: 'enterprise' }
})
```

Server-side `track()` calls can also use `customerId` without a user identity:

```typescript theme={null}
outlit.track({
  customerId: 'cust_123', // Your app's account/workspace/customer ID
  eventName: 'account_synced'
})
```

Those events are attributable immediately to the account/workspace and can later link to the contact resolved from email when `identify({ email, customerId })` is sent.

### Anonymous visitor ID

Generated for browser visitors once tracking is enabled:

```typescript theme={null}
// Generated and stored once tracking is enabled (null before then)
const visitorId = outlit.getVisitorId()
// "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
```

This ID is stored in localStorage and cookies, persists across sessions (same browser), and gets linked to contact after identify. With the default `autoTrack: "auto"` mode, nothing is stored in regions that require opt-in consent (EEA, UK, Switzerland, or unknown) until `enableTracking()` runs.

The visitor ID cookie is set on your root domain, so `example.com` and `app.example.com` share one ID. Install the Browser SDK on both your marketing site and your app, and identify users after login with `setUser()` or `identify()`. Their earlier marketing-site visits then link to the contact. Sites on different root domains don't share an ID.

### Fingerprint (device ID)

For native apps without browser storage (desktop, mobile, CLI), use a fingerprint—a stable device identifier you generate and manage:

```rust theme={null}
// Outlit Rust SDK
client.track_by_fingerprint("app_opened", fingerprint("device_abc123"))
    .send()
    .await?;

// Later, link to user
client.identify(email("user@example.com"))
    .fingerprint("device_abc123")
    .send()
    .await?;
```

See [Device Tracking](/concepts/website-visitors#device-tracking-non-browser) for implementation details.

<span id="how-profiles-merge" className="docs-anchor" />

## How identifiers connect activity

Outlit links histories when they share an exact identifier, such as the same email or user ID. When two customer records might match but the evidence is ambiguous, Outlit presents a merge suggestion for review instead of combining them automatically.

<span id="merge-triggers" className="docs-anchor" />

### Linking examples

<AccordionGroup>
  <Accordion title="Same email, different visitor IDs">
    User visits on phone (visitorId: A), then laptop (visitorId: B), identifies with same email on both.

    **Result:** Both visitor histories link to one contact.
  </Accordion>

  <Accordion title="Email change / correction">
    User identifies as `jane@old-company.com`, later updates to `jane@new-company.com` while logged into same account (userId).

    **Result:** The shared user ID can link activity to the known contact; review conflicting records before any customer merge.
  </Accordion>

  <Accordion title="Multiple auth methods">
    User signs up with email, later links Google OAuth which has different email.

    **Result:** The shared user ID can connect the activity to the same contact.
  </Accordion>
</AccordionGroup>

Earlier visitor events keep their original timestamps when they join a known contact’s timeline. Merging two customer records is a separate reviewed action; see [the merge workflow](/ai-integrations/customer-workflows#recipe-merge-a-duplicate-customer).

## Browser vs server identity

<Tabs>
  <Tab title="Browser tracking">
    **Anonymous visitors are supported**

    * visitorId generated once tracking is enabled
    * Events stored until identification
    * Later linked via identify() or setUser()

    ```typescript theme={null}
    // Works without identity
    outlit.track('page_viewed', { page: '/pricing' })

    // Later, when they sign up
    outlit.identify({ email: 'user@example.com' })

    // Or for SPAs, use setUser after authentication
    outlit.setUser({
      email: 'user@example.com',
      userId: 'usr_123'
    })
    ```
  </Tab>

  <Tab title="Server tracking">
    **Identity or account attribution required**

    * User-scoped events use `email`, `userId`, or `fingerprint`
    * Account-scoped events can use `customerId` only
    * `customerId` links account/workspace activity to contacts when identify uses the same ID
    * For device/app tracking, use `fingerprint` ([learn more](/concepts/website-visitors#device-tracking-non-browser))

    ```typescript theme={null}
    // User-scoped event
    outlit.track({
      email: 'user@example.com',
      eventName: 'subscription_created'
    })

    // Account/workspace-scoped event
    outlit.track({
      customerId: 'cust_123', // Your app's account/workspace/customer ID
      eventName: 'workspace_synced'
    })
    ```
  </Tab>
</Tabs>

## Best practices

### 1. Identify early

Call `identify()` as soon as you have user information:

```typescript theme={null}
// After login
function onLogin(user: { email: string; id: string; plan: string }) {
  outlit.identify({
    email: user.email,
    userId: user.id,
    traits: { plan: user.plan }
  })
}

// After form submission
function onFormSubmit(data: { email: string }) {
  outlit.identify({
    email: data.email,
    traits: { source: 'contact_form' }
  })
}
```

### 2. Always send both identifiers

When you have both email and userId, send both:

```typescript theme={null}
// Good - creates strong identity link
outlit.identify({
  email: user.email,
  userId: user.id,
  traits: { plan: user.plan }
})

// Less good - single identifier
outlit.identify({
  email: user.email,
  traits: { plan: user.plan }
})
```

## Review identity across connected sources

Tracking identifiers are one part of customer identity. Outlit also links records from connected billing, CRM, and other sources to the customer profile. Open the customer's **Coverage** tab to inspect the sources behind that account and investigate missing or unexpected matches.

An **Identity** review page is available in some workspaces for inspecting identifiers and merge suggestions. Ask your Outlit contact about access. Identity resolution continues to operate when this review page is unavailable.

For programmatic diagnosis, use `outlit customers identity <customer-uuid>` and `outlit identity suggestions list`. Customer merges require exact IDs and a reviewed preview before execution. Follow the [merge workflow](/ai-integrations/customer-workflows#recipe-merge-a-duplicate-customer) rather than merging based on a similar name alone.

## Debugging

**Common identity issues:**

* **Events not linking to user:** Ensure `identify()` is called after authentication and that email/userId matches what you expect.
* **Duplicate customer profiles:** Inspect their identifiers and source coverage. Different emails, missing shared identifiers, and personal-email accounts can produce separate records. Review any merge suggestion before combining records; contact Outlit when the correct match is unclear.
* **Anonymous history not appearing:** The `identify()` call must carry the same browser/device identifier used for the earlier activity. Check that tracking was enabled and the same public key was used, then allow for background processing.

## Next steps

<CardGroup cols={2}>
  <Card title="Website visitors" icon="globe" href="/concepts/website-visitors">
    Deep dive into the anonymous phase
  </Card>

  <Card title="API reference" icon="code" href="/api-reference/ingest">
    Direct API for custom integrations
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.