# Leadara — Integration Guide

> Context for AI coding assistants. Paste this file into your project (or attach it to your AI coding tool) and ask it to integrate **Leadara** — event tracking, user identification, and audience automation — into your app, website, or backend. Follow the conventions exactly.

Leadara is a marketing automation and customer-analytics platform. You send it **events** (what people do) and **identities/traits** (who they are). Leadara then powers analytics, segments, journeys (automations), popups, and campaigns. Your job as the integrator is just to **send good events**; everything else is configured by the marketer in the Leadara dashboard.

---

## Instructions for the assistant — read this first

Do **not** stop at wiring the SDK or HTTP client. Before writing integration code:

1. **Explore the host project.** Read routes, pages, auth flows, payment/checkout handlers, webhooks, background jobs, and any existing analytics hooks. Infer what the product does and which user actions matter for marketing and retention.
2. **Propose an event plan** (in your reply to the user) before or alongside implementation: event names, when each fires, which properties to attach, and whether each call belongs in the browser, the server, or both. Tailor the plan to *this* codebase — do not copy a generic checklist blindly.
3. **Instrument at the source of truth.** Fire conversion and billing events from the server (webhook handler, order service, subscription callback) when one exists — the browser alone is not authoritative for money or account state. Use the browser SDK for navigation, UI interactions, and anything that only happens client-side.
4. **Keep names stable forever.** Pick snake_case event names once and reuse them byte-for-byte everywhere. Journeys and segments match **exact, case-sensitive** names; a typo means an automation silently never fires.
5. **Identify early, identify often.** Call `identify` as soon as you know who the user is (sign-up, login, session restore) and again when important traits change (plan, locale, contact info).
6. **Use configuration, not hardcoded URLs or secrets.** Resolve all values from the table below. Never commit production URLs or keys into source when env-based config is available.

The sections below explain *how* to send data. **You** decide *what* to send by reading the project.

---

## Configuration (resolve before any code)

Read these from environment variables, `.env` files, or the Leadara dashboard (**Integrations → Sources** / **Install**). Use the same names in examples below.

| Variable | Required | Description |
| --- | --- | --- |
| `LEADARA_API_BASE` | yes | API origin **without** a trailing slash. Shown on the Install screen in the dashboard. Local dev is usually `http://localhost:9000`. |
| `LEADARA_WORKSPACE_ID` | yes (SDK) | Workspace UUID from the dashboard. |
| `LEADARA_WRITE_KEY` | yes | Write key for the `x-write-key` header / SDK snippet. Publishable (write-only). |

**Every URL in this document** is built from `LEADARA_API_BASE`. Examples use `${LEADARA_API_BASE}` — substitute the resolved value at runtime. Do not hardcode a production hostname in application source.

---

## Discovering what to track (generic playbook)

There is no fixed event list for every product. Derive events from **user journeys** you find in the code.

### Step 1 — Map the journeys

Walk through the codebase and list the main flows, for example:

- How does someone become a user? (sign-up, invite, OAuth, guest checkout)
- What is the core value action? (the thing you'd put on a dashboard as "success")
- What steps lead to that action? (browse → select → configure → confirm)
- What keeps people coming back? (return visits, notifications, progress, renewals)
- Where can someone drop off or churn? (abandoned flows, cancellations, errors)

You do not need the user to spell this out — infer it from routes, components, services, and domain models.

### Step 2 — Turn journeys into events

For each meaningful step, define **one** `track` event (or rely on automatic `page` / `screen` where sufficient):

| Journey moment | Typical event shape | Where to fire |
| --- | --- | --- |
| Account created | `signed_up` + `{ source, method }` | Server preferred; client OK as backup |
| Signed in | `logged_in` | After auth succeeds |
| Core item/content viewed | `{entity}_viewed` + id/slug properties | Page mount or detail API success |
| Selection added / saved | `{entity}_selected` or `{entity}_added` | UI action or API success |
| Flow started but not finished | `{flow}_started` | Enter checkout, onboarding step 1, wizard open |
| Success / conversion | `{outcome}_completed` + value/id properties | **Server** when state is committed |
| Subscription or plan change | `subscription_started` / `subscription_cancelled` + plan fields | Billing webhook or server handler |
| Invite or referral succeeds | `referral_completed` + code/id | Server when reward is granted |
| Meaningful progress | `{step}_completed` + context ids | When your domain marks progress done |

Use **consistent property names** across related events (`entity_id`, `entity_type`, `amount`, `currency`, `order_id`, `plan`, `source`). Prefer a small set of well-named events over dozens of one-offs.

### Step 3 — Define traits on `identify`

Attach durable facts about the person, not one-off event details:

- **Reachability:** `email`, `phone` (needed for email/SMS journeys)
- **Identity:** `name`, `locale`, `timezone`
- **Relationship to the product:** `plan`, `role`, `signup_date`, `account_id`
- **Segmentation helpers:** anything the marketing team would filter on (status, tier, preferences) — derive names from your domain models

Re-send `identify` when these change, not on every page view.

### Step 4 — Choose browser vs server

| Signal | Browser (SDK) | Server (HTTP API) |
| --- | --- | --- |
| Page / screen views | yes (automatic + manual) | optional |
| Clicks, UI funnels | yes | rarely |
| Auth identity | yes, after login | yes, on sign-up webhook |
| Payments, orders, refunds | no | **yes** |
| Webhook-driven state | no | **yes** |
| Background jobs / cron | no | **yes** |

When both sides fire the same event, use the **same event name and properties** so analytics stay unified.

### Step 5 — Where to hook in code (patterns to search for)

Search the codebase for these patterns and attach calls at the success path (after validation, after DB commit, after HTTP 2xx):

- Auth: `login`, `signUp`, `register`, `session`, `passport`, `next-auth`, OAuth callbacks
- Commerce: `checkout`, `cart`, `order`, `payment`, `stripe`, `webhook`, `invoice`
- Content: route loaders, `useEffect` on detail pages, `onSuccess` after fetch
- Onboarding: step components, progress reducers, "complete profile" handlers
- Billing: subscription services, plan change endpoints, cancellation flows
- Async: queue workers, `POST` handlers that finalize state

Avoid tracking in render paths, failed requests, or before the action is durable.

### Step 6 — Deliverables for the user

When you finish, give the user a short summary:

1. Event catalog you implemented (name → when → properties → client/server)
2. Env vars they must set
3. Any server-only endpoints you instrumented
4. Gaps you intentionally skipped (and why)

---

## Quick facts

- **API base URL:** `${LEADARA_API_BASE}` — always from configuration (see table above).
- **Authentication:** every request carries a **write key** in the `x-write-key` HTTP header. The write key is **publishable** (safe to ship in browser/mobile clients) and only allows *writing* events — it can't read data.
- **Content type:** request bodies are JSON (`Content-Type: application/json`).

## Get your credentials

1. Sign in to the Leadara dashboard.
2. Go to **Integrations → Sources** (or the **Install** / **Website SDK** screen).
3. Create a source if you don't have one, then copy:
   - **Workspace ID** → `LEADARA_WORKSPACE_ID`
   - **Write key** → `LEADARA_WRITE_KEY`
   - **API base URL** → `LEADARA_API_BASE`

Keep one write key per source (web, mobile, server). You can rotate or disable a key from the same screen.

## Pick your integration path

Choose the **first** one that matches the project:

1. **WordPress site** → install the **Leadara WordPress plugin**, paste Workspace ID + write key, done. (No code.)
2. **Website or web app** (any framework) → drop in the **JavaScript SDK** snippet. Auto-tracks page views; exposes `track` / `identify` helpers.
3. **Mobile app, backend, server-side jobs, or anything non-browser** → call the **HTTP API** directly.

You can combine paths (e.g. SDK on the website + HTTP API from your backend) — use keys from the same workspace.

---

## Path 1 — WordPress plugin

1. Install the **Leadara** plugin from WordPress admin (**Plugins → Add New**, or upload the plugin zip).
2. Open plugin settings and paste **Workspace ID** and **Write key** (and API base URL if the form asks for it).
3. Save. Page views flow into Leadara automatically.

Add SDK or HTTP API calls below for custom events the plugin does not cover.

---

## Path 2 — JavaScript SDK (website / web app)

Add this snippet to the site's `<head>` (or via your tag manager). Resolve all values from configuration:

```html
<script
  async
  src="${LEADARA_API_BASE}/sdk/${LEADARA_WORKSPACE_ID}/sdk.js"
  data-write-key="${LEADARA_WRITE_KEY}"
></script>
```

In real code, inject the URL from env at build time or from your CMS/settings — do not leave literal `${...}` placeholders in production HTML.

Optional script attributes:

- `data-session-replay="true"` — record masked session replays (privacy policy must cover it). Uses HTTP uploads only — independent of the live connection below.
- `data-replay-sample-rate="0.5"` — fraction of sessions to record (0–1) when replay is on.
- `data-realtime="false"` — skip the background live connection. **Still works:** `track`, `identify`, `group`, `page`, and session replay. **Disabled:** instant popup push and the live chat widget (popups can still load on page open via HTTP).
- `data-debug="true"` — log SDK diagnostics to the browser console.

Once loaded, the SDK **bootstraps automatically** and tracks page views. `window.leadara` exposes the API. Calls made before load are queued safely.

### SDK integration rules (important)

Follow these so you do not get duplicate connections or surprise network traffic:

1. **Load the snippet once** — one `<script>` tag in `<head>` (or your tag manager). Do not inject it on every route change.
2. **Do not call `window.leadara.init()`** when the script tag is present — the SDK auto-starts from the tag. Calling `init()` again creates a second client.
3. **Use only the public API** — `identify`, `track`, `group`, `page`, `screen`, and optionally `openChat` / `closeChat` / `toggleChat`. You do not need to wire your own live connection or listen for server navigation events.
4. **Analytics-only sites** (no popups, no live chat) — add `data-realtime="false"` to keep HTTP event tracking and optional session replay without opening a live connection.
5. **Session replay** — enable with `data-session-replay="true"`; it does not require realtime and does not control whether a live connection opens (realtime is on by default unless you opt out).

### SDK methods

```js
// Identify the signed-in user and attach traits — call on login and when traits change.
window.leadara.identify("user-123", {
  email: "sam@example.com",
  name: "Sam Rivers",
  plan: "pro",
});

// Track a custom event — name and properties from your event plan.
window.leadara.track("checkout_started", {
  cart_value: 149000,
  currency: "IRR",
  item_count: 3,
});

// Manual page view (SDK also tracks on load).
window.leadara.page("Pricing", { plan: "pro" });

// Mobile-style screen view (hybrid / webview apps).
window.leadara.screen("Settings");

// Associate the user with an account/organization.
window.leadara.group("acct-42", { company_name: "Acme Co." });

// Live chat widget (if enabled for the workspace):
window.leadara.openChat();
window.leadara.closeChat();
window.leadara.toggleChat();

// Web push for journey notifications (requires setup below):
const pushResult = await window.leadara.subscribePush();
// "subscribed" | "unsupported" | "denied" | "skipped" | "not_configured"
```

Notes:

- The SDK manages an **anonymous id** and links it after `identify`, so pre-login events still attach to the right person.
- Event names: stable, lowercase, snake_case (e.g. `signed_up`, `order_completed`).
- Methods return promises; awaiting is optional for fire-and-forget tracking.

### Web push for visitors (journey Web Push nodes)

**Team live-chat alerts in the dashboard do not need this** — only visitors who should receive journey push notifications.

Browsers require a **service worker on the same origin** as your site. The main SDK script loads from Leadara's API; the push worker must be hosted on **your** domain.

1. **Download** the service worker (static file, rarely changes):

   ```
   GET ${LEADARA_API_BASE}/leadara-push-sw.js
   ```

2. **Host it at your site root** so it is served at:

   ```
   https://your-domain.com/leadara-push-sw.js
   ```

   Same origin as your pages (e.g. `www` vs non-`www` matters). Put the file in your static `public/` folder or equivalent.

3. **Ask for permission in context** — after sign-up, purchase, or a settings toggle — not on first page load. Then call:

   ```js
   const result = await window.leadara.subscribePush();
   if (result === "subscribed") {
     // user is registered for journey web push
   }
   ```

4. **`subscribePush()` return values:**

   | Value | Meaning |
   | --- | --- |
   | `subscribed` | Browser permission granted; subscription saved for this visitor |
   | `denied` | User blocked notifications in the browser |
   | `unsupported` | Browser lacks service worker / push APIs |
   | `not_configured` | VAPID keys not set on the Leadara server |
   | `skipped` | Missing write key or subscribe API failed |

5. **Call `identify` when the user logs in** so anonymous subscriptions stitch to the known profile (same as analytics).

6. **Journey behavior:** A **Web Push** node sends only to visitors with an active subscription. If none exists, the step fails with **No push subscriptions** in run history — expected until step 3 is done.

**Test:** Chrome or Firefox desktop first. Safari on macOS needs recent OS versions; iOS needs a PWA added to the home screen for web push.

---

## Path 3 — HTTP API (mobile / backend / custom)

Call these endpoints from any HTTP client. Base URL: `${LEADARA_API_BASE}`. Header on every request: `x-write-key: ${LEADARA_WRITE_KEY}`.

### Conventions

- **Who did it:** `userId` (known user) and/or `anonymousId` (visitor). For `track`, `page`, and `screen`, **at least one** is required.
- **Timestamps:** optional ISO‑8601 `timestamp`. Omit to use server receive time.
- **Properties vs traits:** `properties` = this event. `traits` = the person or group.
- **Retries:** safe on network errors; reuse the same `timestamp` for de-duplication.

### Response envelope

Success:

```json
{
  "success": true,
  "data": { "...": "result, or null" },
  "meta": { "timestamp": "2026-01-01T12:00:00.000Z", "requestId": "..." }
}
```

Failure:

```json
{
  "success": false,
  "error": { "code": "VALIDATION_ERROR", "message": "human readable", "details": [] },
  "meta": { "timestamp": "2026-01-01T12:00:00.000Z", "requestId": "..." }
}
```

Branch on `success` (and HTTP status) before reading `data`.

### Errors

| HTTP | code | meaning |
| --- | --- | --- |
| 400 | `VALIDATION_ERROR` | Body failed validation; see `error.details`. |
| 400 | `BAD_REQUEST` | Malformed request. |
| 401 | — | Write key missing or invalid. |
| 403 | — | Source inactive, expired, or missing `Write` permission. |
| 429 | — | Rate limit; slow down and retry. |
| 500 | `INTERNAL_SERVER_ERROR` | Server error; retry with backoff. |

### Endpoints

All paths are relative to `${LEADARA_API_BASE}`.

#### `POST /track` — custom event

```json
{
  "userId": "user-123",
  "name": "order_completed",
  "properties": { "order_id": "A-1001", "total": 249000, "currency": "IRR" },
  "timestamp": "2026-01-01T12:00:00.000Z"
}
```

```bash
curl -X POST "${LEADARA_API_BASE}/track" \
  -H "x-write-key: ${LEADARA_WRITE_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"userId":"user-123","name":"order_completed","properties":{"order_id":"A-1001","total":249000,"currency":"IRR"}}'
```

```javascript
const base = process.env.LEADARA_API_BASE;
const writeKey = process.env.LEADARA_WRITE_KEY;

await fetch(`${base}/track`, {
  method: "POST",
  headers: { "x-write-key": writeKey, "Content-Type": "application/json" },
  body: JSON.stringify({
    userId: "user-123",
    name: "order_completed",
    properties: { order_id: "A-1001", total: 249000, currency: "IRR" },
  }),
});
```

#### `POST /page` — page view

```json
{ "userId": "user-123", "name": "Pricing", "properties": { "plan": "pro" } }
```

#### `POST /screen` — mobile screen view

```json
{ "userId": "user-123", "name": "Settings", "properties": {} }
```

#### `POST /identify` — identify a user & set traits

Requires **both** `userId` and `anonymousId`. Use the same `anonymousId` as pre-login events so activity merges.

```json
{
  "userId": "user-123",
  "anonymousId": "anon-9f2c0d1e",
  "traits": { "email": "sam@example.com", "name": "Sam Rivers", "plan": "pro" }
}
```

#### `POST /group` — associate user with an account

```json
{ "groupId": "acct-42", "userId": "user-123", "traits": { "company_name": "Acme Co.", "seats": 12 } }
```

#### `POST /alias` — merge two identities

Use when an anonymous id becomes a known user (e.g. after sign-up).

```json
{ "userId": "user-123", "previousId": "anon-9f2c0d1e" }
```

#### `POST /batch` — many events in one request

Up to **100** events. Each item includes a `type` field.

```json
{
  "batch": [
    { "type": "identify", "userId": "user-123", "anonymousId": "anon-9f2c0d1e", "traits": { "email": "sam@example.com" } },
    { "type": "track", "userId": "user-123", "name": "signed_up", "properties": { "plan": "pro" } },
    { "type": "page", "userId": "user-123", "name": "Welcome" }
  ]
}
```

```bash
curl -X POST "${LEADARA_API_BASE}/batch" \
  -H "x-write-key: ${LEADARA_WRITE_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"batch":[{"type":"track","userId":"user-123","name":"signed_up","properties":{}}]}'
```

Response:

```json
{ "success": true, "data": { "processed": 3, "success": 3, "failed": 0 } }
```

---

## Naming and data quality

> **Names are matched exactly.** `order_completed` and `Order Completed` are different events. Pick snake_case once and never change spelling in production.

**Starter events** many products end up needing (adapt names/properties to the domain you discovered):

- Lifecycle: `signed_up`, `logged_in`, `profile_completed`
- Engagement: `{entity}_viewed`, `{entity}_selected`, `{action}_started`
- Conversion: `{outcome}_completed` with `total` / `amount`, `currency`, and stable ids
- Retention: `{milestone}_reached`, `{step}_completed`
- Monetization: `subscription_started`, `subscription_cancelled`, `trial_ended`
- Growth: `referral_completed`, `invite_accepted`

**Starter traits:** `email`, `phone`, `name`, `plan`, `locale`, `signup_date`. Include `email` and/or `phone` so email/SMS journeys can reach the person.

For revenue reporting, send the primary conversion event with numeric `total` or `amount` plus `currency`.

---

## Auth & login (required for SMS/email journeys)

Journeys read the **user profile** (traits and contact columns), not one-off properties on a `track` event. If `phone` or `email` is missing when an SMS or email step runs, the step fails with a clear message in run history.

### Login pattern

On every page load where the user is authenticated:

1. Call **`identify(userId, { email, phone, … })` first** — include reachability traits every time.
2. Then call **`track('logged_in')`** (or your login event) if you need it for analytics or triggers.

`track` does **not** carry traits. Do **not** trigger SMS/email journeys on `logged_in` unless `identify` with `phone`/`email` has already run on that same page load (or use **`identify` as the journey trigger** instead).

```javascript
// After auth succeeds (same page load, identify before track)
window.leadara.identify(user.id, {
  email: user.email,
  phone: user.phone,
  name: user.name,
  plan: user.plan,
});
window.leadara.track("logged_in", { method: "password" });
```

### Session restore on app boot

When your app starts and a session already exists, call **`identify` with traits from your user database** — not only on fresh login. Returning visitors often skip the login screen; without this, their profile may be empty and SMS/email steps will skip them.

```javascript
if (session?.user) {
  window.leadara.identify(session.user.id, {
    email: session.user.email,
    phone: session.user.phone,
    plan: session.user.plan,
  });
}
```

### Trait requirements for SMS and email

| Channel | Required on profile | Notes |
| --- | --- | --- |
| SMS | `phone` in identify traits (or `mobile` / `phoneNumber`) | Journeys resolve phone at send time from the profile |
| Email | `email` in identify traits | Same — profile must have email before the step runs |

Recommended: trigger welcome SMS on **`identify`** (after traits are saved), or add an **If** step that checks phone before the SMS node.

### Ordering and race conditions

- `identify` saves traits on ingest; journey triggers are processed **asynchronously** (typically ~1 second after the event).
- Identity stitching may run in parallel with journey triggers — always send **`identify` before `logged_in`** on the same load so the profile is populated first.
- **`track` events do not update traits** — never rely on login event properties for contact info.

### Legacy users

Users who existed in your product **before** Leadara was integrated may have empty profiles until their next login with traits or a server-side `identify` backfill. Plan a one-time `identify` from your user DB for active accounts if you enable SMS journeys.

### Phone format

Prefer **E.164** where possible (`+989121234567`). Iran local numbers (`0912…`) often work but normalization on your side reduces delivery failures. Leadara stores the value you send; your SMS provider may require a specific format.

---

## Implementation checklist for the assistant

- [ ] Read the codebase and wrote an event plan tailored to this project
- [ ] `LEADARA_API_BASE`, `LEADARA_WORKSPACE_ID`, `LEADARA_WRITE_KEY` read from config — no hardcoded production host
- [ ] `identify` on sign-up/login and when traits change
- [ ] **`identify` before `logged_in` on the same page load**; include `email` and/or `phone` for SMS/email journeys
- [ ] **Session restore:** call `identify` with traits from your user DB on app boot when a session exists
- [ ] Do not trigger SMS journeys on `logged_in` unless profile already has `phone` (prefer `identify` as trigger or guard with an If step)
- [ ] Conversion events on the server when authoritative state exists
- [ ] Stable snake_case event names; rich, consistent properties
- [ ] Web: JS SDK where possible; HTTP API for server/mobile/background
- [ ] SDK snippet loaded **once**; no manual `window.leadara.init()` when the script tag auto-bootstraps
- [ ] Use `data-realtime="false"` when the site only needs analytics/replay (no live popups or chat)
- [ ] **Journey web push:** host `/leadara-push-sw.js` on the site root (download from `${LEADARA_API_BASE}/leadara-push-sw.js`); call `subscribePush()` after user opt-in
- [ ] Failures handled (`success` / HTTP status); retry with backoff on `429`/`5xx`
- [ ] Summary delivered to the user (event catalog + env vars + gaps)
- [ ] Write key never used to read data — it is write-only by design
