# Chat widget attributes

## Chat Widget Attributes

When using the Bird Chat Widget, you can pass contextual data into conversations using two different mechanisms:

* **Contact Attributes** — update the Contact Profile in BirdCRM (persistent across sessions)
* **Conversation Attributes** — attach metadata to a specific conversation (per-conversation)

### Contact Attributes (via Bird Web SDK)

Contact attributes update the CRM profile for the identified contact. These persist across conversations and sessions.

```javascript
// First, identify the contact
await Bird.contact.identify({
  strategy: 'SignedIdentityClaims',
  signedIdentity: signedIdentity,
});

// Then update their profile
await Bird.contact.putAttributes({
  displayName: 'Jane Smith',
});
```

Contact attributes are visible in the Contact Profile UI and can be used in:

* **Journeys**: Access via `{{run.contact.attributes.<key>}}`
* **Flows**: Use a "Fetch Contact" step, then access contact fields
* **Audience segmentation**: Filter contacts by attribute values

#### Supported Attributes

| Attribute     | Type   | Description                                      |
| ------------- | ------ | ------------------------------------------------ |
| `displayName` | string | Contact's display name shown in CRM              |
| Custom keys   | any    | Any key-value pair stored as a contact attribute |

### Conversation Attributes (via Chat Widget API)

Conversation attributes attach metadata to the next conversation created through the chat widget. They are session-specific and do not persist on the contact record.

```javascript
window.mbchat.setAttributes({
  currentPage: window.location.pathname,
  userPlan: 'premium',
  cartValue: '149.99',
  referrer: document.referrer,
});
```

These attributes are:

* Sent automatically when the visitor creates a new conversation
* Stored on the conversation record
* Visible to agents in the conversation details
* Accessible in Flows via a **Get Conversation** step (not directly on the trigger)
* Not persisted on the Contact Profile

#### When to Use Each

| Data Type                          | Use                     | API                             |
| ---------------------------------- | ----------------------- | ------------------------------- |
| User identity (name, email)        | Contact Attributes      | `Bird.contact.putAttributes()`  |
| Subscription tier, account info    | Contact Attributes      | `Bird.contact.putAttributes()`  |
| Current page, referrer, UTM params | Conversation Attributes | `window.mbchat.setAttributes()` |
| Cart value, product being viewed   | Conversation Attributes | `window.mbchat.setAttributes()` |

### Complete Example

Here is the recommended integration pattern for a logged-in user:

```html
<script
  src="https://embeddables.p.mbirdcdn.net/sdk/v0/bird-sdk.js"
  data-config-url="YOUR_CONFIG_URL"
></script>

<script>
async function initBirdChat(user) {
  // 1. Get signed identity from your authenticated backend
  //    Your backend verifies the user's session and generates the JWT
  //    using their email/ID from YOUR database — not from client input
  const resp = await fetch('/api/bird-identity', {
    method: 'POST',
    credentials: 'include', // sends session cookies for authentication
  });
  const { signedIdentity } = await resp.json();

  // 2. Identify the contact (links to CRM record)
  await Bird.contact.identify({
    strategy: 'SignedIdentityClaims',
    signedIdentity,
  });

  // 3. Update contact profile (persistent)
  await Bird.contact.putAttributes({
    displayName: user.fullName,
  });

  // 4. Set conversation context (per-conversation)
  window.mbchat.setAttributes({
    currentPage: window.location.pathname,
    plan: user.plan,
  });
}
</script>
```

### Anonymous Visitors

For visitors who haven't logged in, you can still pass conversation context:

```javascript
// No signed identity needed — SDK auto-identifies as Visitor
window.mbchat.setAttributes({
  landingPage: document.referrer,
  currentPage: window.location.pathname,
  utm_source: new URLSearchParams(window.location.search).get('utm_source'),
  utm_campaign: new URLSearchParams(window.location.search).get('utm_campaign'),
});
```

You can optionally set a display name on the anonymous contact:

```javascript
await Bird.contact.identify({ strategy: 'Visitor' });
await Bird.contact.putAttributes({ displayName: 'Website Visitor' });
```

> **Note:** If the visitor later logs in and is identified with Signed Identity, Bird will link them to their real CRM record. The anonymous contact's attributes do not automatically transfer to the verified contact.

### Visitor Logs In Mid-Session

If a visitor starts chatting anonymously and then logs in:

```javascript
async function onUserLogin(user) {
  // Get signed identity from your authenticated backend
  // The backend verifies the login and looks up identifiers from its own records
  const { signedIdentity } = await fetch('/api/bird-identity', {
    method: 'POST',
    credentials: 'include',
  }).then(r => r.json());

  // Re-identify — links contact to real CRM record
  await Bird.contact.identify({
    strategy: 'SignedIdentityClaims',
    signedIdentity,
  });

  // Update the real contact profile
  await Bird.contact.putAttributes({
    displayName: user.fullName,
  });

  // Update conversation context for the next conversation
  window.mbchat.setAttributes({
    plan: user.plan,
    authenticated: 'true',
  });
}
```

> **Important:** The conversation that was already in progress keeps its original attributes. New `setAttributes()` values apply to the *next* conversation created.

### Accessing Attributes in Flows

Both conversation attributes and contact profile data require an explicit step in your Flow to retrieve them.

| Attribute Source        | Flow Step Required        | Then Access Via                                                           |
| ----------------------- | ------------------------- | ------------------------------------------------------------------------- |
| Conversation attributes | **Get Conversation** step | `conversation.attributes.<key>` (e.g. `conversation.attributes.userPlan`) |
| Contact profile         | **Fetch Contact** step    | `contact.displayName`, `contact.attributes.<key>`                         |

#### Example: Get Conversation step output

After adding a **Get Conversation** step to your Flow, the conversation attributes are available in the result:

```json
{
  "attributes": {
    "currentPage": "https://example.com/pricing",
    "userPlan": "premium",
    "userEmail": "demo@example.com",
    "source": "support-page"
  },
  "featuredParticipants": [
    {
      "displayName": "Demo User",
      "type": "contact"
    }
  ]
}
```

> **Note:** The trigger payload contains the `conversationId` and `contactId`, but not the full attribute data. Use a Get Conversation or Fetch Contact step to retrieve the details you need.

### Signed Identity

Signed Identity is the recommended way to identify contacts in production. It uses a JWT signed by your backend server, ensuring that only your server can assert a visitor's identity.

> **Security: Always authenticate the user first.** Your backend must verify the user's identity (e.g. validate their session token, check their login credentials) *before* generating a Signed Identity JWT. The signing key should never be exposed to the client. If your endpoint issues JWTs without authentication, any visitor could impersonate any contact.

#### Setup

To generate signed identities, you need the **Signing Key** and **Issuer** from your Bird dashboard:

1. Go to **Developer > Applications**
2. Select your application
3. Find the **Identity Signing Key** section on the Overview tab
4. Store the signing key securely on your backend (e.g. environment variable or secrets manager) -- never expose it in client-side code

#### Backend Implementation

Your backend endpoint should:

1. **Authenticate the request** -- verify the user is who they claim to be (session token, cookie, OAuth token, etc.)
2. **Generate identifiers from your own records** -- do not trust identifiers sent from the client
3. **Sign and return the JWT**

```javascript
const crypto = require('crypto');

const ISSUER = process.env.BIRD_ISSUER;
const SIGNING_KEY = process.env.BIRD_SIGNING_KEY;

function makeSignedIdentity(identifiers) {
  const header = JSON.stringify({ alg: 'HS256', typ: 'JWT', kid: ISSUER });
  const payload = JSON.stringify({ identifiers });

  const headerB64 = Buffer.from(header).toString('base64url');
  const payloadB64 = Buffer.from(payload).toString('base64url');

  const signature = crypto
    .createHmac('sha256', SIGNING_KEY)
    .update(`${headerB64}.${payloadB64}`)
    .digest('base64url');

  return `${headerB64}.${payloadB64}.${signature}`;
}

// Express example
app.post('/api/bird-identity', async (req, res) => {
  // Step 1: Authenticate the user using YOUR session/auth system
  const user = await getUserFromSession(req);
  if (!user) {
    return res.status(401).json({ error: 'Not authenticated' });
  }

  // Step 2: Build identifiers from your own trusted user record
  // DO NOT use identifiers sent from the client request body
  const signedIdentity = makeSignedIdentity([
    { key: 'emailaddress', value: user.email },
    { key: 'externalid', value: user.id },
  ]);

  // Step 3: Return the signed JWT
  res.json({ signedIdentity });
});
```

#### Client-Side Usage

```javascript
async function identifyWithBird() {
  // Call YOUR authenticated backend endpoint
  const resp = await fetch('/api/bird-identity', {
    method: 'POST',
    credentials: 'include', // sends session cookies
  });

  if (!resp.ok) throw new Error('Not authenticated');

  const { signedIdentity } = await resp.json();

  // Pass the server-signed JWT to Bird
  await Bird.contact.identify({
    strategy: 'SignedIdentityClaims',
    signedIdentity,
  });
}
```

For more details, see [Signed Identity](https://docs.bird.com/api/client-sdks/contact-profiles/signed-identity).

### API Reference

#### `Bird.contact.putAttributes(attributes)`

Updates the Contact Profile in BirdCRM.

| Parameter    | Type                  | Description                           |
| ------------ | --------------------- | ------------------------------------- |
| `attributes` | `Record<string, any>` | Key-value pairs to set on the contact |

Returns `Promise<ContactResponse>`.

#### `Bird.contact.identify(claim)`

Identifies the current visitor as a contact.

| Parameter              | Type                                    | Description                                      |
| ---------------------- | --------------------------------------- | ------------------------------------------------ |
| `claim.strategy`       | `'Visitor'` \| `'SignedIdentityClaims'` | Identification strategy                          |
| `claim.signedIdentity` | `string`                                | JWT from your backend (for SignedIdentityClaims) |
| `claim.identifier`     | `{ key: string, value: string }`        | Optional identifier (for Visitor)                |

Returns `Promise<{ contactId: string, accessToken: string }>`.

#### `Bird.contact.getCurrent()`

Returns the current contact's profile data.

Returns `Promise<ContactResponse>`.

#### `Bird.contact.reset(options?)`

Resets the current session and generates a new anonymous ID.

#### `window.mbchat.setAttributes(attributes)`

Sets conversation-level attributes on the chat widget.

| Parameter    | Type                     | Description                                       |
| ------------ | ------------------------ | ------------------------------------------------- |
| `attributes` | `Record<string, string>` | Key-value pairs attached to the next conversation |

These are sent when a new conversation is created through the widget.
