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

# Cross-Source Identity → Unified Persona

> Pull Segment's identity graph to create unified cross-channel personas that account for behavior differences across web, mobile, and email

### Scenario

Segment's identity graph merges user identities across sources — the same person visiting your website, using your mobile app, and opening your emails appears as one unified profile. You pull the merged identity data showing which channels a user engages with, then send it to Mave with the instruction to create a unified persona that accounts for cross-channel behavior differences. The result is a persona that understands the user's full journey, not just a single-channel slice.

### Architecture

```mermaid theme={"dark"}
flowchart LR
A["Segment identity graph"] --> B["Extract cross-channel patterns"] --> C["POST /api/v1/mave/chat"] --> D["Unified cross-channel persona"]
```

### Code

<CodeGroup>
  ```python Python theme={"dark"}
  import os, requests, time
  from collections import defaultdict

  SEG_TOKEN = os.environ["SEGMENT_TOKEN"]
  MV = os.environ["MAVERA_API_KEY"]
  MB = "https://app.mavera.io/api/v1"
  MH = {"Authorization": f"Bearer {MV}", "Content-Type": "application/json"}
  SH = {"Authorization": f"Bearer {SEG_TOKEN}", "Content-Type": "application/json"}

  SPACE_ID = os.environ.get("SEGMENT_SPACE_ID", "spa_xxxxx")

  r = requests.get(
      f"https://api.segmentapis.com/spaces/{SPACE_ID}/collections/users/profiles",
      headers=SH,
      params={"limit": 100, "include": "traits,external_ids"},
  )

  profiles = []
  if r.status_code == 200:
      profiles = r.json().get("data", {}).get("profiles", [])

  if not profiles:
      profiles = [
          {
              "traits": {"email": "j.doe@example.com", "ltv": 890, "plan": "pro", "last_web_visit": "2026-03-15", "last_mobile_open": "2026-03-16", "last_email_click": "2026-03-12", "web_sessions_30d": 14, "mobile_sessions_30d": 22, "emails_opened_30d": 8},
              "external_ids": [{"type": "user_id", "id": "usr_001"}, {"type": "ios_id", "id": "ios_abc"}, {"type": "email", "id": "j.doe@example.com"}],
          },
          {
              "traits": {"email": "s.chen@example.com", "ltv": 340, "plan": "starter", "last_web_visit": "2026-03-10", "last_mobile_open": "2026-02-28", "last_email_click": "2026-03-14", "web_sessions_30d": 4, "mobile_sessions_30d": 1, "emails_opened_30d": 12},
              "external_ids": [{"type": "user_id", "id": "usr_002"}, {"type": "email", "id": "s.chen@example.com"}],
          },
          {
              "traits": {"email": "m.patel@example.com", "ltv": 1450, "plan": "enterprise", "last_web_visit": "2026-03-17", "last_mobile_open": "2026-03-17", "last_email_click": "2026-03-16", "web_sessions_30d": 28, "mobile_sessions_30d": 35, "emails_opened_30d": 15},
              "external_ids": [{"type": "user_id", "id": "usr_003"}, {"type": "ios_id", "id": "ios_xyz"}, {"type": "android_id", "id": "and_789"}, {"type": "email", "id": "m.patel@example.com"}],
          },
      ] * 10

  channel_patterns = {"web_dominant": [], "mobile_dominant": [], "email_dominant": [], "omnichannel": []}

  for p in profiles:
      traits = p.get("traits", {})
      ext_ids = p.get("external_ids", [])
      id_types = [e.get("type", "") for e in ext_ids]

      web = int(traits.get("web_sessions_30d", 0) or 0)
      mobile = int(traits.get("mobile_sessions_30d", 0) or 0)
      email = int(traits.get("emails_opened_30d", 0) or 0)
      total = web + mobile + email

      if total == 0:
          continue

      profile_data = {
          "ltv": float(traits.get("ltv", 0) or 0),
          "plan": traits.get("plan", "unknown"),
          "web": web, "mobile": mobile, "email": email,
          "id_count": len(id_types),
          "channels": id_types,
      }

      web_pct = web / total
      mobile_pct = mobile / total
      email_pct = email / total

      if web_pct > 0.5:
          channel_patterns["web_dominant"].append(profile_data)
      elif mobile_pct > 0.5:
          channel_patterns["mobile_dominant"].append(profile_data)
      elif email_pct > 0.5:
          channel_patterns["email_dominant"].append(profile_data)
      else:
          channel_patterns["omnichannel"].append(profile_data)

  def summarize_pattern(users, label):
      if not users:
          return ""
      avg_ltv = sum(u["ltv"] for u in users) / len(users)
      avg_web = sum(u["web"] for u in users) / len(users)
      avg_mobile = sum(u["mobile"] for u in users) / len(users)
      avg_email = sum(u["email"] for u in users) / len(users)
      avg_ids = sum(u["id_count"] for u in users) / len(users)
      plans = defaultdict(int)
      for u in users:
          plans[u["plan"]] += 1
      top_plans = sorted(plans, key=plans.get, reverse=True)[:3]

      return (
          f"**{label}** (N={len(users)})\n"
          f"  Avg LTV: ${avg_ltv:.0f} | Web sessions: {avg_web:.0f} | Mobile: {avg_mobile:.0f} | Email opens: {avg_email:.0f}\n"
          f"  Avg identity count: {avg_ids:.1f} | Plans: {', '.join(top_plans)}"
      )

  pattern_block = "\n\n".join(
      summarize_pattern(users, label.replace("_", " ").title())
      for label, users in channel_patterns.items()
      if users
  )

  mave = requests.post(
      f"{MB}/mave/chat",
      headers=MH,
      json={"message": f"""Create a unified persona framework that accounts for cross-channel behavior. Users interact with our product via website, mobile app, and email — and the same user behaves differently on each channel.

  CROSS-CHANNEL PATTERNS (from Segment identity graph, 30 days):

  {pattern_block}

  For each channel pattern, provide:
  1. A persona name and 2-sentence description capturing their cross-channel identity
  2. Why they prefer their dominant channel — what need does it serve?
  3. How to reach them on their weaker channels — what message/format would work?
  4. Cross-channel journey map — how do they typically move between channels?
  5. Content format recommendations per channel
  6. Risk of losing them — which channel loss would cause churn?

  Then provide an overall cross-channel strategy:
  - Where to invest in consistency vs. channel-specific adaptation
  - Identity resolution opportunities (matching anonymous to known)
  - Moments when users switch channels and what triggers the switch"""},
  ).json()

  print("--- Cross-Channel Unified Persona Framework ---")
  print(mave.get("content", "")[:3000])
  ```

  ```javascript JavaScript theme={"dark"}
  const SEG_TOKEN = process.env.SEGMENT_TOKEN;
  const MV = process.env.MAVERA_API_KEY;
  const MB = "https://app.mavera.io/api/v1";
  const MH = { Authorization: `Bearer ${MV}`, "Content-Type": "application/json" };
  const SPACE_ID = process.env.SEGMENT_SPACE_ID || "spa_xxxxx";

  const profRes = await fetch(
    `https://api.segmentapis.com/spaces/${SPACE_ID}/collections/users/profiles?limit=100&include=traits,external_ids`,
    { headers: { Authorization: `Bearer ${SEG_TOKEN}`, "Content-Type": "application/json" } }
  ).then((r) => r.json());

  let profiles = profRes.data?.profiles || [];
  if (!profiles.length) {
    profiles = Array(10).fill(null).flatMap(() => [
      { traits: { ltv: 890, plan: "pro", web_sessions_30d: 14, mobile_sessions_30d: 22, emails_opened_30d: 8 }, external_ids: [{ type: "user_id" }, { type: "ios_id" }, { type: "email" }] },
      { traits: { ltv: 340, plan: "starter", web_sessions_30d: 4, mobile_sessions_30d: 1, emails_opened_30d: 12 }, external_ids: [{ type: "user_id" }, { type: "email" }] },
      { traits: { ltv: 1450, plan: "enterprise", web_sessions_30d: 28, mobile_sessions_30d: 35, emails_opened_30d: 15 }, external_ids: [{ type: "user_id" }, { type: "ios_id" }, { type: "android_id" }, { type: "email" }] },
    ]);
  }

  const patterns = { web_dominant: [], mobile_dominant: [], email_dominant: [], omnichannel: [] };
  for (const p of profiles) {
    const t = p.traits || {};
    const web = parseInt(t.web_sessions_30d || 0);
    const mobile = parseInt(t.mobile_sessions_30d || 0);
    const email = parseInt(t.emails_opened_30d || 0);
    const total = web + mobile + email;
    if (!total) continue;

    const data = { ltv: parseFloat(t.ltv || 0), plan: t.plan || "unknown", web, mobile, email, idCount: (p.external_ids || []).length };
    if (web / total > 0.5) patterns.web_dominant.push(data);
    else if (mobile / total > 0.5) patterns.mobile_dominant.push(data);
    else if (email / total > 0.5) patterns.email_dominant.push(data);
    else patterns.omnichannel.push(data);
  }

  function summarize(users, label) {
    if (!users.length) return "";
    const avg = (key) => users.reduce((s, u) => s + u[key], 0) / users.length;
    const plans = {};
    users.forEach((u) => { plans[u.plan] = (plans[u.plan] || 0) + 1; });
    const topPlans = Object.keys(plans).sort((a, b) => plans[b] - plans[a]).slice(0, 3);
    return `**${label}** (N=${users.length})\n  LTV: $${avg("ltv").toFixed(0)} | Web: ${avg("web").toFixed(0)} | Mobile: ${avg("mobile").toFixed(0)} | Email: ${avg("email").toFixed(0)}\n  IDs: ${avg("idCount").toFixed(1)} | Plans: ${topPlans.join(", ")}`;
  }

  const patternBlock = Object.entries(patterns)
    .filter(([, u]) => u.length)
    .map(([k, u]) => summarize(u, k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase())))
    .join("\n\n");

  const mave = await fetch(`${MB}/mave/chat`, { method: "POST", headers: MH,
    body: JSON.stringify({
      message: `Create unified persona framework for cross-channel behavior (website, mobile, email).\n\nPATTERNS (Segment identity graph, 30d):\n\n${patternBlock}\n\nFor each pattern: 1) Persona name + description 2) Why they prefer dominant channel 3) How to reach on weak channels 4) Cross-channel journey 5) Content format per channel 6) Churn risk by channel loss.\n\nOverall: consistency vs. adaptation strategy, identity resolution opportunities, channel-switch triggers.`,
    }),
  }).then((r) => r.json());

  console.log("--- Cross-Channel Persona Framework ---");
  console.log((mave.content || "").slice(0, 3000));
  ```
</CodeGroup>

### Example Output

```text theme={"dark"}
--- Cross-Channel Unified Persona Framework ---

## 1. Mobile Dominant — "The On-The-Go Operator"
Power user who runs their business from their phone. 22 mobile sessions
vs. 14 web sessions per month. LTV: $890. Pro plan.

**Why mobile:** They manage in motion — between meetings, on commutes,
during events. Mobile gives them quick-check capability without context-
switching from other work.

**Reach on web:** Desktop email with "Continue on Desktop" CTAs for
complex tasks (reports, settings). They use web for setup, mobile for
monitoring.

**Journey:** Discovers on web → installs mobile → daily mobile check-ins
→ monthly web deep-dive → upgrade decision on desktop.

## 2. Email Dominant — "The Inbox Dweller"
Engages primarily through email. 12 opens vs. 4 web sessions and 1
mobile session. LTV: $340. Starter plan.

**Why email:** They're not heavy product users — they consume insights
passively. Email delivers value without requiring them to log in.

**Risk:** HIGH churn risk if email quality drops. They don't have a
product habit — email IS their product experience. If you stop sending
useful content, they'll forget you exist.

## 3. Omnichannel — "The Power Orchestrator"
Uses all channels heavily. 28 web + 35 mobile + 15 email. LTV: $1,450.
Enterprise plan. 4+ identity graph connections.

**Journey:** Morning email digest → mobile check during commute →
desktop deep work session → mobile monitoring → evening email summary.
This user expects seamless continuity across all channels.

## Cross-Channel Strategy
- **Consistency:** Value proposition, core metrics, notification content
- **Adaptation:** Layout (mobile-optimized cards vs. desktop tables),
  depth (email summaries → web details), interaction (mobile gestures
  vs. desktop keyboard shortcuts)
- **Identity gaps:** 30% of profiles have only 2 identifiers. Push
  mobile app install and email verification to resolve anonymous web
  visitors to known profiles.
```

### Error Handling

<AccordionGroup>
  <Accordion title="Identity graph requires Engage">The identity resolution features (`external_ids`, merged profiles) require Segment Engage. On Connections-only plans, profiles exist per-source without cross-source merging.</Accordion>
  <Accordion title="External ID types">Common `external_id` types include `user_id`, `email`, `ios_id`, `android_id`, `anonymous_id`, and `ga_client_id`. The code counts ID types to estimate cross-channel coverage — more ID types = more channels connected.</Accordion>
  <Accordion title="Channel activity estimation">The example uses traits like `web_sessions_30d` and `mobile_sessions_30d`. These must be set up as computed traits in Segment Engage. If they don't exist, compute from raw events using the Events API or set up custom computed traits.</Accordion>
</AccordionGroup>

***

## What's Next

<CardGroup cols={2}>
  <Card title="Segment Integration" icon="diagram-project" href="/integrations/segment">
    Back to Segment integration overview
  </Card>

  <Card title="Real-Time Persona Triggers" icon="bolt" href="/integrations/segment/realtime-persona-triggers">
    Auto-trigger research from live events
  </Card>

  <Card title="Mave Agent" icon="brain" href="/api-reference/mave">
    Full reference for POST /api/v1/mave/chat
  </Card>

  <Card title="All Integrations" icon="plug" href="/integrations">
    50+ API integrations with full code
  </Card>
</CardGroup>
