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

# Salesforce permissions, scopes, and the integration user

> What access Introw needs in Salesforce: OAuth scopes, why to use a dedicated integration user, and the minimal object and field permissions required.

Introw runs in Salesforce under the identity of the **user who authorizes the connection**. It has exactly that user's access - no more. Whatever objects, fields, and records that user can read or edit, Introw can; anything gated by their profile, permission sets, field-level security, or sharing rules is gated for Introw too.

That is the important part for security review: **you control everything Introw can touch by controlling one Salesforce user.** This guide covers the OAuth scopes Introw requests, why we recommend a dedicated integration user, and the minimal object and field access - read versus write - your partner program actually needs.

<Note>
  Introw honours Salesforce security at every layer. Reads and writes run in the user's context (CRUD, field-level security, and sharing all apply), so Introw can never reach data the connecting user can't.
</Note>

## Use a dedicated integration user

Because Introw inherits the connecting user's permissions, **connect with a dedicated Salesforce integration user** rather than a person's own login. We recommend this to every customer, and it's the cleanest way to run the integration:

<CardGroup cols={2}>
  <Card title="Least privilege" icon="lock">
    A permission set scoped to exactly the objects and fields your program uses is the boundary of what Introw can touch.
  </Card>

  <Card title="Survives staff changes" icon="anchor">
    The connection isn't tied to a person, so it doesn't break when someone changes roles or leaves.
  </Card>

  <Card title="Auditable" icon="clipboard-check">
    Every Introw change is attributed to one clearly-named user in your Salesforce audit trail.
  </Card>
</CardGroup>

Create the user (a Salesforce or Salesforce Integration license is typically enough), assign a permission set with the access below, and authorize Introw while logged in as that user.

<Warning>
  A scoped integration user is also the user most likely to be **refused at the OAuth screen**. Since September 2025 Salesforce blocks authorization of any connected app that is not installed in the org, exempting only users who hold **Approve Uninstalled Connected Apps** - which system administrators have by default and a purpose-built integration user does not. So an admin's test connection succeeds, and the integration user you actually want to run on fails. Allow the app first: see [Allow the Introw connected app](#allow-the-introw-connected-app).
</Warning>

## Allow the Introw connected app

This is the most common blocker on a first Salesforce connection, and it is worth planning for rather than discovering. Since September 2025 Salesforce refuses OAuth authorization for any connected app that is not installed in the org, unless the authorizing user holds a permission that exempts them. System administrators hold that permission by default, which is why an admin's own quick test succeeds while **the dedicated integration user we recommend fails on the same org**.

Already-connected orgs are unaffected. Salesforce keeps honouring an authorization completed before the restriction, so this only bites a new connection or a new connecting user. Switching an existing integration to a fresh integration user counts as new.

<Warning>
  This is not the AppExchange package. Two separate Introw artifacts land in Salesforce, and each solves a different problem. The **Introw managed package** carries the objects, the permission set, and the Lightning component behind the in-CRM embed. The **Introw connected app** is the OAuth client the connection itself runs through, and it is a standalone app rather than part of the package. Installing the package does not allow the connected app, and allowing the connected app does not install the package. A complete setup needs both.
</Warning>

Expect the first attempt to fail, and let it. Salesforce only lists a connected app for your org once the org has actually received a request for it, so there is nothing to install until someone has tried to connect.

When the attempt is refused, Salesforce ends the flow on its own page and never redirects back, so Introw records nothing at all: no connection, no error, no half-finished integration to clean up. What you see is Salesforce's message, **"We can't authorize you because of an OAuth error. For more information, contact your Salesforce administrator."**, with `error=invalid_client` and `error_description=app+must+be+installed+into+org` in the URL.

<Steps>
  <Step title="Attempt the connection once and let it fail">
    Run the connect flow in Introw ([Connect Salesforce](./connect-salesforce)) as the user Introw should run as. The refusal is what puts the app on your org's radar.
  </Step>

  <Step title="Open Connected Apps OAuth Usage">
    In Salesforce, open **Setup** and search Quick Find for `OAuth`, then select **Connected Apps OAuth Usage** (under **Apps > Connected Apps**). The page lists every connected app the org has seen a request for, installed or not, which is why the failed attempt had to happen first.
  </Step>

  <Step title="Read the Introw row">
    Find the row named **Introw**. Your refused attempts are counted under **Denied Attempts Due to Usage Restriction**, with a timestamp in **Last Denied Attempt Due to Usage Restriction**, and **User Count** stays at **0** because nobody completed authorization. **App Status** still reads **Allowed**, which trips people up: nobody blocked Introw, the restriction applies because the app is not installed. The available actions on the row are **Block** and **Install**.
  </Step>

  <Step title="Install the app, or exempt the connecting user">
    Both routes below unblock the connection. Installing is the better default. Pick one, then authorize again from Introw.
  </Step>
</Steps>

### Option A: install the app (recommended)

Select **Install** on the Introw row and confirm. Salesforce converts it into an installed connected app in your org, which means the app now sits under your own access controls instead of Salesforce's blanket restriction.

Then decide who may authorize it. Open **Setup > Manage Connected Apps**, select **Introw**, and set the **Permitted Users** OAuth policy:

| Permitted Users                             | What it means                                                                 | When to pick it                                                                                                                                      |
| ------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Admin approved users are pre-authorized** | Only the profiles and permission sets you assign to the app can authorize it. | The tighter choice, and the natural pairing with a dedicated integration user: assign that user's permission set and nobody else can connect Introw. |
| **All users may self-authorize**            | Any user in the org can complete the Introw OAuth flow.                       | Simpler, and fine if you are comfortable with any user being able to authorize Introw under their own permissions.                                   |

If you choose **Admin approved users are pre-authorized**, assign the integration user's profile or permission set to the app before reconnecting, or the authorization is refused again for a different reason.

Prefer this route: the decision is recorded on the app itself, it survives staff changes, it does not widen what anyone can authorize elsewhere in Salesforce, and it is the version a security reviewer will accept.

### Option B: grant the connecting user the permission

Enable **Approve Uninstalled Connected Apps** for the user who authorizes Introw, under **System Permissions** on a profile or, better, a permission set assigned only to that user. They can then authorize Introw without the app being installed in the org.

<Warning>
  **Approve Uninstalled Connected Apps** is not Introw-specific. It lets that user authorize **any** uninstalled connected app, which is exactly the exposure Salesforce's restriction exists to close. Keep it on a permission set assigned to the integration user alone, never on a broadly-shared profile. Salesforce's **Use Any API Client** permission also bypasses the restriction, but it is far broader still: do not use it for this.
</Warning>

### Verify the app is allowed

Reconnect from Introw. The Salesforce approval screen now appears, the connection completes, and back on **Connected Apps OAuth Usage** the **Introw** row shows **User Count** at **1** (your integration user) with the denied-attempt counter no longer climbing. If you installed the app, it also appears under **Setup > Manage Connected Apps**, where you can revoke it at any time.

## OAuth scopes

Introw connects via OAuth (a modern External Client App, or a connected app). It requests three scopes:

| Scope                              | What it lets Introw do                                                                                             |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| **Api**                            | Read and write Salesforce data through the API - the core two-way sync, within the integration user's permissions. |
| **Refresh token (offline access)** | Keep the connection alive without asking someone to log in again. Refresh tokens rotate for security.              |
| **Lightning**                      | Render the Introw component inside your Lightning record pages (the in-Salesforce embed).                          |

The connection uses **PKCE** and a **rotating refresh token**. Scopes define what the token *can* do at the API level; the integration user's permissions still decide which objects and fields are actually reachable.

## What access Introw needs

The principle is simple: **Introw only needs access to the objects you embed in the portal or report on** - nothing else. Read and write are managed separately per object and field (through the permission set and field-level security), so you can grant read-only broadly and write only where you need it.

### Minimal baseline (read)

To identify partners and their people, Introw reads a small set of standard fields:

| Object                        | Fields (read)                                                                                                                    | Why                                                                             |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| **Account** (partner company) | `Name`, `Website`, `Industry`, `Description`, `NumberOfEmployees`, `AnnualRevenue`, `Phone`, `Billing*` address, `OwnerId`, `Id` | Populate the partner profile; `Website` also drives domain-based portal access. |
| **Contact** (portal users)    | `FirstName`, `LastName`, `Email`, `Title`, `Phone`, `MobilePhone`, `Id`                                                          | Identify and match the people who log into the portal.                          |
| **User** (owners/managers)    | `Id`, `FirstName`, `LastName`, `Email`, `SmallPhotoUrl`                                                                          | Resolve owners and partner managers to Introw users by email.                   |

Any other standard or custom Account/Contact field you want in Introw (for segments, reports, portal columns) just needs read access on that field.

### Per object you collaborate on or include

For each additional object you want in the portal or in reporting - **Opportunity, Case, a custom object, ...** - Introw needs two things:

1. **Read on the object and the fields you pull in** - the properties you display in Introw or expose to partners.
2. **Read on whatever links it to a partner** - the attribution, whether that's the **OpportunityPartner** junction, or a lookup/picklist field on the record. See [Attribution in Salesforce](./attribution-in-salesforce).

If you never surface Cases, Introw never needs access to Case. Access tracks exactly the surface area of your program.

## When Introw needs write access

Reads cover most of the program. Introw needs **write** access only for the specific, opt-in actions you configure:

| Action                                 | Write access needed                                                                                                                                                                                                                                                         |
| -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Forms create records**               | If a partner form creates Opportunities, Leads, Accounts, Contacts, Cases, or a custom object, the integration user needs **Create** on that object and **edit** on the mapped fields. On a new Opportunity, Salesforce also requires `Name`, `CloseDate`, and `StageName`. |
| **Partners edit fields in the portal** | Any field you let partners edit needs **field-level edit** for the integration user, since the edit writes back as that user.                                                                                                                                               |
| **Attribution on form-created deals**  | If attribution uses the junction, Introw writes an **OpportunityPartner** row (`Role`, `IsPrimary`) - so it needs create on that relationship.                                                                                                                              |
| **Write-back mappings**                | Pushing tier, partner manager, contact role, or portal access to Salesforce needs **edit** on those (usually Introw-created) fields.                                                                                                                                        |
| **LMS (managed package)**              | Course-enrollment and certificate objects are written by Introw; the managed package's permission set grants this.                                                                                                                                                          |

Everywhere else, Introw follows a safe default: it only writes fields you've mapped or a form is set to write, defaults to never overwriting a value that's already set, and never mass-edits records. See [CRM field mapping](/features/forms/crm-automations).

## How Introw shows you what it is missing

Least privilege only works if a gap is visible.
Because Introw inherits the connecting user's access, a shortfall does not look like an error: it looks like Introw being wrong.
A partner count comes back short, a mapped field never lands, a submission saves without half its values.

So Introw checks what the connection is actually allowed to do and marks the shortfall on the screen where you would otherwise configure something that cannot work.

* **On the integration's Object linking step**, every linked object carries a verdict pill with the reasoning on hover: **Read-only**, **Partly visible**, **Fields read-only**, **Record types limited**, **Cannot read back**, and the rest. The panel opens with one line naming the connection and whether the check ran at all.
* **On a form's Automation tab**, each CRM object automation the connection cannot write carries a warning, and every unwritable property is struck through in the picker with its reason. **No write permission for this connection** is the one you can fix; **Calculated in your CRM** and **Managed by your CRM** are not.
* **Re-check after changing permissions** re-probes on demand, so a permission set you just assigned shows up immediately rather than after the cache expires.

What Introw checks is the union of the profile and every permission set assigned to the connecting user: object CRUD, field-level security, the object's sharing model and any View All grant, and which record types that user holds.
Where it cannot check, it says so rather than reporting a clean result.

[Every CRM error Introw logs](./every-crm-error-introw-logs) is the full map, including what each verdict means and where a refused write is reported.

<Note>
  Two Salesforce facts shape what you will read there. A field Salesforce refuses field-level security on, such as a formula or a derived field, is reported as **Managed by your CRM** rather than as a missing permission, so a full admin connection stays quiet. And Salesforce will not report records the connecting user may not see, so **Partly visible** can tell you the view is partial but never how much is missing.
</Note>

## In short

<Steps>
  <Step title="Create a dedicated integration user">
    A named Salesforce user Introw connects as.
  </Step>

  <Step title="Grant a scoped permission set">
    Read on Account, Contact, and User baseline fields; read on each object (and its partner link) you embed or report on; write only where forms create records or partners edit fields.
  </Step>

  <Step title="Authorize Introw as that user">
    Introw then operates strictly within that permission set - your one, auditable boundary.
  </Step>

  <Step title="Confirm which user it actually connected as">
    On [Integrations](https://app.introw.io/settings/integrations?category=crm), open Salesforce: the detail header names the Salesforce user the connection authenticates as, with their username and the instance in the tooltip. If it names an admin rather than your integration user, select **Reconnect** from the three-dots menu and authorize again while logged in as the integration user.
  </Step>
</Steps>

## Related

<CardGroup cols={2}>
  <Card title="Connect Salesforce" icon="book-open" href="./connect-salesforce">
    The end-to-end connection and setup flow.
  </Card>

  <Card title="Attribution in Salesforce" icon="diagram-project" href="./attribution-in-salesforce">
    How objects link to partners - the access Introw needs to read attribution.
  </Card>

  <Card title="Show & rename CRM fields" icon="table-columns" href="./show-and-rename-crm-fields">
    Choose which fields to pull in and expose - each needs read (and edit, if editable).
  </Card>

  <Card title="Embed Introw in Salesforce" icon="book-open" href="./embed-introw-in-salesforce">
    Place the Introw component on record pages (the Lightning scope).
  </Card>

  <Card title="Every CRM error Introw logs" icon="triangle-exclamation" href="./every-crm-error-introw-logs">
    Where a permission gap is reported, and what each verdict means.
  </Card>
</CardGroup>
