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

# Salesforce activity sync

> How learner activity flows from Juno into Salesforce records.

export const RelatedPages = ({pages = []}) => {
  if (pages.length === 0) return null;
  return <>
      <br />
      <strong>Related articles</strong>
      <CardGroup cols={2}>
        {pages.map(page => <Card title={page.title} href={page.href} key={page.href} />)}
      </CardGroup>
    </>;
};

When a learner does something meaningful — gets an assignment, completes a Unit, passes a quiz — Juno writes it onto that learner's **Juno Learning Activity** record for the Unit. There is **one record per learner per Unit**, and its fields fill in as the learner progresses: assigned, then started, then completed, with the latest quiz attempt alongside. This page explains the flow, what each record contains, and what happens when something can't sync. For setup, see [Salesforce](/integrations/salesforce).

## How the sync works

1. A learner action fires an event. Only the event types you selected sync; everything else is skipped — see [Choosing which events sync](#choosing-which-events-sync).
2. The event is queued in Juno. The queue is durable — Salesforce being briefly unreachable never loses activity.
3. A scheduled sync run drains the queue and upserts records into Salesforce in batches. Runs are **hourly**, so activity appears in Salesforce within the hour rather than instantly.
4. Each learner+Unit pair has one activity key — every event updates that same record, so retries and duplicates can't create double records.

## Event types

| Learner action     | Event type in Salesforce | Fields it fills             |
| ------------------ | ------------------------ | --------------------------- |
| Learning assigned  | `Assigned`               | Assigned At                 |
| Learning started   | `Started`                | Started At                  |
| Progress milestone | `Progress`               | Progress                    |
| Learning completed | `Completed`              | Completed At                |
| Journey completed  | `JourneyCompleted`       | Completed At                |
| Quiz passed        | `QuizPassed`             | Quiz Result, Quiz At, Grade |
| Quiz failed        | `QuizFailed`             | Quiz Result, Quiz At, Grade |

<Note>
  **Report on the timestamp fields, not Event Type.** Event Type shows the latest synced event; the lifecycle truth is the timestamps. "Assigned but never started" = Assigned At is set and Started At is blank. "Completions" = Completed At is set.
</Note>

<Note>
  **A quiz attempt is not a completion.** Passed or failed, the latest attempt lands in Quiz Result / Quiz At / Grade. Completed At is set only when the learner completes the Unit or Journey itself — a completions report never counts failed attempts.
</Note>

<Note>
  **Timestamps are learning times.** Assigned At, Started At, Completed At and Quiz At record when the learner acted — not when Juno synced the event. Retaken quizzes keep the latest attempt.
</Note>

## Choosing which events sync

Go to **Admin → Settings → Integrations**. Under **Activity events written to Salesforce**, tick the events you want. Connecting your org switches all seven on, so activity flows from day one — clear any you don't want in Salesforce, then **Save**.

<Warning>
  **Clearing every event stops the sync.** With nothing ticked, Juno writes nothing to Salesforce — existing records stay as they are, but they stop updating. The page warns you when no events are selected.
</Warning>

Changes apply to events fired from that point on. Turning an event back on does not backfill the period it was off.

## What's on a record

| Field                                   | Content                                                                     |
| --------------------------------------- | --------------------------------------------------------------------------- |
| Juno Activity Key                       | Unique key per learner+Unit — the upsert external ID (dedupes retries)      |
| Event Type                              | Latest synced event (see the report-on-timestamps note above)               |
| Juno Unit Id                            | The Unit the record belongs to                                              |
| Unit Name                               | Title of the course, resolved on every sync so renames catch up             |
| Deep Link                               | Opens the course for the learner — the same link the assignment email sends |
| Progress                                | Latest milestone percentage                                                 |
| Assigned At / Started At / Completed At | Lifecycle times, per the table above                                        |
| Quiz Result / Quiz At / Grade           | Latest quiz attempt: outcome, time, score                                   |
| SF User / Contact + Account             | The matched learner (see below)                                             |
| Learner Kind                            | `Staff` or `Customer`                                                       |

## How learners are matched

Juno matches learners to Salesforce by email — the learner's primary email in Juno against:

* **Staff** learners → an **active Salesforce User** with that email.
* **Customer** learners → a **Contact** with that email (the Contact's Account is linked too).

If a staff learner has no matching active User, the event retries — a User provisioned later still syncs. If a customer learner has no Contact, the event is parked and won't retry on its own; create the Contact and re-enqueue via support.

## When something fails

* Failed sends retry automatically with increasing delays (up to an hour apart). After 8 attempts a row is marked dead and kept for inspection.
* A **lost session** (expired or revoked Salesforce access) shows a reconnect banner on the integration card. Activity keeps queueing and drains after you reconnect — nothing is lost, and the wait burns no retry attempts. This is not the same as [disconnecting deliberately](/integrations/salesforce#disconnecting-and-reconnecting), which drops new activity instead of holding it.
* Synced rows are pruned from Juno's queue after 30 days (failed entries are kept 90 days for diagnosis, then pruned); the Salesforce records are permanent.

<RelatedPages
  pages={[
{ href: "/integrations/salesforce", title: "Salesforce — connect your org" },
{ href: "/integrations/salesforce-records-and-reports", title: "Salesforce records and reports" },
{ href: "/integrations/salesforce-provisioning", title: "Provision customer learners from Salesforce" },
{ href: "/integrations/salesforce-troubleshooting", title: "Salesforce sync troubleshooting" },
]}
/>
