Profile Sync

Pre-populate user data during authentication

View as Markdown

When a user authenticates, the SDK can send partner-collected data — profile details, address, insurance, and dependents — alongside the auth.upgrade message. Sunny uses this to pre-populate the user’s account, removing the need for the user to re-enter information they’ve already provided to your application.

How it works

Profile sync data is included in the auth.upgrade WebSocket message that fires when a user authenticates. The backend uses upsert semantics: it only creates or updates records for the fields you provide, and leaves everything else untouched.

Configuration

Pass authUpgradeProfileSync to createSunnyChat. You can provide static data or an async function that resolves the data at authentication time.

Static data

1import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
2
3const chat = createSunnyChat({
4 container: document.getElementById("chat"),
5 partnerIdentifier: "acme-health",
6 publicKey: "pk-sunnyagents_abc_xyz",
7 authType: "tokenExchange",
8 idTokenProvider: async () => getMyToken(),
9 authUpgradeProfileSync: {
10 user_profile: {
11 first_name: "Jane",
12 last_name: "Doe",
13 phone: "+15551234567",
14 date_of_birth: "1990-03-15",
15 gender: "female",
16 },
17 user_address: {
18 address_line_1: "123 Main St",
19 city: "San Francisco",
20 state: "CA",
21 zip_code: "94102",
22 },
23 },
24});

Async provider

Use a function when the data needs to be fetched at authentication time — for example, from your own API:

1const chat = createSunnyChat({
2 container: document.getElementById("chat"),
3 partnerIdentifier: "acme-health",
4 publicKey: "pk-sunnyagents_abc_xyz",
5 authType: "tokenExchange",
6 idTokenProvider: async () => getMyToken(),
7 authUpgradeProfileSync: async () => {
8 const res = await fetch("/api/user/profile", { credentials: "include" });
9 if (!res.ok) return null;
10 const user = await res.json();
11 return {
12 user_profile: {
13 first_name: user.firstName,
14 last_name: user.lastName,
15 phone: user.phone,
16 date_of_birth: user.dob,
17 },
18 user_address: user.address
19 ? {
20 address_line_1: user.address.line1,
21 city: user.address.city,
22 state: user.address.state,
23 zip_code: user.address.zip,
24 }
25 : undefined,
26 insurances: user.insurance
27 ? [
28 {
29 partner_plan_id: user.insurance.planId,
30 member_id: user.insurance.memberId,
31 group_id: user.insurance.groupId,
32 },
33 ]
34 : undefined,
35 };
36 },
37});

Returning null from the provider skips profile sync entirely — the user authenticates normally without any pre-populated data.

Data types

All fields in authUpgradeProfileSync are optional. Include only what your application has collected.

User profile

Basic demographic information. All fields are optional; only provided fields are updated.

1interface AuthUpgradeProfile {
2 first_name?: string | null;
3 last_name?: string | null;
4 phone?: string | null;
5 date_of_birth?: string | null; // "YYYY-MM-DD"
6 gender?: string | null; // "male", "female", "other"
7}

Address

Mailing address for the user. When provided, address_line_1, city, state, and zip_code are required.

1interface AuthUpgradeAddress {
2 address_line_1: string;
3 address_line_2?: string | null;
4 city: string;
5 state: string;
6 zip_code: string;
7 country?: string; // defaults to "USA"
8}

Insurance

Insurance plan records. The partner_plan_id is a UUID that maps to a plan configured in Sunny Central for your partner. Requires partnerIdentifier on the SDK connection.

1interface AuthUpgradeInsurance {
2 partner_plan_id: string; // UUID from Sunny Central
3 member_id: string;
4 group_id: string;
5}

Dependents

Family members or dependents covered under the user’s insurance. Each dependent can be linked to a specific insurance plan by index.

1interface AuthUpgradeDependent {
2 first_name: string;
3 last_name: string;
4 date_of_birth: string; // "YYYY-MM-DD"
5 gender?: string | null;
6 relationship_code: string; // Stedi code (see table below)
7 member_id?: string | null; // if different from subscriber
8 insurance_index?: number | null; // index into the insurances array
9}

The relationship_code field uses Stedi relationship codes:

CodeRelationship
18Self
01Spouse
19Child
20Employee
21Unknown
39Organ donor
40Cadaver donor
53Life partner
G8Other relationship

Linking dependents to insurance plans

The insurance_index field links a dependent to a specific entry in the insurances array (zero-based). If omitted, the dependent is associated with the first insurance plan.

1authUpgradeProfileSync: {
2 insurances: [
3 { partner_plan_id: "plan-a-uuid", member_id: "M001", group_id: "G100" },
4 { partner_plan_id: "plan-b-uuid", member_id: "M002", group_id: "G200" },
5 ],
6 dependents: [
7 {
8 first_name: "Alex",
9 last_name: "Doe",
10 date_of_birth: "2015-06-01",
11 relationship_code: "19", // child
12 insurance_index: 0, // covered under plan-a
13 },
14 {
15 first_name: "Sam",
16 last_name: "Doe",
17 date_of_birth: "1991-11-20",
18 relationship_code: "01", // spouse
19 // insurance_index omitted -- defaults to first plan (plan-a)
20 },
21 ],
22}

Full example

A complete example syncing all data types with an async provider:

1import { createSunnyChat } from "@sunnyhealthai/agents-sdk";
2
3const chat = createSunnyChat({
4 container: document.getElementById("chat"),
5 partnerIdentifier: "acme-health",
6 publicKey: "pk-sunnyagents_abc_xyz",
7 authType: "tokenExchange",
8 idTokenProvider: async () => getMyToken(),
9 authUpgradeProfileSync: async () => {
10 const user = await fetchCurrentUser();
11 return {
12 user_profile: {
13 first_name: user.firstName,
14 last_name: user.lastName,
15 phone: user.phone,
16 date_of_birth: user.dob,
17 gender: user.gender,
18 },
19 user_address: {
20 address_line_1: user.address.street,
21 city: user.address.city,
22 state: user.address.state,
23 zip_code: user.address.zip,
24 },
25 insurances: user.plans.map((plan) => ({
26 partner_plan_id: plan.sunnyPlanId,
27 member_id: plan.memberId,
28 group_id: plan.groupId,
29 })),
30 dependents: user.dependents.map((dep, i) => ({
31 first_name: dep.firstName,
32 last_name: dep.lastName,
33 date_of_birth: dep.dob,
34 gender: dep.gender,
35 relationship_code: dep.relationshipCode,
36 member_id: dep.memberId,
37 insurance_index: dep.planIndex,
38 })),
39 };
40 },
41});

Behavior notes

  • Upsert semantics — the backend creates new records or updates existing ones. Existing data not covered by the payload is left unchanged.
  • Dependent deduplication — dependents are matched by natural key (first name + last name + date of birth). Sending the same dependent twice updates the existing record rather than creating a duplicate.
  • Auth mode compatibility — profile sync works with all authentication modes (token exchange and passwordless).
  • Timing — the async provider is called at authentication time, just before the auth.upgrade message is sent. This ensures data is as fresh as possible.

Next steps