Complete Vault · v66.0 Spring '26

Salesforce Integration Handbook

Everything you need to master Salesforce integration in one place: fundamentals, patterns, authentication, inbound & outbound APIs, events, bulk & async, modern APIs, security, tooling, hands-on projects, and what's new in 2026.

13 modules114 topic files12 hands-on projectsevery diagram renderedofficial sources
Prepared for Shakhawat · Sources verified June 18, 2026 · Use the sidebar or the cards below to jump anywhere. Click « in the sidebar for focus mode. To save as PDF: Print → Save as PDF.

Explore the Vault 13 modules · read in order or jump to any topic

MODULE 01
Fundamentals
The mental model: core vocabulary, REST vs SOAP, sync vs async, and the three layers every integration shares.
10 topics→
MODULE 02
Integration Patterns
The six official patterns — request & reply, fire & forget, batch sync, remote call-in, data virtualization, and UI updates.
6 topics→
MODULE 03
Authentication
Every OAuth 2.0 flow, connected apps vs external client apps, named credentials, and SSO with SAML & OpenID Connect.
17 topics→
MODULE 04
Inbound APIs
Calling into Salesforce: standard REST & SOAP, Apex REST/SOAP, Composite, Connect, and the User Interface API.
7 topics→
MODULE 05
Outbound Callouts
Calling out from Apex: HTTP callouts, named credentials, External Services, SOAP callouts, and the continuation pattern.
7 topics→
MODULE 06
Event-Driven
Platform Events, Change Data Capture, and the modern Pub/Sub API for real-time, decoupled streaming integrations.
6 topics→
MODULE 07
Bulk & Async
Move data at scale: Bulk API 2.0 and 1.0 plus Batch, Queueable, Future, and Scheduled Apex — with limits and monitoring.
7 topics→
MODULE 08
Modern APIs
GraphQL, sObject Collections, Data Cloud APIs, and a definitive map of the complete modern API landscape.
4 topics→
MODULE 09
Security & Limits
Connection security, authentication & access controls, data-level security, mTLS & Shield, and governor & API limits.
5 topics→
MODULE 10
Tools & Middleware
The daily toolkit: Postman, Workbench, Salesforce CLI, Data Loader, testing helpers, and middleware / iPaaS.
6 topics→
MODULE 11
Hands-On Projects
Twelve end-to-end builds, from a first REST callout to a live Platform Event LWC and a Pub/Sub CDC listener.
12 projects→
MODULE 12
Cheatsheets
Printable one-pagers: API picker, OAuth flows, callout & async limits, error codes, and HTTP status codes.
11 sheets→
MODULE 13
What's New 2026
Retirements & deprecations, Agentforce & MCP, MuleSoft in 2026, and new platform & API capabilities.
4 topics→

Module 01 - Fundamentals

Goal: Build the mental model. Just vocabulary and pictures, so every later module makes sense. API version: v66.0 (Spring '26). Outcome: you can explain integration to a non-technical person in one minute and define every core term without hesitating.

This module is the foundation for the whole vault. Read the files in order (01 → 10), or jump to a concept via the map below. Everything later (Authentication, APIs, Events, Bulk) assumes the words and ideas defined here.


How to use this module

  1. Start with 01-what-and-why-of-integration.md for the big picture and the three questions every integration answers.
  2. Lock in the vocabulary with 02-core-vocabulary.md, then work through the "vs" files (they are short and high-yield).
  3. Finish with 09 (the capstone model) and 10 (why Salesforce is special).
  4. Use the glossary table below as your quick-reference cheat sheet.

Map of this module

# File What it covers
01 what-and-why-of-integration What integration is, why integrate Salesforce, the 3 questions
02 core-vocabulary API, endpoint, payload, request/response, HTTP methods, status codes
03 rest-vs-soap The two API styles + the SOAP login() retirement
04 json-vs-xml The two data formats, side by side
05 synchronous-vs-asynchronous Wait-for-it vs fire-and-continue
06 inbound-vs-outbound Who calls whom, relative to Salesforce
07 push-pull-and-webhooks How data moves, and what a webhook is
08 middleware-and-esb The glue layer: ESB vs iPaaS / MuleSoft
09 three-layers-transport-format-auth The capstone model behind every integration
10 salesforce-as-a-platform Multitenant, API-first, governor limits

The one model that ties it all together

Every integration is just one choice from each of three layers. Pin this picture and the rest is detail.

flowchart TD subgraph L1[Layer 1 - TRANSPORT: how bytes travel] T1[HTTP / HTTPS] T2[gRPC] T3[CometD / long-poll] end subgraph L2[Layer 2 - FORMAT: how data is shaped] F1[JSON] F2[XML] F3[Protobuf] end subgraph L3[Layer 3 - AUTH: how identity is proven] A1[OAuth 2.0] A2[API key] A3[mTLS] end L1 --> L2 --> L3 style L1 fill:#00A1E0,color:#fff style L2 fill:#ff9900,color:#fff style L3 fill:#9C27B0,color:#fff

Example: a Salesforce REST API call = HTTPS + JSON + OAuth 2.0 Bearer token. Full detail in 09-three-layers-transport-format-auth.md.


Vocabulary quick-reference (glossary)

Term One-line meaning
API A contract that lets one system request data or actions from another.
Endpoint The specific URL an API request is sent to.
Instance / Base URL Your org's host, e.g. https://MyDomain.my.salesforce.com.
Request / Response The message you send, and the answer you get back.
Payload The body of a request or response (the actual data).
Header Metadata on a request, e.g. Authorization, Content-Type.
HTTP method The verb: GET (read), POST (create), PATCH/PUT (update), DELETE (remove).
Status code Result of a call: 2xx success, 4xx your fault, 5xx server fault.
REST Lightweight, resource-based API style, usually JSON. The modern default.
SOAP Strict XML-envelope API style with a WSDL contract. Mature, heavier.
JSON Compact key/value data format, native to the web.
XML Verbose tag-based data format, used by SOAP and Metadata.
Synchronous The caller waits for the response before continuing.
Asynchronous The caller fires the request and continues; result comes later.
Inbound An external system calls into Salesforce.
Outbound Salesforce calls out to an external system.
Pull The consumer asks for data when it wants it (polling).
Push The producer sends data the moment something changes.
Webhook An HTTP callback fired to a registered URL (a push).
Middleware Software between systems that connects, translates, and routes.
ESB / iPaaS Old on-prem hub (ESB) vs modern cloud integration platform (iPaaS, e.g. MuleSoft).
Multitenant Many customers share infrastructure; metadata makes each org unique.
Governor limit A cap Salesforce enforces so no tenant hogs shared resources.
API allocation Your org's quota of API calls per rolling 24 hours.

What changed worth knowing (2025-2026)

  • SOAP API login() is retiring in Summer '27 (not available in API v65.0+, disabled by default in new orgs). Migrate API authentication to OAuth via External Client Apps. Detail in 03-rest-vs-soap.md and Module 03.
  • REST + JSON is the default for new builds; SOAP + XML is legacy-but-supported.
  • For real-time event push, Pub/Sub API is the modern choice over older Streaming API.

Where this leads

flowchart LR M1[01 Fundamentals<br/>you are here] --> M2[02 Integration Patterns] M2 --> M3[03 Authentication] M3 --> M4[04+ APIs, Events, Bulk] style M1 fill:#4CAF50,color:#fff style M3 fill:#ff9900,color:#fff

After this module you have the vocabulary. Module 02 (Integration Patterns) teaches when to use each style, and Module 03 (Authentication) is already built out in full.


Sources (Verified June 2026)

Each topic file has its own Sources section with the specific official docs.

01 - What Integration Is (and Why Salesforce Needs It)

One-liner: Integration is just two systems talking to each other so data and actions flow between them instead of being re-typed by hand. Why it matters: Salesforce is almost never the only system a business runs. Its value multiplies when it shares data with the website, the ERP, the payment gateway, the data warehouse, and the phone system. Goal of this file: Be able to explain integration to a non-technical person in one minute.

This is Module 01. No deep code yet, just the mental model and vocabulary. Each later file builds on the picture drawn here.


1. The idea in plain English

Picture two people who speak different languages trying to do business. They need three things: a way to reach each other (a phone line), a shared language (so the words mean the same thing), and proof of who they are (so neither gets scammed). Software systems are exactly the same. Salesforce and an external system each store their own data in their own shapes. Integration is the agreed-upon phone call between them.

Without integration, a human becomes the integration: someone exports a CSV from the website, opens Salesforce, and re-types 200 leads. That is slow, error-prone, and always out of date. An integration replaces that human with an automatic, reliable connection.

flowchart LR SF[Salesforce] <-->|API call| EXT[External System<br/>ERP, website, payments] SF -.->|publishes an event| BUS{{Event Bus}} BUS -.->|notifies| SUBS[Other apps] EXT -->|inbound webhook| SF style SF fill:#00A1E0,color:#fff style BUS fill:#ff9900,color:#fff style EXT fill:#9C27B0,color:#fff

2. Why integrate Salesforce? (the business drivers)

Every integration exists to serve one of a few goals. In an interview, name the driver, not just the tech.

Driver What it means Real example
Single source of truth One trusted place for a piece of data, synced everywhere. Customer address lives in SAP, mirrored into Salesforce so reps see it.
Automation Remove manual re-keying between systems. A web form creates a Lead in Salesforce automatically.
Real-time experience Show fresh data or react instantly. A rep sees live order status from the ERP inside the Account page.
Reach / capability Use a service Salesforce does not provide. Call a credit-scoring API or a payment gateway.
Scale / offload Move heavy work to the right system. Push millions of records to a data warehouse nightly for analytics.

3. The shape of every integration (3 questions)

Before choosing any API or auth method, answer these three questions. The rest of this vault is just detailed answers to them.

flowchart TD Q1{1. WHO is talking?} --> Q2{2. HOW do they<br/>prove identity?} Q2 --> Q3{3. WHAT style of<br/>conversation?} Q1 -.- A1[Salesforce out, external in,<br/>or both - see file 06] Q2 -.- A2[Authentication -<br/>see Module 03] Q3 -.- A3[Pattern + API -<br/>sync/async, REST/SOAP] style Q1 fill:#00A1E0,color:#fff style Q2 fill:#ff9900,color:#fff style Q3 fill:#9C27B0,color:#fff
  1. Who is talking? Salesforce calling out, an external system calling in, or both. (See 06-inbound-vs-outbound.md.)
  2. How do they prove identity? The authentication layer. (Whole of Module 03.)
  3. What style of conversation? Real-time or batch, request/reply or fire-and-forget, REST or SOAP. (Files 03, 05, 07.)

4. How it shows up in Salesforce

Concrete examples you can name in an interview:

  • Website → Salesforce: a "Contact Us" form posts to the Salesforce REST API and creates a Lead. (Inbound, request/reply.)
  • Salesforce → Payment gateway: Apex makes an HTTP callout through a Named Credential to charge a card. (Outbound, request/reply.)
  • Salesforce → ERP: when an Opportunity closes, Salesforce publishes a Platform Event that the ERP subscribes to. (Outbound, fire-and-forget.)
  • Nightly sync: an ETL tool loads 2 million records via Bulk API 2.0. (Batch, async.)
  • Live data: an Account page shows SAP invoices through Salesforce Connect without copying them. (Data virtualization.)

Platform note: Salesforce is API-first — almost everything you can do in the UI, you can do through an API. That is why integration is a first-class skill, not an afterthought.


5. Common confusions and interview traps

Confusion The clarification
"Integration = API." An API is one tool for integration. Integration is the broader goal; it can also use events, files, or middleware.
"It's all real-time." Most enterprise integration is batch/async. Real-time is a deliberate choice with trade-offs.
"Salesforce just stores data." Salesforce is a platform: it exposes APIs, runs code (Apex), and publishes events. See 10-salesforce-as-a-platform.md.
"More integration is better." Each connection adds coupling and failure points. Integrate with a clear driver, not by default.

6. Interview Q&A

Q: Explain integration to a non-technical stakeholder. A: It is an automatic, reliable connection that lets two systems share data and trigger actions, so people don't have to copy information between them by hand.

Q: Why would a company integrate Salesforce instead of just using it standalone? A: To keep one source of truth across systems, automate manual work, give users real-time data, reach capabilities Salesforce lacks (payments, scoring), and offload heavy processing. The driver should be a business need, not the technology.

Q: What are the first questions you ask when scoping an integration? A: Who initiates (inbound vs outbound), how identity is proven (auth), and the conversation style (real-time vs batch, request/reply vs event). Those three decisions drive the API and pattern choice.

Q: Is integration always real-time? A: No. Much of it is scheduled batch or asynchronous events. Real-time adds cost and tighter coupling, so you choose it only when the use case truly needs a fresh response.

Talking point to explain it to anyone: "It's the difference between emailing a coworker a spreadsheet to retype, versus the two apps just texting each other automatically."


7. Key terms

API, endpoint, payload, request/response, inbound/outbound, synchronous/asynchronous, webhook, middleware — all defined in 02-core-vocabulary.md and the README glossary.


Sources (Verified June 2026)


Next: 02-core-vocabulary.md — the words you must know before anything else makes sense.

02 - Core Vocabulary (The Words You Must Know)

One-liner: Eight or nine words — API, endpoint, request, response, payload, methods, headers, status codes — unlock almost every integration conversation. Why it matters: Every later topic (REST, SOAP, auth, events) reuses these terms. Get them wrong in an interview and the rest sounds shaky. Goal of this file: Read one real Salesforce REST call and name every part of it.

New here? Start with 01-what-and-why-of-integration.md for the big picture.


1. The idea in plain English

Think of ordering at a restaurant. You don't walk into the kitchen and cook. You talk to a waiter through a fixed routine: you give an order (a request), the kitchen does the work, and the waiter brings back a plate (a response). The menu tells you what you're allowed to ask for. You never touch the stove.

An API is that waiter. It is the agreed set of things one system will do for another, and the exact way to ask. You send a request to a specific address, the system does the work, and it sends a response back. Everything below is just naming the parts of that exchange.

flowchart LR REQ["Request<br/>method + URL + headers + payload"] --> NET{HTTP} NET --> SF[Salesforce] SF --> RESP["Response<br/>status code + headers + body"] RESP --> REQ style REQ fill:#00A1E0,color:#fff style SF fill:#00A1E0,color:#fff style RESP fill:#4CAF50,color:#fff style NET fill:#607D8B,color:#fff

2. The vocabulary, one term at a time

Term Plain meaning Restaurant analogy
API (Application Programming Interface) The contract: what you can ask a system to do, and how. The waiter + the menu.
Endpoint The specific URL you send a request to. One API has many endpoints. The exact table/counter you order from.
Base URL / Instance URL The root address of the server. In Salesforce it is your My Domain instance host. Endpoints hang off it. The restaurant's street address.
Request The message you send asking for something. Placing your order.
Response The message that comes back. The plate the waiter returns.
Payload (body) The actual data carried in a request or response. Often JSON. The food on the plate (or the order written on the ticket).
HTTP method (verb) The kind of action you want: read, create, update, delete. "I'd like to order / change / cancel."
Headers Metadata about the message: who you are, what format you're sending. Notes to the kitchen: allergies, who's paying.
Status code A 3-digit number saying how the request went. "Order's up" vs "We're out of that" vs "Kitchen's on fire."

Endpoint vs Base URL: the base URL is https://MyDomainName.my.salesforce.com. The endpoint is the full path you hit, e.g. https://MyDomainName.my.salesforce.com/services/data/v66.0/sobjects/Account. Salesforce returns your base URL as instance_url when you authenticate.


3. HTTP methods (what each verb does in REST)

REST maps the four basic data operations (often called CRUD — Create, Read, Update, Delete) onto HTTP verbs. Memorize this table.

Method Means CRUD Salesforce REST example
GET Read. Never changes data (it is safe and idempotent). Read GET /sobjects/Account/001... retrieves one Account.
POST Create a new resource. Not idempotent — call it twice, get two records. Create POST /sobjects/Account creates an Account.
PATCH Partial update — change only the fields you send. Update PATCH /sobjects/Account/001... updates a field.
PUT Full replace / upsert of a resource. Salesforce uses it for upsert by external ID. Update/Upsert PUT /sobjects/Account/ExtId__c/12345.
DELETE Remove the resource. Delete DELETE /sobjects/Account/001... deletes the Account.

Interview note: Salesforce updates use PATCH, not PUT. PUT in the Salesforce REST API is reserved for upsert by external ID (create-or-update). Mixing these up is a common slip. Idempotent means repeating the same call leaves the system in the same state — GET, PATCH, PUT, and DELETE are idempotent; POST is not.


4. Headers (the two that matter most)

Headers are Name: Value lines that travel with the request. For integrations, two are non-negotiable:

Header Purpose Example value
Authorization Proves who you are. Carries the access token. Authorization: Bearer 00D5g...!AQEA...
Content-Type Tells the server the format of your payload. Content-Type: application/json
Accept Tells the server the format you want back. Accept: application/json

Bearer token: "Bearer" means whoever holds this token gets access, like cash. That is why tokens live in a header over HTTPS and are guarded like passwords. The token itself comes from Module 03 (authentication).


5. HTTP status codes (the three families)

The first digit tells you the family. Learn the family first, then the common members.

Family Meaning Common members you must know
2xx — Success The request worked. 200 OK (read/update succeeded), 201 Created (new record made), 204 No Content (success, nothing to return — e.g. after DELETE).
4xx — Client error You sent something wrong. Fix the request; don't retry blindly. 400 Bad Request (malformed body/field), 401 Unauthorized (missing/expired token), 403 Forbidden (no permission, or API limit exceeded), 404 Not Found (bad endpoint or deleted record), 415 Unsupported Media Type (wrong/missing Content-Type).
5xx — Server error The server failed. Often safe to retry with backoff. 500 Internal Server Error, 503 Service Unavailable (overloaded / maintenance).

The mental rule: 2xx = "great." 4xx = "your fault." 5xx = "their fault." In Salesforce, an expired session shows up as 401 INVALID_SESSION_ID, and hitting your daily API limit shows up as 403 REQUEST_LIMIT_EXCEEDED.


6. A real Salesforce request and response

Putting every term together. Here we create an Account via the REST API (API version v66.0, Spring '26).

The request (POST = create):

POST /services/data/v66.0/sobjects/Account HTTP/1.1
Host: MyDomainName.my.salesforce.com
Authorization: Bearer 00D5g000004...!AQEAQ...
Content-Type: application/json

{
  "Name": "Acme Corp",
  "Industry": "Technology",
  "NumberOfEmployees": 250
}
  • Method = POST (create). Endpoint = /services/data/v66.0/sobjects/Account.
  • Headers = Authorization (the token) and Content-Type (the payload is JSON).
  • Payload = the JSON object with the field values.

The response — status 201 Created:

{
  "id": "001gK000004xZpQQAU",
  "success": true,
  "errors": []
}
  • Status code 201 = a new record was created.
  • Payload = the new record's id, a success flag, and an empty errors array.
sequenceDiagram autonumber participant App as Client App participant SF as Salesforce REST API Note over App,SF: 1. Build and send the request App->>SF: POST /services/data/v66.0/sobjects/Account Note right of App: Headers Authorization + Content-Type<br/>Payload {Name Acme Corp} Note over App,SF: 2. Salesforce creates the record SF-->>App: 201 Created {id, success true, errors []} Note over App,SF: 3. Read it back to confirm App->>SF: GET /services/data/v66.0/sobjects/Account/001... SF-->>App: 200 OK + full record JSON

7. How it shows up in Salesforce

  • The base URL is your My Domain instance (e.g. acme.my.salesforce.com), returned as instance_url after login.
  • Almost every data endpoint sits under /services/data/vXX.X/. The vXX.X is the API version — v66.0 is Spring '26.
  • Authorization: Bearer <token> is on nearly every call; the token is a temporary Session ID.
  • A 201 means a record was created; the response gives you its 15- or 18-character record Id.
  • A 401 mid-session almost always means the token expired — refresh it. A 403 often means a permission problem or an API limit.

8. Common confusions and interview traps

Confusion The clarification
"Endpoint and base URL are the same." Base URL is the root host; the endpoint is the full path you call.
"Use PUT to update a record." In Salesforce REST, PATCH updates; PUT is upsert by external ID.
"401 and 403 are the same." 401 = not authenticated (bad/expired token). 403 = authenticated but not allowed (permission or limit).
"POST is idempotent." No. Two identical POSTs create two records. GET/PUT/PATCH/DELETE are idempotent.
"Content-Type and Accept are interchangeable." Content-Type describes what you send; Accept describes what you want back.
"A 200 always means data changed." 200 just means success. A GET returns 200 and changes nothing.

9. Interview Q&A

Q: What is an API, in one sentence? A: A contract that defines what one system can ask another to do, and exactly how to ask — like a waiter taking a structured order to the kitchen and bringing back the plate.

Q: Difference between an endpoint and a base URL? A: The base (instance) URL is the server's root address, like https://acme.my.salesforce.com. An endpoint is the full path you actually call, like .../services/data/v66.0/sobjects/Account. One base URL exposes many endpoints.

Q: Which HTTP method updates a record in Salesforce REST, and what's the trap? A: PATCH for a partial field update. The trap is assuming PUT — in the Salesforce REST API, PUT is used for upsert by external ID, not a normal update.

Q: A call returns 401. What does it mean and what do you do? A: The request isn't authenticated — usually an expired or missing access token (INVALID_SESSION_ID). You get a fresh token (refresh flow) and retry. A 403, by contrast, means you're authenticated but lack permission or hit an API limit.

Q: What's in an HTTP request versus a response? A: A request has a method, an endpoint URL, headers (auth, content type), and an optional payload/body. A response has a status code, headers, and a body. The status code's first digit tells you the outcome family: 2xx success, 4xx client error, 5xx server error.

Q: What does Authorization: Bearer mean? A: It carries an access token where possession equals access. The server trusts whoever presents a valid token, so it must travel over HTTPS and be protected like a password.

Talking point to explain it to anyone: "It's a restaurant order. You ask the waiter (the API) for something specific (the endpoint), hand over your details and your order (headers and payload), and get back a plate with a note saying how it went (the response and its status code)."


10. Key terms

API, endpoint, base/instance URL, request/response, payload, HTTP methods, headers, status codes — cross-referenced in the README glossary. Next we use them to compare the two API styles.


Sources (Verified June 2026)


Next: 03-rest-vs-soap.md — the two API styles, and why new Salesforce builds favor REST.

03 - REST vs SOAP (The Two API Styles)

One-liner: REST and SOAP are two ways to build a web API — REST is lightweight and resource-based, SOAP is a strict, contract-driven XML protocol. The core difference: REST uses plain HTTP verbs on resources and usually speaks JSON; SOAP wraps every message in a rigid XML envelope defined by a WSDL contract. Why it matters: Salesforce ships both. New builds default to REST, but SOAP is still everywhere in older integrations — and one big SOAP auth change is landing in 2026-2027.

Need the words first? See 02-core-vocabulary.md.


1. The idea in plain English

Two ways to send a parcel.

REST is like standard post. You write the address on the box (the URL), drop it in (a verb like GET or POST), and the system knows what to do. Light, flexible, everyone supports it.

SOAP is like registered mail with a notarized form. Every parcel must go inside a specific envelope, with a strictly formatted form attached (the XML envelope), and a master rulebook (the WSDL) defines exactly what every field must look like. More ceremony, but the contract is ironclad and machine-readable — tools can auto-generate client code from the WSDL.

Neither is "better" everywhere. SOAP is a protocol with built-in rules; REST is an architectural style that leans on HTTP itself.


2. The core difference, side by side

Dimension REST SOAP
What it is Architectural style over HTTP. Strict messaging protocol (XML).
Data format Usually JSON (can do XML). XML only, wrapped in a SOAP envelope.
Contract Optional/loose (OpenAPI is nice-to-have). Mandatory WSDL — a machine-readable contract.
Transport HTTP/HTTPS, uses verbs (GET/POST/PATCH/DELETE). Usually HTTP, but transport-agnostic; almost always POST.
Verbosity Compact. Less overhead per call. Verbose — envelope + namespaces add weight.
Error handling HTTP status codes (200/201/4xx/5xx) + JSON error body. A SOAP Fault element inside a 200-wrapped envelope.
Tooling / codegen Hand-rolled or OpenAPI generators. Strong: import the WSDL, auto-generate a typed client.
State / standards Stateless, minimal. Rich WS-* standards (WS-Security, etc.).
Best fit Web/mobile, microservices, modern integrations. Legacy enterprise, formal contracts, strict typing.

One-line interview answer: "REST is lightweight, resource-and-verb based, and usually JSON. SOAP is a heavier XML protocol with a strict WSDL contract and built-in standards. New work goes REST; SOAP lives on in mature enterprise systems."


3. The same call in both styles

REST — create an Account (compact, JSON, verb in the method):

POST /services/data/v66.0/sobjects/Account
Content-Type: application/json

{ "Name": "Acme Corp" }

SOAP — create an Account (envelope, namespaces, everything is XML):

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:urn="urn:partner.soap.sforce.com">
  <soapenv:Header>
    <urn:SessionHeader><urn:sessionId>00D...!AQ...</urn:sessionId></urn:SessionHeader>
  </soapenv:Header>
  <soapenv:Body>
    <urn:create>
      <urn:sObjects xsi:type="Account">
        <Name>Acme Corp</Name>
      </urn:sObjects>
    </urn:create>
  </soapenv:Body>
</soapenv:Envelope>

Same outcome, very different weight. Note SOAP carries the session in a SessionHeader inside the envelope, while REST carries it in the Authorization HTTP header.

flowchart TD subgraph REST["REST style"] R1["Verb + URL<br/>POST /sobjects/Account"] --> R2["JSON body<br/>{Name: Acme}"] R2 --> R3["HTTP status<br/>201 Created"] end subgraph SOAP["SOAP style"] S1["WSDL contract<br/>defines every operation"] --> S2["XML envelope<br/>Header + Body"] S2 --> S3["SOAP Fault or result<br/>inside a 200 response"] end style R1 fill:#4CAF50,color:#fff style R2 fill:#4CAF50,color:#fff style R3 fill:#4CAF50,color:#fff style S1 fill:#607D8B,color:#fff style S2 fill:#607D8B,color:#fff style S3 fill:#607D8B,color:#fff

4. How it shows up in Salesforce

Salesforce exposes both, and you should be able to name them:

API Style Format Typical use
REST API REST JSON (XML optional) The modern default for most app and mobile integration.
SOAP API SOAP XML + WSDL Mature, fully supported; common in older middleware and tools that import a WSDL.
Bulk API 2.0 REST CSV/JSON Large data loads (millions of records).
Metadata API SOAP XML + WSDL Deploy/retrieve metadata (config), used by tooling.

Salesforce publishes two WSDLs for the SOAP API: the Enterprise WSDL (strongly typed to one org's schema) and the Partner WSDL (loosely typed, works across orgs). New builds favor REST for its simplicity and lighter footprint.

Platform note: this is which style to call Salesforce with. It is separate from authentication (Module 03) and from data format (next file). A SOAP API call still needs a session; a REST call still needs a bearer token.


5. CURRENT and important — SOAP API login() is being retired

This is a live, exam-worthy change. Say it precisely:

  • The SOAP API login() call (the old way of trading username + password for a session) is being retired in Summer '27 for SOAP API versions 31.0 through 64.0. After that release, those login() calls stop working and every dependent integration fails to authenticate.
  • login() is not available in API version 65.0 and later at all.
  • From the Summer '26 release, in newly created orgs the SOAP login() call is disabled by default; an admin must enable it, and once enabled, users need the new "Use Any API Auth" user permission to authenticate this way.
  • Salesforce's guidance: migrate to OAuth — Web Server flow, JWT Bearer flow, or Client Credentials flow — configured through External Client Apps, well before the cutoff.
What Detail
What's retiring SOAP API login() in versions 31.0-64.0
When Summer '27 (the SOAP API itself is not being removed — only the login() auth call)
Already gone login() absent in v65.0+
New control (Summer '26) Disabled by default in new orgs; Use Any API Auth permission required
Migrate to OAuth via External Client Apps: Web Server, JWT Bearer, or Client Credentials

Interview gold: "SOAP isn't dying — the SOAP login() authentication call is. Salesforce is pushing everyone onto OAuth and External Client Apps. If I owned a legacy SOAP integration that calls login(), I'd plan a migration to JWT Bearer or Client Credentials before Summer '27." See Module 03 for those flows.


6. When to use which

Choose REST when... Choose SOAP when...
Building web/mobile/microservice integrations. Integrating with older middleware that imports a WSDL.
You want compact JSON and minimal overhead. You need a strict, machine-readable contract and strong typing.
You're starting something new in Salesforce. You must use a Salesforce API that is SOAP-only (e.g. parts of Metadata API).
You want easy debugging (curl, Postman). The existing system already speaks SOAP and rewriting isn't worth it.

Common confusions and traps

Confusion The clarification
"SOAP is dead in Salesforce." The SOAP API is fully supported. Only the SOAP login() auth call is being retired (Summer '27).
"REST always uses JSON, SOAP always uses XML." SOAP is XML-only; REST usually JSON but can return XML. The format is a separate choice — see 04-json-vs-xml.md.
"SOAP errors come back as HTTP 500." A SOAP Fault is typically returned inside a 200 envelope; you parse the body, not just the status.
"REST has no contract." It can (OpenAPI), but it's optional. SOAP's WSDL is mandatory.

7. Interview Q&A

Q: What's the core difference between REST and SOAP? A: REST is a lightweight architectural style that uses HTTP verbs on resources and usually returns JSON. SOAP is a strict protocol where every message is an XML envelope defined by a mandatory WSDL contract. REST is lighter and dominant for new work; SOAP offers a rigid, strongly typed contract favored in legacy enterprise systems.

Q: Which does Salesforce recommend for new integrations, and why? A: REST. It's simpler, less verbose, JSON-friendly, easy to test and debug, and fits web/mobile and microservice patterns. SOAP remains supported for existing systems and WSDL-based tooling.

Q: How does error handling differ? A: REST uses HTTP status codes (201, 400, 401, 500) plus a JSON error body. SOAP returns a SOAP Fault element, usually inside an HTTP 200 response, so you must parse the envelope rather than rely on the status code alone.

Q: What's the SOAP login() retirement about? A: The SOAP API login() call — username/password for a session — is being retired in Summer '27 for API versions 31.0-64.0 and is already absent in v65.0+. From Summer '26 it's disabled by default in new orgs and gated by the "Use Any API Auth" permission. Salesforce wants migration to OAuth (Web Server, JWT Bearer, or Client Credentials) via External Client Apps. The SOAP API itself stays; only that auth call goes away.

Q: When would you still pick SOAP today? A: When integrating with middleware or partners that consume a WSDL, when a strict typed contract is required, or when the only Salesforce API for the job is SOAP-based (parts of Metadata API). Otherwise REST.

Talking point to explain it to anyone: "REST is standard post — write the address, drop it in. SOAP is registered mail with a notarized form: more paperwork, but the contract is airtight."


8. Key terms

REST, SOAP, WSDL, envelope, SOAP Fault, JSON, XML, OAuth — see 02-core-vocabulary.md and the README glossary. Auth flows live in Module 03.


Sources (Verified June 2026)


Next: 04-json-vs-xml.md — the two data formats these APIs carry.

04 - JSON vs XML (The Two Data Formats)

One-liner: JSON and XML are two ways to write structured data as text so two systems can exchange it. The core difference: JSON is compact key/value data, native to web/JavaScript; XML is verbose tag-based markup with namespaces, attributes, and schemas. Why it matters: REST and most modern Salesforce APIs speak JSON; SOAP API and Metadata API speak XML. Knowing which goes where avoids a lot of confusion.

Coming from the API styles? See 03-rest-vs-soap.md. New words live in 02-core-vocabulary.md.


1. The idea in plain English

Both are just text that carries structured data — a way to write down "this record has these fields with these values" so another program can read it back exactly.

JSON (JavaScript Object Notation) is like a packing list: short labels and values, Name: Acme, with curly braces grouping things. Minimal punctuation, easy for a human to skim and for a browser to parse.

XML (eXtensible Markup Language) is like a legal form: every field is wrapped in an opening and closing tag, <Name>Acme</Name>, and you can attach extra notes (attributes), declare which rulebook applies (namespaces), and validate against a schema. More words on the page, but very explicit.

Neither is "the data" — they are two spellings of the same data. A record is a record; JSON and XML are just how you write it down on the wire.


2. The same record in both formats

A single Contact — first name, last name, email, and whether they're active — written both ways.

JSON:

{
  "FirstName": "Ada",
  "LastName": "Lovelace",
  "Email": "ada@example.com",
  "IsActive": true
}

XML:

<Contact>
  <FirstName>Ada</FirstName>
  <LastName>Lovelace</LastName>
  <Email>ada@example.com</Email>
  <IsActive>true</IsActive>
</Contact>

Same four fields. JSON is ~80 characters lighter here, and the structure is implied by braces and quotes rather than repeated tags. XML repeats every field name twice (open and close) but can carry extra metadata JSON cannot express as cleanly.

flowchart LR REC["One Contact record<br/>Ada Lovelace"] --> J["JSON<br/>key: value, braces<br/>compact"] REC --> X["XML<br/>open/close tags<br/>verbose, typed"] J --> WIRE{Sent over HTTP} X --> WIRE style REC fill:#00A1E0,color:#fff style J fill:#4CAF50,color:#fff style X fill:#607D8B,color:#fff style WIRE fill:#ff9900,color:#fff

3. The core difference, side by side

Dimension JSON XML
Shape Key/value pairs, arrays, nested objects. Nested elements wrapped in tags.
Verbosity Compact — no closing tags. Verbose — every element opens and closes.
Data types Native: string, number, boolean, null, array, object. Everything is text; types come from a schema (XSD).
Attributes No attributes — only keys and values. Supports attributes (<tag id="1">) and elements.
Namespaces None (keys are plain). Namespaces prevent name clashes across vocabularies.
Schema / validation JSON Schema (optional). XSD / DTD — mature, strict validation.
Comments Not allowed in standard JSON. Allowed (<!-- ... -->).
Readability Easy for web developers; maps to objects. Explicit but heavier to read.
Native home JavaScript / web / REST. Enterprise, config, SOAP, documents.

One-line interview answer: "JSON is compact key/value data that maps straight to objects and dominates REST and the web. XML is verbose tag-based markup with namespaces, attributes, and strong schema validation — the language of SOAP, Metadata API, and config files."


4. How it shows up in Salesforce

The mapping is clean and worth memorizing:

Salesforce API / artifact Format Why
REST API JSON (XML optional) Modern default; lightweight for apps and mobile.
Bulk API 2.0 JSON / CSV Large data loads.
Connect (Chatter) REST API JSON Web/feed data.
SOAP API XML Every message is a SOAP envelope (XML).
Metadata API XML Component definitions; package.xml and *-meta.xml files are XML.
Tooling API JSON or XML Developer tooling; commonly JSON over REST.

So in practice: when you call the REST API you send and receive JSON; when you deploy metadata or call the SOAP API, you're in XML. Every Salesforce metadata file on disk (CustomObject, Flow, ApexClass meta) is XML.

Platform note: a package.xml manifest for a Metadata API deploy is pure XML with namespaces. A record returned from GET /services/data/v66.0/sobjects/Contact/003... is JSON. Same platform, two formats, chosen by the API.


5. When to use which

Reach for JSON when... Reach for XML when...
Calling or building a REST API. Calling the SOAP API or Metadata API.
Talking to web or mobile front ends. A system or partner requires a strict schema (XSD).
You want compact payloads and fast parsing. You need namespaces or attributes to model the data.
Working in JavaScript/Apex with simple objects. Authoring Salesforce config/metadata files.

Common confusions and traps

Confusion The clarification
"JSON is better than XML." Neither is better universally. JSON is lighter; XML is richer (namespaces, attributes, validation). Pick per use case.
"REST can only return JSON." REST usually uses JSON but the Salesforce REST API can return XML if you set the Accept header.
"The format determines the API style." No. Format (JSON/XML) and style (REST/SOAP) are separate choices. SOAP is always XML; REST is usually JSON.
"XML attributes and elements are the same." Attributes (id="1") live on a tag; elements are nested nodes. JSON has neither — only keys.
"Metadata API uses JSON." Metadata API is XML (so are the *-meta.xml files in your repo).

6. Interview Q&A

Q: What's the core difference between JSON and XML? A: JSON is a compact key/value format with native data types that maps directly to objects — dominant in REST and the web. XML is verbose, tag-based markup that supports namespaces, attributes, and strict schema validation — the language of SOAP and config. They're two spellings of the same structured data.

Q: In Salesforce, which APIs use which format? A: REST API, Bulk API 2.0, and Connect API use JSON. SOAP API and Metadata API use XML. Tooling API is typically JSON. As a rule: REST means JSON, SOAP and metadata mean XML.

Q: Can the Salesforce REST API return XML? A: Yes. JSON is the default, but you can request XML via the Accept header. The point is that format and API style are independent choices — REST just defaults to the lighter format.

Q: Why is JSON usually preferred for new web integrations? A: It's compact, parses natively in JavaScript and most languages, maps cleanly to objects, and has less overhead than XML's open/close tags. That makes it faster and simpler for app and mobile work.

Q: When does XML still win? A: When you need namespaces to avoid name clashes, attributes to attach metadata, comments, or rigorous XSD validation — and whenever you're in SOAP, Metadata API, or Salesforce config files, which are XML by design.

Talking point to explain it to anyone: "JSON is a packing list — short labels and values. XML is a legal form — every field wrapped in a labeled box, with room for stamps and rules. Same information, different amount of paperwork."


7. Key terms

JSON, XML, key/value, tag/element, attribute, namespace, schema (XSD/JSON Schema), envelope — see 02-core-vocabulary.md and the README glossary. API styles are in 03-rest-vs-soap.md.


Sources (Verified June 2026)


Next: 05-synchronous-vs-asynchronous.md — request/reply now vs fire-and-forget later.

05 - Synchronous vs Asynchronous

One-liner: Synchronous = the caller waits for the answer before doing anything else. Asynchronous = the caller fires the request and moves on, getting the result later via a poll, callback, or event. Why it matters: This single choice decides your user experience, your throughput, and how you handle failure. Pick wrong and you either freeze a screen for 30 seconds or lose track of work that finished in the background. Core difference in one line: Sync blocks and gives you an immediate answer. Async doesn't block and hands you the answer later.

New here? Skim 01-what-and-why-of-integration.md first for the big picture.


1. The idea in plain English

Imagine ordering food two ways.

Synchronous is a phone call to a busy hotline. You dial, you stay on the line, you cannot do anything else, and you hang up only once you have your answer. Fast when the other side answers quickly. Painful when they put you on hold.

Asynchronous is texting the restaurant. You send "one large pizza," put your phone in your pocket, and go water the plants. A while later a text pings back: "Ready for pickup." You were never stuck waiting.

In software, synchronous means the calling code pauses on that line until a response arrives. Asynchronous means the call returns instantly with a "got it, I'll handle it," and the real result shows up later through a callback, a status you poll, or an event you subscribe to.

flowchart LR subgraph SYNC[Synchronous] A1[Caller sends request] --> A2[Caller WAITS<br/>blocked] A2 --> A3[Response arrives] --> A4[Caller continues] end subgraph ASYNC[Asynchronous] B1[Caller sends request] --> B2[Instant ack<br/>caller continues] B2 -.later.-> B3[Result via poll<br/>callback or event] end style A2 fill:#F44336,color:#fff style B2 fill:#4CAF50,color:#fff style B3 fill:#ff9900,color:#fff

2. The core difference + side-by-side comparison

The whole distinction is does the caller block and wait? Everything else flows from that.

Dimension Synchronous Asynchronous
Blocking Caller is blocked until the response returns. Caller is not blocked, continues immediately.
When you get the result Now, in the same call. Later, via poll, callback, or event.
Latency felt by user You feel the full round-trip time. You feel almost nothing up front.
Error handling Simple. Success or failure returns on the spot, in one place. Harder. You must track job status, retries, and out-of-band failures.
Throughput / volume Limited. One slow call ties up a connection and a timeout budget. High. Work is queued and processed in bulk in the background.
User experience Great for instant answers, bad if the work is slow. Great for big or slow jobs, needs a "we'll notify you" pattern.
Coupling Tighter. Both systems must be up at the same moment. Looser. The receiver can be busy or briefly down.
Best for Small, fast request/reply where the user needs the answer immediately. Large volumes, slow work, or fire-and-forget notifications.

3. A concrete example

Charging a credit card (synchronous). A user clicks "Pay." Your code calls the payment gateway and waits for "approved" or "declined" before it can show the next screen. The user must know the outcome right now, so blocking is the correct design. The cost: if the gateway is slow, the user stares at a spinner.

Loading 2 million records into a warehouse (asynchronous). An ETL tool submits a job, gets back a job id instantly, and walks away. Salesforce processes the records in the background. The tool checks back later, or gets notified, when the job completes. Nobody could sit and wait 40 minutes on a single open connection, so async is the only sane choice.


4. How it shows up in Salesforce

Salesforce draws this line very clearly. Know which side each tool sits on.

flowchart TD Q{Does the caller need<br/>the answer right now?} -->|Yes, small + fast| SYNC[SYNCHRONOUS] Q -->|No, big or slow<br/>or fire-and-forget| ASYNC[ASYNCHRONOUS] SYNC --> S1[REST API request/reply] SYNC --> S2[SOAP API request/reply] SYNC --> S3[Apex HTTP callout<br/>max 120s timeout] ASYNC --> A1[Bulk API 2.0] ASYNC --> A2[Platform Events / Pub-Sub API] ASYNC --> A3["@future methods"] ASYNC --> A4[Queueable Apex] ASYNC --> A5[Batch Apex] style SYNC fill:#00A1E0,color:#fff style ASYNC fill:#ff9900,color:#fff style Q fill:#607D8B,color:#fff

Synchronous in Salesforce:

  • REST API and SOAP API request/reply. You call, you wait, you get rows back in the same response. Best for a handful of records.
  • Apex HTTP callouts (outbound). A callout blocks the running transaction. Each callout can run up to a 120-second timeout (default 10s, set with setTimeout), and a single transaction allows up to 100 callouts. Synchronous callout response size is capped at 6 MB.

Asynchronous in Salesforce:

  • Bulk API 2.0. You submit a job, get a job id, and poll for completion. Built for thousands to millions of records, processed in the background. (REST/SOAP, by contrast, are tuned for real-time small batches.)
  • Platform Events and the Pub/Sub API. Publish an event and move on. Subscribers receive it later over a streaming channel. Pure fire-and-forget.
  • @future methods. Mark an Apex method @future and it runs later in its own background context. The classic way to make a callout from a trigger without blocking.
  • Queueable Apex. Like @future but with chaining and a job id you can monitor. The modern default for background work.
  • Batch Apex. Processes large data sets in chunks asynchronously, ideal for nightly jobs.

Async callouts get more room. Asynchronous Apex allows a larger callout response (12 MB vs 6 MB for synchronous). Another reason heavy work belongs in the background.

Why a trigger uses @future for callouts. You cannot make a synchronous callout in the same transaction that has uncommitted DML, and you do not want a user's save to block on a slow external system. So you defer the callout to an async method.


5. When to use which + common confusions

Confusion / trap The clarification
"Async is always better." No. If the user needs the answer to continue (a payment, a validation), sync is correct. Async needs extra machinery to track results.
"Synchronous means fast." Sync means immediate and blocking, not fast. A slow sync call is the worst case: you wait and you are stuck.
"Bulk API is just a faster REST API." Different model entirely. Bulk API 2.0 is asynchronous job-based. REST/SOAP are synchronous request/reply.
"Platform Events return a result." No. Publishing an event is fire-and-forget. There is no synchronous response payload.
"A callout can run as long as I want." A synchronous Apex callout is capped at 120 seconds. Long work must go async (Queueable, Batch, or a polling pattern).
"Async means no errors to handle." Async has more failure modes: the job can fail after you walked away. You must monitor status and design retries.

Rule of thumb: small + fast + user-needs-it-now → synchronous. Big + slow + fire-and-forget → asynchronous.


6. Interview Q&A

Q: Explain synchronous vs asynchronous. A: Synchronous means the caller blocks and waits for the response before continuing. Asynchronous means the caller sends the request, gets an immediate acknowledgement, keeps working, and receives the real result later through a poll, callback, or event.

Q: Give a Salesforce example of each. A: Synchronous: a REST API query or an Apex HTTP callout to charge a card, where the transaction waits for the response. Asynchronous: Bulk API 2.0 loading millions of records, or publishing a Platform Event that subscribers pick up later.

Q: Why can't a trigger just make a synchronous callout directly? A: A callout is not allowed in a transaction with pending uncommitted DML, and you do not want the user's save blocked on a slow external system. So you defer it with @future or Queueable Apex, which run asynchronously.

Q: A callout to a slow partner API keeps timing out. What do you change? A: A synchronous callout maxes out at 120 seconds. Move the work to an asynchronous pattern. Queueable Apex, Batch Apex, or a job-and-poll design, so the user is not blocked and you control retries.

Q: When would you choose synchronous even though async scales better? A: When the caller cannot continue without the answer. Payments, real-time validations, and "show me this record now" lookups all need a synchronous response.

Q: What is the trade-off you accept by going asynchronous? A: Looser coupling and higher throughput in exchange for more complex result handling. You no longer get a single success/fail in one place. You must track job status, handle out-of-band failures, and design retries and notifications.

Talking point to explain it to anyone: "Synchronous is a phone call. You wait on the line for the answer. Asynchronous is a text message. You send it and get on with your day until a reply pings back."


7. Key terms

Synchronous, asynchronous, blocking, callback, polling, callout, timeout, fire-and-forget, job id, throughput, latency — all defined in 02-core-vocabulary.md and the README glossary.


Sources (Verified June 2026)


Next: 06-inbound-vs-outbound.md — which direction is the call going, into Salesforce or out of it?

06 - Inbound vs Outbound

One-liner: Inbound = an external system calls into Salesforce. Outbound = Salesforce calls out to an external system. The direction is always measured relative to Salesforce. Why it matters: Direction decides who authenticates whom, which APIs and tools you reach for, and which side is the "client" and which is the "server." Get the direction right and the auth and tooling fall into place. Core difference in one line: Inbound, someone calls Salesforce. Outbound, Salesforce calls someone.

New here? Read 01-what-and-why-of-integration.md and 05-synchronous-vs-asynchronous.md first.


1. The idea in plain English

Stand inside Salesforce and watch the front door.

Inbound is someone knocking on your door. A delivery driver (an external app) shows up, proves who they are, and hands you a package or asks for one. Salesforce is the host. It must check the visitor's ID before letting them in.

Outbound is you leaving the house to visit someone else. Salesforce walks out, knocks on the partner's door, and proves its own identity to them. Now Salesforce is the visitor.

The only thing that flips is who initiates the call. The same business integration often has both directions: the website calls in to create a lead (inbound), and later Salesforce calls out to a billing system when a deal closes (outbound).

flowchart LR EXT[External System<br/>website, ERP, billing] -->|INBOUND<br/>calls into SF| SF[Salesforce] SF -->|OUTBOUND<br/>SF calls out| EXT2[External System<br/>payment, ERP, partner API] style SF fill:#00A1E0,color:#fff style EXT fill:#9C27B0,color:#fff style EXT2 fill:#ff9900,color:#fff

2. The core difference + side-by-side comparison

Dimension Inbound Outbound
Who initiates The external system starts the call. Salesforce starts the call.
Salesforce's role Server / resource server. It receives. Client. It sends.
Who must authenticate The external app proves its identity to Salesforce. Salesforce proves its identity to the external system.
Where credentials live In a Connected App / External Client App (Salesforce side) that the external app uses to get a token. In a Named Credential + External Credential (Salesforce side) that stores how to log in to the partner.
Typical tools REST API, SOAP API, Apex REST / Apex SOAP web services. Apex HTTP callouts, Named Credentials, External Services, Outbound Messages (legacy).
Direction of data Data usually flows into Salesforce (or is read out by the caller). Data usually flows out to the partner.
Auth module Salesforce is the resource server (see Module 03). Salesforce is the OAuth client using a Named Credential.

Memory hook: Follow the arrow. If the arrow points at Salesforce, it is inbound. If it points away, it is outbound.


3. A concrete example

Inbound: a web form creates a Lead. A "Contact Us" page on the company website POSTs to the Salesforce REST API. The website app first authenticates using a Connected App and an OAuth token, then calls POST /services/data/v66.0/sobjects/Lead. Salesforce is the server here. It checks the token, then creates the record. The external app had to prove it was allowed in.

Outbound: Salesforce charges a card. When an Opportunity closes, Apex makes an HTTP callout through a Named Credential to the payment gateway. Salesforce is now the client. The Named Credential holds the endpoint and the credentials Salesforce uses to authenticate to the gateway. Salesforce is the visitor proving who it is.

Same company, both directions, completely different auth setup on each side.


4. How it shows up in Salesforce

flowchart TD subgraph IN[INBOUND - external calls into Salesforce] E1[External App] -->|OAuth token from<br/>Connected App / ECA| R1[REST API] E1 --> R2[SOAP API] E1 --> R3[Apex REST / Apex SOAP<br/>web services] R1 --> SFIN[Salesforce<br/>= resource server] R2 --> SFIN R3 --> SFIN end subgraph OUT[OUTBOUND - Salesforce calls out] SFOUT[Salesforce<br/>= client] -->|via Named Credential| O1[Apex HTTP Callout] SFOUT --> O2[External Services] SFOUT --> O3[Outbound Messages<br/>legacy SOAP] O1 --> P1[Partner API] O2 --> P1 O3 --> P1 end style SFIN fill:#00A1E0,color:#fff style SFOUT fill:#00A1E0,color:#fff style E1 fill:#9C27B0,color:#fff style P1 fill:#ff9900,color:#fff

Inbound (Salesforce is the destination):

  • REST API and SOAP API. The standard front doors for external apps to create, read, update, and delete records.
  • Apex REST (@RestResource) and Apex SOAP web services (webservice keyword). Custom endpoints you build in Apex when the standard APIs are not enough, exposing your own URLs and logic.
  • Every inbound caller must authenticate first. The external app is registered as a Connected App or External Client App, and uses OAuth to get an access token. (See Module 03.)

Outbound (Salesforce is the source):

  • Apex HTTP callouts. Apex code that sends an HTTP request to an external endpoint.
  • Named Credentials (with External Credentials). The recommended way to store the endpoint URL and authentication so callout code never hardcodes secrets. Salesforce handles the token for you.
  • External Services. Point Salesforce at an external API's schema and it generates invocable actions you can call from Flow, no code.
  • Outbound Messages (legacy). A workflow/Flow action that sends a SOAP message to an endpoint when a record changes. A single message can carry up to 100 notifications. Mature but dated, covered more in 07-push-pull-and-webhooks.md.

The auth tie, stated plainly: - Inbound → Salesforce is the resource server. The external app holds the credentials and proves itself to Salesforce. Your job is to register the app and grant the right scopes. - Outbound → Salesforce is the client. Salesforce holds the credentials (in a Named Credential) and proves itself to the partner. Your job is to configure the Named/External Credential.


5. When to use which + common confusions

Confusion / trap The clarification
"Inbound vs outbound describes the data direction." No. It describes who initiates the call, relative to Salesforce. Data can flow either way within one call.
"An Outbound Message is inbound because it sends data out... wait." It is outbound: Salesforce initiates and pushes a SOAP message out. The name matches the direction for once.
"Salesforce always authenticates the same way." Direction flips the auth. Inbound, the external app authenticates to Salesforce. Outbound, Salesforce authenticates to the partner.
"Apex callouts are inbound because they are in Salesforce code." The code lives in Salesforce, but it calls out, so it is outbound. Salesforce is the client.
"Named Credentials are for inbound auth." No. Named Credentials are for outbound callouts, storing how Salesforce logs in to the partner. Inbound uses Connected Apps / ECAs.
"REST API is outbound." The Salesforce REST API is an inbound door. External apps call it. Salesforce calling another system's REST API is the outbound case, done via Apex callout.

Rule of thumb: ask "who picked up the phone and dialed?" If it was the external system, inbound. If it was Salesforce, outbound.


6. Interview Q&A

Q: Define inbound and outbound integration in Salesforce. A: Inbound means an external system initiates a call into Salesforce. Salesforce is the server. Outbound means Salesforce initiates a call out to an external system. Salesforce is the client. Direction is measured relative to Salesforce.

Q: For an inbound integration, who authenticates to whom, and how? A: The external app authenticates to Salesforce. It is registered as a Connected App or External Client App and uses OAuth to obtain an access token, which it sends as a bearer token on each API call. Salesforce acts as the resource server.

Q: For an outbound callout, where do the credentials live? A: In a Named Credential paired with an External Credential on the Salesforce side. They store the endpoint and how Salesforce authenticates to the partner, so Apex callout code never hardcodes URLs or secrets.

Q: Name the inbound tools and the outbound tools. A: Inbound: REST API, SOAP API, Apex REST, Apex SOAP web services. Outbound: Apex HTTP callouts, Named Credentials, External Services, and the legacy Outbound Messages.

Q: A partner needs to push order updates into Salesforce. Inbound or outbound, and what do you set up? A: Inbound. The partner calls the Salesforce REST API (or a custom Apex REST endpoint). You register their app as a Connected App or External Client App, grant the api scope, and they authenticate via OAuth.

Q: Salesforce needs to notify an ERP when an Opportunity closes. Inbound or outbound? A: Outbound. Salesforce initiates. Options include an Apex HTTP callout through a Named Credential, an External Services action, or a legacy Outbound Message. For event-style delivery you might publish a Platform Event the ERP subscribes to.

Talking point to explain it to anyone: "Inbound is someone knocking on Salesforce's door. Outbound is Salesforce leaving the house to knock on someone else's. Whoever knocks has to prove who they are."


7. Key terms

Inbound, outbound, client, server, resource server, Connected App, External Client App, Named Credential, External Credential, Apex REST, callout — all defined in 02-core-vocabulary.md and the README glossary. Auth roles are detailed in Module 03 fundamentals.


Sources (Verified June 2026)


Next: 07-push-pull-and-webhooks.md — does the consumer ask for data, or does the producer push it the moment it changes?

07 - Push, Pull, and Webhooks

One-liner: Pull = the consumer asks for data when it wants it. Push = the producer sends data the moment something changes. A webhook is a push: an HTTP callback the producer fires to a URL the consumer registered in advance. Why it matters: This is the difference between data that is always a little stale and burns resources, versus data that arrives the instant it changes. Most "why is my integration so slow / so chatty" problems trace back to choosing pull where push belonged. Core difference in one line: Pull, you go fetch it. Push, it comes to you.

New here? Read 01-what-and-why-of-integration.md, then 05 and 06.


1. The idea in plain English

Think about how you find out the news.

Pull is refreshing a news website. Nothing reaches you until you go and ask. To stay current you have to keep hitting refresh, over and over, even when nothing has changed. Most of those refreshes are wasted, and between refreshes your view is out of date.

Push is a breaking-news alert on your phone. You signed up once, then you do nothing. The moment something happens, the alert lands. No wasted checks, no lag.

A webhook is the breaking-news alert formalized for software. The consumer says, "here is my URL, call it when X happens." The producer keeps that URL and fires an HTTP request to it the instant X happens. The consumer waits passively and reacts.

Polling (pull) is wasteful and laggy: most polls return "nothing new," and real changes wait until the next poll. Push is efficient and fresh: a message is sent only when there is something to say, and it arrives immediately.

flowchart LR subgraph PULL[PULL - consumer asks] C1[Consumer] -->|are we there yet?| P1[Producer] P1 -->|nope| C1 C1 -->|now?| P1 P1 -->|here it is| C1 end subgraph PUSH[PUSH - producer sends] P2[Producer] -->|change happened!| C2[Consumer] end style C1 fill:#9C27B0,color:#fff style P1 fill:#607D8B,color:#fff style P2 fill:#4CAF50,color:#fff style C2 fill:#00A1E0,color:#fff

2. The core difference + side-by-side comparison

Dimension Pull (polling) Push (incl. webhooks)
Who initiates The consumer asks. The producer sends.
Data freshness Stale between polls. Lag = polling interval. Fresh. Arrives the moment it changes.
Efficiency Wasteful. Most polls return nothing new. Efficient. Traffic only when there is real news.
Load pattern Constant, whether or not data changed. Spiky. Matches the rate of actual change.
Complexity Simple to build. Just call on a timer. More moving parts. Endpoints, retries, ordering, security.
Failure handling Easy. Missed a poll? Just poll again. Harder. If the consumer is down, the push can be missed. Needs retry / replay.
Best for Infrequent needs, batch syncs, simple jobs. Real-time reactions, event-driven systems, instant updates.

The trade-off in one line: pull is simple but laggy and chatty. Push is fresh and lean but needs more plumbing (a live endpoint, retries, and security).


3. A concrete example

Pull: nightly inventory sync. A reporting tool calls the Salesforce REST API once a night to fetch the day's orders. Freshness does not matter. Yesterday's data is fine in the morning. A scheduled pull is simple and perfectly adequate.

Push: notify shipping the instant an order is paid. The moment an Opportunity flips to "Paid," the warehouse needs to know now, not at the next nightly poll. Salesforce publishes a Platform Event (or fires a callout to the warehouse's webhook URL). The warehouse reacts within seconds. Polling here would mean either an unacceptable delay or hammering the API every few seconds just in case.


4. How it shows up in Salesforce

Salesforce supports both, and importantly does not use the word "webhook" in its product names. It achieves the same outcome with events (consumers subscribe) and callouts (Salesforce fires an HTTP request to a registered URL).

flowchart TD Q{Do you need it<br/>the instant it changes?} -->|No, periodic is fine| PULL[PULL] Q -->|Yes, react immediately| PUSH[PUSH] PULL --> PL1[REST / SOAP query<br/>on demand] PULL --> PL2[Scheduled jobs<br/>Scheduled Apex, ETL] PUSH --> PS1[Platform Events] PUSH --> PS2[Change Data Capture] PUSH --> PS3[Pub-Sub API] PUSH --> PS4[Outbound Messages<br/>legacy SOAP] PUSH --> PS5[Webhook-style callout<br/>from Flow or Apex] style PULL fill:#607D8B,color:#fff style PUSH fill:#4CAF50,color:#fff style Q fill:#ff9900,color:#fff

Pull in Salesforce (the consumer asks):

  • REST / SOAP query. An external system runs a SOQL query or GET request whenever it wants the current data.
  • Scheduled jobs. Scheduled Apex or an external ETL tool polls on a timer (every night, every hour) to fetch what changed.

Push in Salesforce (the producer sends):

  • Platform Events. Salesforce publishes a custom event. Subscribers receive it over a streaming channel. The event-driven backbone.
  • Change Data Capture (CDC). Salesforce automatically publishes an event whenever records are created, updated, deleted, or undeleted, carrying the changed fields. No code to publish.
  • Pub/Sub API. The modern, unified, gRPC-based API to publish and subscribe to Platform Events, CDC, and real-time event-monitoring events. It uses binary Avro payloads over HTTP/2 and is the recommended subscriber interface.
  • Outbound Messages (legacy). A workflow/Flow action that pushes a SOAP message to a registered endpoint URL when a record changes, up to 100 notifications per message. This is the closest classic Salesforce feature to a webhook.
  • Webhook-style callouts from Flow or Apex. To call an external webhook, Salesforce fires an HTTP callout (from a record-triggered Flow or Apex) to the URL the partner gave you. Salesforce is the producer pushing out.

Webhook, the Salesforce way: a webhook is just "producer fires HTTP to a URL the consumer registered." Salesforce does the inbound side by being an endpoint (an Apex REST resource an external producer calls) and the outbound side by firing a callout or publishing an event. The mechanism has a different name, but the pattern is identical.

Why push beats polling for freshness. Polling every 5 minutes means data is up to 5 minutes stale and you make 288 calls a day even on quiet days. A push delivers in seconds and sends nothing when nothing changed. Push trades that efficiency for needing a reliable listener.


5. When to use which + common confusions

Confusion / trap The clarification
"Salesforce has webhooks." Not by that name. It uses Platform Events / CDC / Pub-Sub API (subscribe) and callouts / Outbound Messages (fire to a URL) to get the same result.
"Push is always better than pull." Push is fresher and leaner, but needs a live, secured, retry-capable listener. For a nightly batch, a simple pull is the right call.
"Polling is real-time if I poll fast enough." Fast polling fakes real-time at the cost of huge wasted load and still has lag. True push is event-driven and immediate.
"CDC is something I have to code to publish." No. CDC publishes change events automatically when records change. You only write the subscriber.
"Platform Events are synchronous request/reply." No. They are push / fire-and-forget. Publish and move on. The subscriber gets it later. See 05.
"Outbound Messages are modern." They work and are reliable, but they are legacy (SOAP, tied to workflow). New designs prefer Platform Events, CDC, and Pub/Sub API.
"Pub/Sub API is a database query API." No. It is the streaming publish/subscribe interface for events (Platform Events, CDC), gRPC-based, not for SOQL queries.

Rule of thumb: need it the instant it changes → push (events / callouts). Periodic or on-demand is fine → pull (query / scheduled job).


6. Interview Q&A

Q: Explain push vs pull. A: Pull means the consumer asks the producer for data when it wants it, often by polling on a timer. Push means the producer sends data to the consumer the moment something changes. Pull is simple but laggy and wasteful. Push is fresh and efficient but needs more infrastructure.

Q: What is a webhook? A: A webhook is a push delivered over HTTP. The consumer registers a URL with the producer in advance, and the producer fires an HTTP request to that URL whenever the relevant event happens. It is a callback rather than something the consumer polls for.

Q: Does Salesforce have webhooks? A: Not under that name. Salesforce achieves the same push pattern with Platform Events, Change Data Capture, and the Pub/Sub API for subscribers, and with HTTP callouts or legacy Outbound Messages when Salesforce fires to an external URL.

Q: When would you poll instead of using events? A: When freshness is not critical and simplicity matters, such as a nightly or hourly batch sync, or when the external system cannot host a reliable listener endpoint. A scheduled pull is easy to build and easy to recover.

Q: What is the downside of polling, and how does push fix it? A: Polling is laggy (data is stale between polls) and wasteful (most polls return nothing new, yet still consume API calls). Push fixes both by sending a message only when something changes, and delivering it within seconds.

Q: Compare Outbound Messages with Platform Events. A: Both push data out, but Outbound Messages are legacy. They send a SOAP message to one endpoint, tied to workflow or Flow. Platform Events are the modern event bus. Many subscribers, richer payloads, and consumed over the Pub/Sub API. New designs favor Platform Events and CDC.

Talking point to explain it to anyone: "Pull is refreshing a website to see if anything changed. Push is a notification that pings you the instant it does. A webhook is just that notification, but for two apps."


7. Key terms

Push, pull, polling, webhook, callback, event, publish/subscribe, Platform Event, Change Data Capture, Pub/Sub API, Outbound Message, streaming — all defined in 02-core-vocabulary.md and the README glossary.


Sources (Verified June 2026)


Next: 08-middleware-and-esb.md — the broker in the middle that connects many systems without point-to-point spaghetti.

08 - Middleware and the ESB (the glue layer)

One-liner: Middleware is software that sits between two systems and connects, translates, routes, and orchestrates the traffic between them. Why it matters: As soon as you have more than a couple of systems, wiring them directly to each other becomes a tangle. Middleware is the hub that keeps it clean, and MuleSoft (owned by Salesforce) is the name you must know.

New here? Read 01-what-and-why-of-integration.md first for the big picture.


1. The idea in plain English

Imagine an office where everyone needs to talk to everyone else. If each person runs their own phone wire to every other person, you get a wall of spaghetti. Add one new hire and you have to run a wire from them to everybody. Now picture a switchboard in the middle instead. Everyone plugs into the switchboard once, and it routes calls, translates languages, and keeps a log. Adding a new hire means one wire to the switchboard.

Middleware is that switchboard for software. It is the layer in the middle that:

  • Connects systems that speak different protocols.
  • Translates data from one format or shape into another.
  • Routes a message to the right destination based on rules.
  • Orchestrates multi-step flows ("call A, then B, then write to C").
  • Throttles traffic so a slow system is not overwhelmed.

Without it, every system talks directly to every other system. That works for one or two links. It collapses at scale.

flowchart LR A[App A] --> M{{Middleware<br/>connect, translate,<br/>route, orchestrate}} B[App B] --> M C[App C] --> M M --> SF[Salesforce] M --> ERP[ERP] M --> DW[Data Warehouse] style M fill:#ff9900,color:#fff style SF fill:#00A1E0,color:#fff style ERP fill:#9C27B0,color:#fff style DW fill:#607D8B,color:#fff

2. Point-to-point vs a middleware hub

This is the single most important picture in this file. The same six systems, two ways of wiring them.

flowchart TB subgraph PTP[Point-to-point - the spaghetti] direction LR P1[CRM] <--> P2[ERP] P1 <--> P3[Billing] P2 <--> P3 P1 <--> P4[Website] P2 <--> P4 P3 <--> P4 end subgraph HUB[Hub-and-spoke - through middleware] direction TB H{{Middleware hub}} H1[CRM] --> H H2[ERP] --> H H3[Billing] --> H H4[Website] --> H end style H fill:#ff9900,color:#fff style H1 fill:#00A1E0,color:#fff style H2 fill:#9C27B0,color:#fff style H3 fill:#4CAF50,color:#fff style H4 fill:#607D8B,color:#fff

In point-to-point, N systems can need up to N x (N-1) / 2 connections. Six systems means up to 15 brittle links, each with its own auth, format, and error handling. In hub-and-spoke, each system connects once to the hub. The hub absorbs the change. Add a seventh system and you add one spoke, not six new wires.


3. ESB vs iPaaS (old hub vs new hub)

There are two generations of the "hub" idea. Interviewers like to hear that you know the difference and the direction of travel.

ESB (Enterprise Service Bus) iPaaS (Integration Platform as a Service)
Era 2000s, the classic enterprise hub 2010s onward, the cloud-native hub
Where it runs Usually on-premises, you host and patch it Cloud, the vendor hosts and operates it
Shape Heavy, central bus all traffic flows through Lighter, often a mesh of managed connectors and APIs
Scaling You buy and rack more hardware Elastic, you turn a dial
Cost model Big upfront license plus ops staff Subscription, pay for what you use
Strength Deep protocol support, mature in legacy estates Fast to stand up, pre-built connectors, API management
Examples TIBCO, IBM Integration Bus, older MuleSoft on-prem MuleSoft Anypoint Platform, Boomi, Workato

Plain version: an ESB is a heavy switchboard you buy, install, and run in your own server room. An iPaaS is the same idea rented as a cloud service, so you skip the hardware and get a library of ready-made plugs.

Note: the line blurs in practice. Modern MuleSoft can run in the cloud, on-prem, or hybrid. The clean exam answer is "ESB = older, heavy, often on-prem hub; iPaaS = modern, cloud, subscription hub."


4. MuleSoft - the name to know

MuleSoft makes the Anypoint Platform, the leading iPaaS. Salesforce acquired MuleSoft in 2018, so when an interviewer asks "how does Salesforce do enterprise integration at scale?", MuleSoft is the headline answer.

What to remember:

  • API-led connectivity is MuleSoft's signature methodology. MuleSoft pioneered the idea. Instead of one-off integrations, you build reusable, purposeful APIs in three tiers and compose them:
  • System APIs unlock a backend (Salesforce, SAP, a database).
  • Process APIs combine and orchestrate data across systems.
  • Experience APIs shape that data for a specific channel (mobile, web, partner).
  • Anypoint Platform bundles iPaaS and full lifecycle API management (design, deploy, secure, monitor) in one place.
  • It sells reuse as the win: build the "Customer" System API once, reuse it everywhere, instead of re-integrating Salesforce for every project.
flowchart TD E[Experience APIs<br/>mobile, web, partner] --> P[Process APIs<br/>orchestrate + combine] P --> S[System APIs<br/>unlock each backend] S --> SF[Salesforce] S --> SAP[SAP / ERP] S --> DB[(Database)] style E fill:#4CAF50,color:#fff style P fill:#ff9900,color:#fff style S fill:#00A1E0,color:#fff style SF fill:#00A1E0,color:#fff style SAP fill:#9C27B0,color:#fff style DB fill:#607D8B,color:#fff

Platform note: from inside Salesforce you can directly invoke MuleSoft APIs and flows, which is why the two are pitched as one connected stack. You do not need MuleSoft for every integration. A single Apex callout to one API needs no middleware at all.


5. When you need middleware (and when you don't)

Reach for middleware when... Direct point-to-point is fine when...
You have many systems to connect, not two. You have one or two simple connections.
Systems need data transformation between formats or shapes. Both sides already speak the same format.
You need orchestration across several steps or systems. It is a single request to a single endpoint.
You must throttle or buffer to protect a slow downstream. Volume is low and steady.
You are bridging protocols (e.g. HTTP to a message queue, SOAP to REST). Both sides speak the same protocol.
You want central logging, monitoring, and reuse. The link is one-off and unlikely to grow.

Rule of thumb: middleware buys you decoupling and reuse at the cost of another system to run and pay for. Justify it with the number of connections and the need to transform or orchestrate, not because it sounds enterprise-grade.


6. Interview Q&A

Q: What is middleware? A: Software that sits between two or more systems and connects, translates, routes, and orchestrates the data flowing between them. It is the switchboard in the middle instead of a wire between every pair of systems.

Q: Point-to-point vs hub-and-spoke. Why does it matter? A: Point-to-point connects every system directly, so connections grow roughly with the square of the number of systems and become brittle spaghetti. A middleware hub has each system connect once to the center, so adding a system adds one spoke. It trades direct simplicity for decoupling and reuse.

Q: ESB vs iPaaS? A: An ESB is the older, heavy, often on-premises enterprise bus you host yourself. An iPaaS is the modern cloud version, delivered as a subscription with pre-built connectors and API management. MuleSoft's Anypoint Platform is the leading iPaaS.

Q: What is MuleSoft and how does it relate to Salesforce? A: MuleSoft makes the Anypoint Platform, the leading iPaaS, and is known for API-led connectivity. Salesforce acquired it in 2018, so it is Salesforce's enterprise integration and API management offering. You can invoke MuleSoft APIs directly from Salesforce.

Q: When would you NOT use middleware? A: When you have only one or two simple connections, both sides speak the same protocol and format, and there is no orchestration or throttling needed. A single Apex callout to one REST API does not need a hub. Adding middleware there is overhead with no payoff.

Talking point to explain it to anyone: "It is the difference between everyone in the office running a phone wire to everyone else, versus everyone plugging into one switchboard in the middle that routes and translates the calls."


7. Key terms

Middleware, ESB, iPaaS, MuleSoft, Anypoint Platform, API-led connectivity, hub-and-spoke, point-to-point, orchestration, throttling, protocol bridging - defined in 02-core-vocabulary.md and the README glossary.


Sources (Verified June 2026)


Next: 09-three-layers-transport-format-auth.md - the single mental model that every integration is built from.

09 - The Three Layers: Transport, Format, Auth

One-liner: Every integration is built from three layers - how the bytes travel (transport), how the data is structured (format), and how identity is proven (auth). Why it matters: This is the capstone mental model of Module 01. Once you see it, choosing an integration becomes "pick one from each layer," and any Salesforce API stops being a mystery.

New here? Read 01-what-and-why-of-integration.md first.


1. The idea in plain English

Think about mailing a letter. Three things must be true for it to work:

  1. A way to travel - the postal road, a truck, a plane. (Transport)
  2. A language inside the envelope - English, French, a shared alphabet. (Format)
  3. Proof of who sent it - a return address, a signature, a wax seal. (Auth)

Change any one and the other two still apply. You can send English by truck or by plane. You can sign a letter written in any language. The three concerns are independent, and you choose one option for each.

Software integration is identical. Every single integration, no matter the vendor, answers the same three questions:

  • TRANSPORT - how do the bytes physically move between the two systems?
  • FORMAT - how is the data inside structured so both sides understand it?
  • AUTH - how does the caller prove it is allowed to do this?

Choosing an integration = picking one option from each layer. For example:

A Salesforce REST API call = HTTPS (transport) + JSON (format) + OAuth 2.0 Bearer token (auth).

That one sentence is the whole module in a nutshell.


2. The three layers (the signature visual)

flowchart TB subgraph L1[Layer 1 - TRANSPORT - how bytes travel] T1[HTTP / HTTPS] T2[gRPC] T3[CometD / long-polling] end subgraph L2[Layer 2 - FORMAT - how data is structured] F1[JSON] F2[XML] F3[Protobuf] end subgraph L3[Layer 3 - AUTH - how identity is proven] A1[OAuth 2.0] A2[API key] A3[mTLS] A4[Basic auth] end L1 --> L2 --> L3 --> R[One concrete integration] style L1 fill:#00A1E0,color:#fff style L2 fill:#ff9900,color:#fff style L3 fill:#9C27B0,color:#fff style R fill:#4CAF50,color:#fff

Read it top to bottom: pick a transport, pick a format, pick an auth method, and the combination is your integration. Different Salesforce APIs simply make different picks.


3. The three layers, one at a time

Transport - how the bytes travel

The pipe the data flows through.

Option What it is Where you see it
HTTP / HTTPS The web's request/response protocol. HTTPS is HTTP wrapped in TLS encryption. Nearly all Salesforce APIs. The default.
gRPC A fast binary protocol over HTTP/2, great for streaming. Salesforce Pub/Sub API.
CometD / long-polling A technique to push events over HTTP by holding the connection open. Salesforce Streaming API, PushTopics, generic events.

Format - how the data is structured

The shape of the payload once it arrives. (Deep dive in 04-json-vs-xml.md.)

Option What it is Where you see it
JSON Lightweight, human-readable key/value text. The modern default. REST API, Bulk API 2.0.
XML Verbose, tag-based, strongly typed via schema. SOAP API.
Protobuf Compact binary format defined by a schema. Small and fast. Pub/Sub API (paired with gRPC).

Auth - how identity is proven

The proof the caller is allowed in. (Whole of Module 03.)

Option What it is Where you see it
OAuth 2.0 Token-based authorization. A short-lived Bearer token unlocks the API. The standard for Salesforce APIs.
API key A single shared secret string passed on each call. Common on simple third-party APIs.
mTLS Both client and server present certificates. Mutual proof. High-security B2B links, some Named Credential setups.
Basic auth Username and password base64-encoded. Weakest. Avoid. Legacy systems only.

4. A concrete example

A web form on your company site needs to create a Lead in Salesforce. Walk the three layers:

  1. Transport - the form posts over HTTPS so the data is encrypted in transit.
  2. Format - the body is JSON: { "LastName": "Khan", "Company": "Acme" }.
  3. Auth - the server attaches an OAuth 2.0 Bearer token in the Authorization header.

Put together, the call is:

POST https://MyDomainName.my.salesforce.com/services/data/v66.0/sobjects/Lead
Authorization: Bearer 00D...!AQ...
Content-Type: application/json

{ "LastName": "Khan", "Company": "Acme" }

POST ... HTTPS is the transport. Content-Type: application/json and the body are the format. Authorization: Bearer ... is the auth. Three layers, one request.


5. How it shows up in Salesforce (the mapping table)

This table is worth memorizing. Name any Salesforce API and you can recite its three layers.

Salesforce API Transport Format Typical Auth Best for
REST API HTTPS JSON (XML optional) OAuth 2.0 Bearer Most modern integrations, single/small record ops
SOAP API HTTPS XML OAuth 2.0 Bearer or Session ID Legacy clients, strongly typed contracts (WSDL)
Bulk API 2.0 HTTPS JSON / CSV OAuth 2.0 Bearer Large data loads, asynchronous batches
Pub/Sub API gRPC over HTTP/2 Protobuf OAuth 2.0 Bearer Event streaming, publish and subscribe
Streaming API CometD / long-polling over HTTPS JSON OAuth 2.0 Bearer Pushing change/platform events to subscribers

Pattern to notice: the auth layer is almost always OAuth 2.0 across Salesforce. What really differs between APIs is the transport and format. That is why Module 03 (auth) is largely shared across every API, while transport and format are what you pick per use case.


6. Common confusions and interview traps

Confusion The clarification
"REST vs SOAP is the whole choice." REST vs SOAP bundles a transport and format choice. The three-layer model breaks it into separate decisions, which is clearer. See 03-rest-vs-soap.md.
"JSON is a protocol." JSON is a format, not a transport. It travels over HTTP. Mixing the layers is a classic slip.
"HTTPS is the security, so I'm done." HTTPS secures the transport (encryption in transit). It does not prove who is calling. You still need an auth layer.
"OAuth is encryption." OAuth is auth (proving identity and permission). Encryption is the transport's job (TLS). Different layers.
"Pub/Sub API is just REST." No. It uses gRPC + Protobuf, a different transport and format from REST's HTTPS + JSON.

7. Interview Q&A

Q: Give me a single mental model for any integration. A: Three independent layers. Transport is how the bytes travel (HTTP/HTTPS, gRPC, CometD). Format is how the data is structured (JSON, XML, Protobuf). Auth is how identity is proven (OAuth 2.0, API key, mTLS, Basic). Every integration picks one from each.

Q: Break a Salesforce REST call into the three layers. A: Transport is HTTPS, format is JSON, auth is an OAuth 2.0 Bearer token in the Authorization header. Change the transport or format and you have a different API, but the auth layer stays OAuth in nearly all cases.

Q: How does Pub/Sub API differ from REST API in this model? A: Same auth layer (OAuth 2.0), but a different transport and format. Pub/Sub uses gRPC over HTTP/2 with Protobuf, which is binary and efficient for streaming, whereas REST uses HTTPS with JSON.

Q: HTTPS already encrypts the call. Why do I still need OAuth? A: Different layers solving different problems. HTTPS (transport) protects the data in transit so it cannot be read or tampered with. OAuth (auth) proves the caller is who they claim and is allowed to do this. Encryption is not identity.

Q: Where does this model send you next when designing an integration? A: Transport choice points you at which API to use, format is detailed in the data formats file, and auth is the whole of Module 03. The model turns one big decision into three smaller, well-scoped ones.

Talking point to explain it to anyone: "It is like mailing a letter. You need a way to travel, a language inside, and proof of who sent it. Software picks one of each, and that combination is the integration."


8. Key terms

Transport, format, auth, HTTP/HTTPS, gRPC, CometD, long-polling, JSON, XML, Protobuf, OAuth 2.0, Bearer token, API key, mTLS, Basic auth - defined in 02-core-vocabulary.md and the README glossary. Auth detail lives in Module 03. Format detail lives in 04-json-vs-xml.md.


Sources (Verified June 2026)


Next: 10-salesforce-as-a-platform.md - why "platform" changes how you design every integration.

10 - Salesforce as a Platform (why it changes integration)

One-liner: Salesforce is not just an app you log into. It is a multitenant, metadata-driven platform with APIs on everything and strict limits, and those four traits shape how you must design every integration. Why it matters: If you design an integration as if Salesforce were a private server you own, you will hit runtime errors in production. Understanding the platform is what separates a junior from a senior integration engineer.

This is the final file of Module 01. It ties the whole module together.


1. The idea in plain English

A regular app is like a single house you own. You control the wiring, you can run the dishwasher and the dryer at the same time, nobody else is affected.

Salesforce is like an apartment building. Many tenants share the same plumbing, the same electrical supply, the same elevators. To keep it fair and safe, the building has rules: don't run every appliance at once, don't hog the elevator, quiet hours apply. The building manager (Salesforce) enforces those rules automatically, and if you break them, the power to your unit trips.

That apartment building is multitenancy, and the rules are governor limits. Once you internalize that you are a tenant sharing infrastructure, every design decision in this module makes sense.


2. The four traits that matter

flowchart TD P[Salesforce as a PLATFORM] --> T1[1. Multitenant<br/>shared infrastructure] P --> T2[2. API-first<br/>everything is callable] P --> T3[3. Governor limits<br/>guardrails for fair use] P --> T4[4. Metadata-driven<br/>+ event-capable] T1 --> D[How you DESIGN integrations] T2 --> D T3 --> D T4 --> D D --> R[Bulkify, respect limits,<br/>go async for volume] style P fill:#00A1E0,color:#fff style T1 fill:#9C27B0,color:#fff style T2 fill:#4CAF50,color:#fff style T3 fill:#F44336,color:#fff style T4 fill:#ff9900,color:#fff style R fill:#607D8B,color:#fff

1. Multitenant

Many customers share the same infrastructure. A single instance of Salesforce runs a shared multitenant database and a multitenant kernel (the application runtime). The kernel reads each tenant's metadata and serves up an app that behaves like it is yours alone. This is why your org feels private even though you share servers with thousands of others.

2. API-first

Nearly everything you can do in the Salesforce UI is also available through an API. Creating records, running queries, deploying metadata, subscribing to events - all callable programmatically. This is why integration is a first-class skill on this platform and not an afterthought.

3. Governor limits

Because resources are shared, Salesforce strictly enforces limits so no single tenant can starve the others. Exceed a limit and you get a runtime error, not a slow response. Limits are the guardrails of multitenancy. (Detailed below.)

4. Metadata-driven and event-capable

Your objects, fields, validation rules, and logic are stored as metadata that the kernel materializes at runtime. The platform is also event-capable: it can publish and subscribe to events (Platform Events, Change Data Capture), enabling real-time, decoupled integration.


3. The metadata-driven kernel serving many orgs

flowchart TB subgraph SF[One Salesforce instance] K{{Multitenant kernel<br/>application runtime}} DB[(Shared database<br/>single schema)] K <--> DB end M1[Org A metadata] --> K M2[Org B metadata] --> K M3[Org C metadata] --> K K --> O1[Org A app + APIs] K --> O2[Org B app + APIs] K --> O3[Org C app + APIs] G[Governor limits<br/>guardrails on every org] -.-> O1 G -.-> O2 G -.-> O3 style K fill:#00A1E0,color:#fff style DB fill:#607D8B,color:#fff style M1 fill:#ff9900,color:#fff style M2 fill:#ff9900,color:#fff style M3 fill:#ff9900,color:#fff style G fill:#F44336,color:#fff

One kernel, one shared database, but each org's metadata makes it behave like its own application. The governor limits wrap every org as guardrails, keeping one tenant from hurting the rest.


4. Governor limits (the ones that bite integrations)

These exist purely because the platform is shared. Know that they exist and roughly what they cap. Exact numbers vary by edition and change across releases, so describe them rather than memorizing figures unless you have checked the current docs.

Limit What it caps Why it matters for integration
Total API requests per 24 hours A daily allocation of API calls for the whole org. For paid editions it starts in the low hundreds of thousands and scales with the licenses you own. It is a soft limit meant for bursts, not for planning normal volume on. A chatty integration that makes a call per record can blow the daily allocation. Batch your calls.
Concurrent long-running requests The number of synchronous API/Apex requests that can run at once once a request exceeds about 5 seconds. The org-wide cap is small (single digits to low tens). Several slow synchronous callouts at once can lock out other traffic. Keep calls fast or go async.
Callouts per transaction The number of HTTP callouts a single Apex transaction may make, plus a cap on total callout time per transaction. You cannot loop over thousands of records firing one callout each. Aggregate or move to async/batch.
Callout timeout How long a single callout may wait, with a configurable default and a hard maximum. A hung external service will not hang forever. Set sensible timeouts and handle failures.
Per-transaction Apex limits SOQL queries, DML statements, CPU time, and heap per transaction. Inbound integrations that trigger Apex must be bulkified to survive a 200-record batch.

Important: treat the precise allocation numbers as "look it up for the current release and edition." The interview-safe move is to describe the limit and the design response, not to quote a figure you might get wrong. (Verified ranges are in Sources below.)


5. Why this changes how you design integrations

This is the payoff of the whole module. Because Salesforce is a shared platform with limits, three habits are mandatory:

Habit What it means Driven by
Bulkify Process records in sets, not one at a time. One query and one DML for 200 records, never 200 of each. Per-transaction SOQL/DML limits
Respect API allocations Count your calls. Use composite or batch endpoints to do more per request. Monitor daily usage. Daily API request allocation
Go async for volume For large data, use Bulk API 2.0, Platform Events, future/queueable Apex, or batch jobs instead of synchronous calls. Concurrent + callout + timeout limits
flowchart LR V[Large volume<br/>or many records?] -->|Yes| ASYNC[Async path<br/>Bulk API 2.0, events,<br/>queueable, batch] V -->|No, small + real-time| SYNC[Synchronous path<br/>REST callout] ASYNC --> OK[Stays within limits] SYNC --> OK style V fill:#ff9900,color:#fff style ASYNC fill:#4CAF50,color:#fff style SYNC fill:#00A1E0,color:#fff style OK fill:#607D8B,color:#fff

One sentence to carry forward: on a shared platform you design for fairness and scale, which in practice means bulkify, count your API calls, and prefer async for anything large.


6. Common confusions and interview traps

Confusion The clarification
"Limits are bugs / Salesforce being stingy." Limits are the price of multitenancy. They guarantee one tenant cannot degrade everyone else. Design within them, do not fight them.
"I'll just retry until it works." Hitting a limit throws a runtime error. Blind retries make it worse. Fix the design (bulkify, async) instead.
"Metadata-driven just means customizable." It is deeper: the kernel materializes your app from metadata at runtime, which is what lets one instance serve many distinct orgs.
"API-first is marketing." It is literal. Almost every UI action has an API equivalent, which is exactly why integration is so central to the platform.
"Governor limits only matter for Apex." They also cap API usage (daily allocation, concurrency, callouts). Inbound and outbound integrations both feel them.

7. Interview Q&A

Q: What does it mean that Salesforce is a platform, not just an app? A: It is multitenant (many customers share one infrastructure), metadata-driven (a kernel builds each org's app from metadata at runtime), API-first (nearly everything in the UI is also callable through an API), and event-capable. Those traits, plus enforced limits, shape every integration you build on it.

Q: What is multitenancy and why should an integration engineer care? A: Many customers share the same database and application kernel, isolated by metadata. You care because shared resources mean enforced governor limits. You must bulkify, watch your API allocation, and design for fairness, since you are not the only tenant.

Q: What are governor limits and why do they exist? A: Runtime caps on resources like API calls per day, concurrent long-running requests, callouts per transaction, and per-transaction SOQL/DML/CPU. They exist because the platform is shared, so no single tenant can monopolize resources. Exceeding one throws a runtime error.

Q: How do platform limits change your integration design? A: Three ways. Bulkify so logic handles record sets, not singletons. Respect the daily API allocation by batching calls and using composite endpoints. Go asynchronous for large volume using Bulk API 2.0, Platform Events, or async Apex, rather than many synchronous calls.

Q: A nightly job needs to load two million records. How do you approach it given the platform? A: Not record-by-record through the REST API, which would burn the daily allocation and hit concurrency limits. Use Bulk API 2.0, which is asynchronous and built for large volumes, processing data in server-side batches that respect the shared-platform constraints.

Talking point to explain it to anyone: "Salesforce is an apartment building, not a private house. Everyone shares the plumbing, so there are house rules. Design your integration like a good neighbor: don't run every appliance at once, and do the big laundry loads off-peak."


8. Key terms

Platform, multitenant, metadata-driven, kernel, API-first, governor limits, API request allocation, concurrent requests, bulkify, asynchronous, Platform Events, Bulk API 2.0 - defined in 02-core-vocabulary.md and the README glossary.


Sources (Verified June 2026)


Next: back to the README.md module index. From here, the natural continuation is Module 02 - Integration Patterns, where these fundamentals turn into concrete design choices (request/reply, fire-and-forget, batch, data virtualization, and more).

Module 02 - Integration Patterns

Goal: Memorize the 6 official Salesforce integration patterns and know exactly when to use which. API version: v66.0 (Spring '26). Outcome: given any integration requirement, point at one pattern and justify it in a sentence.

A pattern is a named, reusable answer to a recurring integration problem. Salesforce's official Integration Patterns and Practices guide defines six. Master these and you can scope any integration before touching code. New to the vocabulary (sync/async, inbound/outbound, push/pull)? Read Module 01 first.


How to use this module

  1. Read the patterns in order (01 → 06). Each is short and built the same way.
  2. Use the decision tree and comparison table below as your interview cram sheet.
  3. For the auth behind any callout or inbound call, see Module 03 - Authentication.

Map of this module

# Pattern Direction Timing
01 request-and-reply Salesforce → External Synchronous
02 fire-and-forget Salesforce → External Asynchronous
03 batch-data-synchronization Both ways Scheduled / async
04 remote-call-in External → Salesforce Sync or async
05 data-virtualization Salesforce reads External Real-time read
06 ui-update-based-on-data-changes Salesforce → subscribers Async push

Which pattern? (decision tree)

flowchart TD Start([New integration requirement]) --> Dir{Who initiates,<br/>and what's needed?} Dir -->|External system calls Salesforce| RCI[Remote Call-In<br/>REST / SOAP / Apex REST] Dir -->|Show external data without storing it| DV[Data Virtualization<br/>Salesforce Connect] Dir -->|UI must refresh when data changes| UI[UI Update on Change<br/>CDC / Pub-Sub API] Dir -->|Salesforce calls out| Vol{Volume and<br/>response need?} Vol -->|High volume, scheduled| BATCH[Batch Data Sync<br/>Bulk API 2.0 / ETL] Vol -->|Real-time, need the answer| RR[Request and Reply<br/>HTTP callout] Vol -->|Real-time, no answer needed| FF[Fire and Forget<br/>Platform Events] style RCI fill:#9C27B0,color:#fff style DV fill:#4CAF50,color:#fff style UI fill:#ff9900,color:#fff style BATCH fill:#9C27B0,color:#fff style RR fill:#00A1E0,color:#fff style FF fill:#ff9900,color:#fff

The master comparison table (memorize this)

Pattern Direction Timing Volume Salesforce tech Use when
Request & Reply (01) SF → External Sync Low HTTP callout, External Services You need the answer now to continue.
Fire & Forget (02) SF → External Async Low-Med Platform Events, Outbound Messages You announce an event and don't wait.
Batch Data Sync (03) Both ways Scheduled High Bulk API 2.0, ETL/MuleSoft Large volumes on a schedule.
Remote Call-In (04) External → SF Sync/Async Any REST/SOAP API, Apex REST An external system drives the CRUD.
Data Virtualization (05) SF reads External Real-time read Low-Med Salesforce Connect (OData) View external data without copying it.
UI Update on Change (06) SF → subscribers Async push Med CDC, Pub/Sub API, empApi UI must auto-refresh on a change.

Scenario → pattern (quick mapping)

Requirement Pattern
"Check a credit score before saving the loan." Request & Reply
"Tell the ERP an order shipped." Fire & Forget
"Sync 2M accounts from SAP every night." Batch Data Sync
"Our website must create Leads in Salesforce." Remote Call-In
"Show SAP invoices on the Account, don't store them." Data Virtualization
"The agent console must update the moment a case changes." UI Update on Change

Interview rapid-fire

Q: Request & Reply vs Fire & Forget? → Reply waits for and uses the response (sync). Forget sends and moves on (async). Choose by whether the result drives the next step.

Q: A partner wants to push orders into Salesforce. Which pattern and tech? → Remote Call-In via the REST API (or Apex REST for custom logic), authenticated with a Connected App / External Client App + OAuth.

Q: 5 million records nightly. Which pattern? → Batch Data Sync with Bulk API 2.0, upserting by External ID, scheduled off-peak. Never loop synchronous callouts.

Q: Show live external data without storing it? → Data Virtualization with Salesforce Connect (External Objects over OData). Each view triggers a callout, so mind performance.

Q: Make an LWC refresh instantly when a record changes? → UI Update on Change using Change Data Capture / Pub/Sub API, with the LWC subscribing via lightning/empApi.


Sources (Verified June 2026)

Each pattern file has its own Sources section with the specific official doc.

01 - Request and Reply

One-liner: Salesforce calls an external system, waits for the answer, and uses it right away. Direction: Salesforce → External (outbound). Timing: Synchronous. Volume: Low, one record/transaction at a time. Use when: A user or process needs an immediate response before continuing.

This is Module 02, the integration patterns. New to the vocabulary (sync/async, inbound/outbound)? See Module 01. For the auth behind callouts, see Module 03.


1. The idea in plain English

Request and Reply is a phone call. You dial, you ask your question, and you stay on the line until the other person answers. You cannot continue your day until you hear back. If they do not pick up, or take too long, your whole call fails.

In Salesforce terms: Apex (or Flow) makes an HTTP callout to an external service, the user or transaction blocks until the response arrives, and the result is shown or saved immediately. Think of a rep clicking "Check Credit Score" and seeing the score appear two seconds later. That round-trip, in-the-moment answer is the whole pattern.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You need the response right now to continue (validate, enrich, display). You do not need an answer back → 02-fire-and-forget.md.
The work is one record / small and the external system is fast. High volume or bulk loads → 03-batch-data-synchronization.md.
A user is waiting at the screen, or a process must branch on the result. The external call is slow/long-running (risk of timeout).

Real-world examples: real-time credit check during loan creation, address validation on a form, fetching live inventory/price before quoting, a payment authorization call.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant U as User / Process participant SF as Salesforce<br/>(Apex / Flow) participant EXT as External System U->>SF: Trigger action (e.g. Save / button) SF->>EXT: HTTP request via Named Credential<br/>(callout) Note over SF,EXT: Salesforce BLOCKS and waits EXT-->>SF: Response (200 + data) SF->>SF: Parse, store, or branch on result SF-->>U: Show result / continue

Walkthrough

  1. A user action or process kicks off the transaction.
  2. Apex or Flow makes an outbound HTTP callout (through a Named Credential, so no hardcoded secrets).
  3. Salesforce waits synchronously for the reply. The transaction is paused.
  4. The external system returns data.
  5. Salesforce parses it and acts (saves a field, makes a decision).
  6. Control returns to the user with the result.

4. How it shows up in Salesforce (the tech)

Tool What it is Use it for
Apex HTTP callout HttpRequest/Http classes calling a REST endpoint via callout:NamedCredential. Custom logic, full control over request/response.
External Services Declarative import of an OpenAPI spec; generates invocable actions for Flow. Low-code request/reply from Flow, no Apex.
Flow HTTP Callout Declarative callout action configured in Flow. Admin-built real-time calls.
Apex SOAP callout Generated stub from a WSDL. Legacy SOAP services.

Minimal Apex shape (note the Named Credential, never a hardcoded URL or token):

HttpRequest req = new HttpRequest();
req.setEndpoint('callout:Credit_Service/score?ssn=' + ssn);
req.setMethod('GET');
req.setTimeout(120000); // max 120s
HttpResponse res = new Http().send(req);
if (res.getStatusCode() == 200) {
    Map<String,Object> body = (Map<String,Object>) JSON.deserializeUntyped(res.getBody());
    // use body, save to record
}

Auth: outbound callouts authenticate with a Named Credential + External Credential. See Module 03 - Named Credentials.


5. Design considerations and gotchas

Consideration Why it matters What to do
Callout timeout Max 120 seconds per callout; default 10s. A slow service kills the transaction. Keep services fast. Set a sensible setTimeout. If slow, switch to async.
Callouts per transaction Max 100 callouts per Apex transaction. Batch logic; do not loop callouts per record.
No callouts after DML (uncommitted) You cannot call out after a pending DML in the same transaction. Call out first, or use async (@future(callout=true), Queueable).
User is blocked Synchronous means the user waits. Only use for genuinely fast calls.
Error handling The external system may be down or slow. Handle non-200 codes, timeouts, and retries gracefully.
Idempotency A retry could double-charge or double-create. Make requests safe to repeat where possible.

6. Interview Q&A

Q: What is the Request and Reply pattern? A: Salesforce calls an external system synchronously and waits for the response before continuing. It is for real-time, low-volume calls where you need the answer immediately, like a credit check at the point of sale.

Q: How do you implement it in Salesforce? A: An Apex HTTP callout (or Flow HTTP Callout / External Services for low-code), authenticated through a Named Credential. The transaction blocks until the response returns.

Q: What are the main limits to watch? A: 120-second max callout timeout, 100 callouts per transaction, and you cannot make a callout after uncommitted DML. If the service is slow or volume is high, move to async or batch instead.

Q: How is this different from Fire and Forget? A: Request and Reply waits for and uses the response. Fire and Forget sends a message and moves on without waiting. Use Reply when the result drives the next step, Forget when it does not.

Q: The external API takes 5 minutes. Now what? A: Do not hold a synchronous transaction. Switch to an asynchronous approach: queue the work (Queueable/@future) or redesign as Fire and Forget with a callback, so the user is not blocked and you avoid the timeout.

Talking point to explain it to anyone: "It's a phone call. You ask, you wait on the line, you get your answer, then you act on it."


7. Key terms

HTTP callout, Named Credential, synchronous, timeout, idempotency, External Services - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 02-fire-and-forget.md - when Salesforce sends a message and does not wait for a reply.

02 - Fire and Forget

One-liner: Salesforce announces that something happened and moves on. It does not wait for a reply. Direction: Salesforce → External (outbound). Timing: Asynchronous. Volume: Many small events, near real time. Use when: You need to broadcast a business event ("OrderShipped") and you do not need an answer back.

This is Module 02, the integration patterns. New to the vocabulary (sync/async, inbound/outbound, pub/sub)? See Module 01. For the auth behind callouts and event subscribers, see Module 03.


1. The idea in plain English

Fire and Forget is posting a letter. You write it, drop it in the mailbox, and walk away. You trust the postal service to deliver it. You do not stand at the box waiting for the recipient to read it and reply. Your day continues immediately.

In Salesforce terms: a process publishes an event (or sends a one-way message) describing something that just happened, then the transaction continues without blocking. One or more external subscribers pick the message up later and do their own work. The publisher and subscriber are loosely coupled — neither has to be online at the same time, and the publisher does not care who is listening or what they do with it.

Contrast this with Request and Reply, which is a phone call you stay on the line for. Here, you hang up before anyone answers.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You are announcing a fact ("Order shipped", "Case closed") and need no answer. You need the response to continue → 01-request-and-reply.md.
You want loose coupling — the subscriber can be down and catch up later. You are moving large data sets on a schedule → 03-batch-data-synchronization.md.
Multiple systems may want the same event (fan-out to many subscribers). You need a guaranteed, transactional round-trip with rollback.
The receiving system handles its own queuing, retry, and errors. The user is waiting at the screen for a result.

Real-world examples: publish OrderShipped so a logistics app and a notification service both react; emit LeadConverted for a marketing platform; send a legacy ERP a one-way AccountUpdated notice via Outbound Message; fan a PaymentReceived event out to several downstream systems.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant U as User / Process participant SF as Salesforce<br/>(Apex / Flow) participant BUS as Event Bus<br/>(Salesforce) participant SUB as External Subscriber U->>SF: Trigger action (e.g. Order saved) SF->>BUS: Publish event (e.g. OrderShipped__e) Note over SF,U: Salesforce does NOT wait. Transaction continues. SF-->>U: Done (control returns immediately) BUS-->>SUB: Deliver event later (Pub/Sub API or CometD) SUB->>SUB: Process at its own pace Note over BUS,SUB: Missed it? Replay by Replay ID (72h window)

Walkthrough

  1. A user action or backend process completes some work.
  2. Salesforce publishes a platform event to the event bus (or sends a one-way Outbound Message).
  3. Salesforce does not block — control returns to the user/process right away.
  4. The transaction finishes normally.
  5. The event bus delivers the message to each external subscriber asynchronously, on their schedule.
  6. Each subscriber processes independently and owns its own error handling.
  7. If a subscriber was offline, it can replay missed events by Replay ID within the retention window.

4. How it shows up in Salesforce (the tech)

The official guide gives Fire and Forget two main implementation options: the modern event-driven approach (Platform Events) and the legacy declarative approach (Outbound Messages).

Tool What it is Use it for
Platform Events (recommended) Publish/subscribe events on the Salesforce event bus. Publish from Apex, Flow, or API. Subscribe via Pub/Sub API (gRPC/HTTP/2, Avro) or CometD. The modern, loosely-coupled, fan-out approach. Multiple subscribers, replay support.
Outbound Messages (legacy) A declarative SOAP message sent to a fixed endpoint, fired from Flow (or legacy Workflow). Salesforce auto-retries on failure. Simple point-to-point notify to one endpoint, no code. Older orgs/integrations.
Change Data Capture (CDC) A built-in stream of record-change events (create/update/delete/undelete) on the same bus. Broadcasting data changes without defining custom events.
Apex callout (async) A one-way @future(callout=true) or Queueable callout via callout:NamedCredential. Fire-and-forget HTTP notify where an event channel is overkill.

Publish a Platform Event from Apex (no hardcoded endpoint — subscribers connect to the bus):

// OrderShipped__e is a Platform Event with custom fields
OrderShipped__e evt = new OrderShipped__e(
    OrderId__c   = order.Id,
    TrackingNo__c = trackingNumber,
    ShippedAt__c = System.now()
);
Database.SaveResult sr = EventBus.publish(evt);
if (!sr.isSuccess()) {
    for (Database.Error err : sr.getErrors()) {
        System.debug('Publish failed: ' + err.getMessage());
    }
}

For an async one-way HTTP notify instead of an event, the outbound callout still goes through a Named Credential — never a hardcoded URL or secret:

@future(callout=true)
public static void notifyExternal(Id orderId) {
    HttpRequest req = new HttpRequest();
    req.setEndpoint('callout:Logistics_API/v1/shipped');
    req.setMethod('POST');
    req.setHeader('Content-Type', 'application/json');
    req.setBody(JSON.serialize(new Map<String,Object>{ 'orderId' => orderId }));
    new Http().send(req); // fire it; we do not act on the response
}

Auth: Outbound callouts use a Named Credential + External Credential. External subscribers to events authenticate to the Pub/Sub/Streaming API via OAuth. See Module 03 - Named Credentials and Module 03 README.


5. Design considerations and gotchas

Consideration Why it matters What to do
No immediate response You give up the answer by design. The publisher learns nothing about downstream success. Only use when you truly do not need the result. Add an optional callback if the remote must report back.
Delivery guarantees differ Platform Events publish to the bus once with no Salesforce-side retry — the subscriber must replay. Outbound Messages auto-retry with backoff for up to ~24h. Pick the option that matches your durability need. For events, build replay-by-Replay-ID into the subscriber.
Events are not transactional on delivery A published platform event cannot be rolled back within the transaction once committed. Publish after your DML succeeds. Do not assume publish = subscriber processed.
Replay window High-volume event messages are retained 72 hours (3 days). Miss it and it is gone. Subscribers must track the Replay ID of the last processed event and resume from it.
Publishing / delivery limits Orgs have hourly publishing and daily event delivery allocations (by license). A fan-out to many subscribers multiplies the delivery count. Watch the allocations in the Platform Events Developer Guide. Avoid needless fan-out.
Outbound Message expiry An unacknowledged Outbound Message is retried then expires (kept ~24h, failed list cleared after 7 days). Make the endpoint highly available and acknowledge promptly. Monitor the failed-messages list.
Idempotency Retries and replays can deliver the same event more than once (at-least-once semantics). Subscribers must dedupe using the Replay ID or a business key. Make processing safe to repeat.
Eventual consistency Subscribers process later, so the two systems are briefly out of sync. Design downstream logic to tolerate a short lag. Do not expect instant consistency.

6. Interview Q&A

Q: What is the Fire and Forget pattern? A: Salesforce publishes a message or event about something that happened and continues without waiting for a reply. It is asynchronous, loosely coupled, and used to broadcast business events like "OrderShipped" where you do not need an answer back.

Q: What are the two main ways to implement it in Salesforce? A: The modern, recommended way is Platform Events — publish from Apex/Flow/API and let external subscribers consume via the Pub/Sub API or CometD. The legacy way is Outbound Messages — a declarative SOAP message sent from Flow to a fixed endpoint, with automatic retry.

Q: How is delivery guaranteed for each? A: They differ. Outbound Messages retry automatically with backoff for about 24 hours until acknowledged. Platform Events publish to the bus once with no Salesforce-side retry; durability comes from the 72-hour replay window — the subscriber resumes from its last Replay ID. Both are effectively at-least-once, so subscribers must be idempotent.

Q: How does this differ from Request and Reply? A: Request and Reply is synchronous — Salesforce waits for and uses the response. Fire and Forget sends and moves on. Use Reply when the result drives the next step; use Forget when announcing a fact to one or many systems.

Q: A subscriber was down for an hour. Did it lose the events? A: Not if it acts within the retention window. Platform Events keep high-volume messages for 72 hours; the subscriber replays from the Replay ID of the last event it processed. Outbound Messages retry until acknowledged or until they expire (~24h).

Talking point to explain it to anyone: "It's posting a letter. You drop it in the box and walk away — you trust it gets there, you don't wait for a reply."


7. Key terms

Platform Events, event bus, Pub/Sub API, CometD, Replay ID, Outbound Message, Change Data Capture, idempotency, eventual consistency, at-least-once delivery - defined in Module 01 vocabulary and the README. Deeper dive in Module 06 - Event-Driven.


Sources (Verified June 2026)


Next: 03-batch-data-synchronization.md - moving large data sets on a schedule, both directions, off-peak.

03 - Batch Data Synchronization

One-liner: Move large volumes of data between Salesforce and another system on a schedule, usually overnight. Direction: Both ways (Salesforce → External and External → Salesforce). Timing: Scheduled / batch. Volume: HIGH, off-peak. Use when: You need a nightly sync of big datasets between Salesforce and an ERP or data warehouse.

This is Module 02, the integration patterns. New to the vocabulary (sync/async, ETL, upsert, External ID)? See Module 01. For the auth a middleware tool uses to reach Salesforce, see Module 03.


1. The idea in plain English

Batch Data Synchronization is the overnight delivery truck. You do not send a courier for every single parcel (that would be one-at-a-time Request and Reply). Instead, you let parcels pile up all day, then a big truck runs once at night when the roads are empty and hauls everything in one trip. It is slow per parcel but extremely efficient for volume.

In Salesforce terms: a scheduled job extracts or loads thousands to millions of records in batches, typically during off-peak hours so it does not compete with daytime users or burn the daily API budget. The goal is to keep two systems in agreement — Salesforce and an ERP, a data warehouse, or a billing platform — without hammering them in real time.

Contrast this with Fire and Forget (one small event at a time) and Request and Reply (one synchronous round-trip). Batch is the heavyweight for bulk.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You sync large datasets (tens of thousands+ records) on a recurring schedule. You need real-time freshness within seconds → events or 01-request-and-reply.md.
Latency is acceptable — nightly or hourly is fine. A single record must update the instant it changes → 02-fire-and-forget.md / CDC.
You can run off-peak to protect daytime performance and API limits. The volume is tiny (a handful of records) — batch is overkill.
Source and target each have a stable unique key (External ID) to match on. There is no reliable key to dedupe against — fix that first.

Real-world examples: nightly ERP → Salesforce product/price catalog refresh; hourly Salesforce → data warehouse export of closed opportunities for BI; daily account/contact reconciliation with a master data system; bulk migration during a go-live.


3. How it works (flowchart)

flowchart TD A[Scheduler fires off-peak<br/>cron / middleware trigger] --> B{Extract delta only<br/>changed since last run} B --> C[Transform / map fields<br/>middleware or ETL] C --> D[Load in batches via<br/>Bulk API 2.0 - upsert] D --> E{Match on<br/>External ID} E -->|Found| F[Update existing record] E -->|Not found| G[Insert new record] F --> H[Per-batch result<br/>success and error files] G --> H H --> I{Errors?} I -->|Yes| J[Log, alert, retry failed rows] I -->|No| K[Record high-water mark<br/>for next delta] style A fill:#607D8B,color:#fff style C fill:#9C27B0,color:#fff style D fill:#00A1E0,color:#fff style J fill:#F44336,color:#fff style K fill:#4CAF50,color:#fff

Walkthrough

  1. A scheduler (cron, middleware, or a Salesforce scheduled job) kicks off the run during off-peak hours.
  2. Delta sync: extract only records that changed since the last run (using a timestamp or high-water mark), not the whole table.
  3. A middleware/ETL layer transforms and maps fields between the two data models.
  4. The data is loaded asynchronously in batches via Bulk API 2.0.
  5. The load uses upsert keyed on an External ID so each row either updates a match or inserts a new record.
  6. Bulk API returns per-batch success and error results.
  7. Failed rows are logged, alerted on, and retried; the run records a new high-water mark for the next delta.

4. How it shows up in Salesforce (the tech)

Tool What it is Use it for
Bulk API 2.0 REST-based async API to insert/upsert/update/delete/query large datasets. Salesforce chunks the job into batches for you. The core engine for high-volume load and extract.
ETL / middleware MuleSoft, Informatica, Talend, Jitterbit, etc. Handle scheduling, transformation, connectivity, retries. Orchestrating the sync, mapping data models, error handling.
Scheduled Apex / Flow Schedulable Apex or a scheduled Flow inside Salesforce. Triggering exports/processing from the Salesforce side.
Bulk API 2.0 Query Async extract of large query results. Pulling big result sets out of Salesforce to a warehouse.
Data Loader / SF CLI Command-line / desktop tools that sit on Bulk API. Scriptable scheduled loads, admin one-offs.

Upsert by External ID is the heart of safe batch sync. The External ID column lets the same job both match-and-update and insert, with no duplicates. A Bulk API 2.0 ingest job specifies it like this:

POST /services/data/v66.0/jobs/ingest
{
  "object": "Account",
  "operation": "upsert",
  "externalIdFieldName": "ERP_Id__c",
  "contentType": "CSV"
}

Then upload the CSV (keep each file within Salesforce's recommended size), close the job, and poll for results. Conceptually:

# 1. Create the job (above)  ->  returns a jobId and contentUrl
# 2. PUT the CSV data to the contentUrl
# 3. PATCH the job state to "UploadComplete"
# 4. GET .../jobs/ingest/{jobId}            -> watch state = JobComplete
# 5. GET .../successfulResults  and  .../failedResults

A scheduled Apex job to trigger an export or downstream processing (the callout itself uses a Named Credential, never a hardcoded secret):

global class NightlySyncJob implements Schedulable {
    global void execute(SchedulableContext ctx) {
        // enqueue work; outbound calls go through callout:Warehouse_API
        System.enqueueJob(new ExportClosedOppsQueueable());
    }
}
// Schedule: System.schedule('Nightly Sync', '0 0 2 * * ?', new NightlySyncJob());

Auth: A middleware tool authenticates to Salesforce as a system integration user, typically via OAuth 2.0 JWT Bearer or Client Credentials (no interactive login). See Module 03 - JWT Bearer and the Module 03 README.


5. Design considerations and gotchas

Consideration Why it matters What to do
Off-peak scheduling Daytime batch loads slow users and chew the 24-hour API allocation (Enterprise starts at 100,000 requests/24h and scales with licenses). Run at night/low-traffic windows. Stagger jobs so they do not overlap.
Delta sync, not full sync Re-syncing every row nightly wastes API capacity and processing time. Extract only records changed since the last run using a timestamp / high-water mark.
Upsert with External ID Without a stable key you create duplicates and break cross-system links. Define an indexed, unique External ID on the object and upsert on it.
Per-batch error handling A bad row should not fail the whole load. Bulk API isolates failures per batch. Read success and error result files, log failures, retry only failed rows.
Idempotency A re-run after a partial failure must not double-insert. Upsert (not insert) + External ID makes re-runs safe to repeat.
Bulk vs governor limits Bulk API is async and high-throughput, but triggers/flows on the records still run and hit Apex governor limits per batch. Bulkify all automation. Consider disabling heavy triggers during migration loads.
Ordering and dependencies Loading children before parents (lookups) fails. Load in dependency order (parents first), or use External IDs on lookups so order matters less.
Monitoring & alerting A silent overnight failure is discovered too late. Alert on job failure and on error-row thresholds. Track run duration and record counts.

6. Interview Q&A

Q: What is the Batch Data Synchronization pattern? A: Moving large volumes of data between Salesforce and another system on a schedule, usually overnight, to keep them in agreement. It is asynchronous, high-volume, and latency-tolerant — the opposite of a real-time call.

Q: Which Salesforce technology do you use, and why? A: Bulk API 2.0, because it is built for asynchronous high-volume load and extract and chunks the work into batches automatically. Usually orchestrated by ETL/middleware (MuleSoft, Informatica) that handles scheduling, transformation, and retries.

Q: How do you avoid creating duplicates? A: Upsert keyed on an External ID — a stable, unique key shared with the source system. Each row updates a match or inserts if none exists. This also makes the job idempotent, so a re-run after a partial failure is safe.

Q: What is delta sync and why does it matter? A: Only extracting and loading records that changed since the last run, tracked with a timestamp or high-water mark, instead of the whole dataset. It dramatically cuts API usage, runtime, and load on both systems.

Q: Why run off-peak, and what limit are you protecting? A: To avoid slowing daytime users and to protect the 24-hour rolling API request allocation (Enterprise Edition starts around 100,000 and scales with licenses). Off-peak windows leave headroom and reduce contention.

Talking point to explain it to anyone: "It's the overnight delivery truck. Parcels pile up all day, then one big truck hauls them all at night when the roads are empty."


7. Key terms

Bulk API 2.0, ETL, middleware, upsert, External ID, delta sync, high-water mark, idempotency, off-peak, API allocation - defined in Module 01 vocabulary and the README. Deeper dive in Module 07 - Bulk and Async.


Sources (Verified June 2026)


Next: 04-remote-call-in.md - when an external system calls into Salesforce to create, read, or update data.

04 - Remote Call-In

One-liner: An external system calls into Salesforce to create, read, update, or delete data. Salesforce is the listener. Direction: External → Salesforce (inbound). Timing: Synchronous or asynchronous. Volume: One record to bulk. Use when: A website creates a Lead in Salesforce, or an ERP pushes order updates into Salesforce.

This is Module 02, the integration patterns. New to the vocabulary (inbound/outbound, REST/SOAP, idempotency)? See Module 01. The external app must authenticate first — see Module 03.


1. The idea in plain English

Remote Call-In flips the direction. In the earlier patterns Salesforce picked up the phone and dialed out. Here, Salesforce is the shop with a front counter, and an outside customer walks in to do business. The external system is the caller; Salesforce exposes a counter (an API) and serves the request — looking up a record, creating one, updating one.

In Salesforce terms: an external app sends an HTTP/SOAP request to a Salesforce API endpoint to perform CRUD on records. Salesforce authenticates the caller, runs the operation under that caller's permissions and sharing, and returns a response. This is the backbone of "a form on our website creates a Lead" or "our ERP keeps Orders in sync by pushing changes."

Because someone else is calling you, the two big themes are security (who is allowed in, and to do what) and resilience (handling retries without creating duplicates).


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
An external system needs to push or pull Salesforce data on its own schedule. Salesforce needs to call out and use a reply → 01-request-and-reply.md.
The caller drives the timing (a web form submit, an ERP event). Salesforce announces an event outward → 02-fire-and-forget.md.
You want standard CRUD (REST/SOAP) or a custom contract (Apex REST). Massive scheduled loads you control → 03-batch-data-synchronization.md (Bulk API).
You need multi-object or transactional inbound logic. The external data should stay external and be read live → 05-data-virtualization.md.

Real-world examples: a marketing website form creates a Lead; an ERP updates Orders in Salesforce when they ship; a partner portal reads Account status via REST; a mobile app does CRUD through a custom Apex REST endpoint; a billing system bulk-loads invoices via Bulk API.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant EXT as External System participant OAUTH as Salesforce OAuth<br/>(/services/oauth2/token) participant API as Salesforce API<br/>(REST / SOAP / Apex REST) participant DB as Salesforce Records EXT->>OAUTH: Authenticate (OAuth via Connected App / ECA) OAUTH-->>EXT: access_token + instance_url EXT->>API: HTTP request + Bearer token<br/>(e.g. POST a Lead) API->>API: Enforce auth, sharing, validation API->>DB: Create / Read / Update / Delete DB-->>API: Result API-->>EXT: HTTP status + body (e.g. 201 + new Id) Note over EXT,API: Use upsert by External ID so retries do not duplicate

Walkthrough

  1. The external app authenticates to Salesforce with OAuth, using credentials from a Connected App / External Client App.
  2. Salesforce returns an access token and the instance URL to call.
  3. The app sends the actual request (e.g. create a Lead) with Authorization: Bearer <token>.
  4. Salesforce enforces authentication, the user's sharing/permissions, and validation rules.
  5. The CRUD operation runs against the database.
  6. Salesforce returns the result.
  7. The app receives an HTTP status code and body (e.g. 201 Created with the new record Id). Using upsert by External ID keeps retries from creating duplicates.

4. How it shows up in Salesforce (the tech)

Inbound calls use either a standard API (no code) or a custom endpoint (Apex). Pick the simplest that meets the contract.

Tool What it is Use it for
REST API Standard RESTful CRUD over JSON/XML on sObjects. Most modern inbound integrations. Single-record CRUD, queries.
SOAP API Standard XML/WSDL CRUD. Synchronous. SOAP-based callers, legacy enterprise systems.
Apex REST Custom endpoint via @RestResource(urlMapping=...) at /services/apexrest/. Custom request/response shape, multi-object or pre/post logic.
Apex SOAP web service Custom SOAP service via the webservice keyword. Custom SOAP contract for legacy callers.
Composite API Bundle several REST calls (or related records) into one request. Reduce round-trips, multi-step inbound operations.
Bulk API (2.0) Async high-volume inbound load. Large inbound data sets (see 03-batch-data-synchronization.md).

A custom inbound endpoint with Apex REST. Note the upsert by External ID for idempotency and the explicit status code:

@RestResource(urlMapping='/v1/orders/*')
global with sharing class OrderInboundService {

    @HttpPost
    global static String upsertOrder(String externalId, Decimal amount) {
        RestResponse res = RestContext.response;
        try {
            Order__c o = new Order__c(ERP_Id__c = externalId, Amount__c = amount);
            // Upsert on the External ID field -> safe to retry, no duplicates
            upsert o ERP_Id__c;
            res.statusCode = 200;            // 201 if you detect a fresh insert
            return o.Id;
        } catch (Exception e) {
            res.statusCode = 400;            // return a meaningful error code
            return 'ERROR: ' + e.getMessage();
        }
    }
}

The classic Apex SOAP web service equivalent uses the webservice keyword:

global with sharing class OrderSoapService {
    webservice static Id upsertOrder(String externalId, Decimal amount) {
        Order__c o = new Order__c(ERP_Id__c = externalId, Amount__c = amount);
        upsert o ERP_Id__c;
        return o.Id;
    }
}

Auth (read this): the external caller authenticates with a Connected App / External Client App + OAuth. For server-to-server use JWT Bearer or Client Credentials; for user context use the Web Server flow. Salesforce runs the operation under that user's profile, permission sets, and sharing rules — so grant least privilege. Full detail in Module 03 README.

Heads up - SOAP login() retirement: the legacy SOAP API login() call (API versions 31.0-64.0) is being retired in Summer '27. Inbound auth is moving to OAuth (Web Server, JWT Bearer, or Client Credentials). From Summer '26, new orgs also require a Use Any API Auth permission to use SOAP login(). Plan inbound integrations on OAuth, not login().


5. Design considerations and gotchas

Consideration Why it matters What to do
API request limits Inbound calls count against the org's 24-hour rolling allocation (Enterprise starts ~100,000 and scales with licenses), shared with all other API traffic. Use Composite/Bulk to reduce call count. Monitor usage. Rate-limit chatty callers.
Idempotency The caller may retry on timeout and create duplicates. Expose upsert by External ID, or dedupe on a business key. Make endpoints safe to call twice.
Security & least privilege Operations run as the integration user with that user's sharing and permissions. Over-permissioned users are a breach risk. Use a dedicated integration user with minimum profile/permission sets. Restrict object/field access.
Authentication method Username-password and SOAP login() are legacy and being retired. Use OAuth (JWT Bearer / Client Credentials for system, Web Server for user). See Module 03.
Bulkification A loop doing one DML per record blows Apex governor limits under load. Bulkify Apex REST logic. Accept and process collections, not one record per call.
Proper status codes Callers branch on HTTP status; returning 200 for an error breaks their retry logic. Return correct codes: 201 create, 200 update, 400 bad input, 401 auth, 409 conflict.
Validation & automation Inbound writes still fire validation rules, triggers, and flows. Account for them. Return clear errors when a validation rule blocks the write.
Sync vs async SOAP API and Apex SOAP services are synchronous; long work blocks the caller. For heavy work, accept the request fast and process async (Queueable / Platform Event), or use Bulk API.

6. Interview Q&A

Q: What is the Remote Call-In pattern? A: An external system calls into Salesforce to create, read, update, or delete data. Salesforce is the listener exposing an API. It is inbound — the opposite of Request and Reply, where Salesforce calls out.

Q: What are the main technologies for inbound calls? A: Standard REST API and SOAP API for CRUD, Apex REST (@RestResource) and Apex SOAP web services (webservice keyword) for custom endpoints, Composite API to bundle calls, and Bulk API for high volume.

Q: How does the external app authenticate? A: Through a Connected App / External Client App using OAuth — JWT Bearer or Client Credentials for server-to-server, Web Server flow for user context. Salesforce then runs the operation under that user's permissions and sharing. Grant least privilege. (The old SOAP login() is retiring in Summer '27.)

Q: How do you stop a retried call from creating duplicates? A: Make the endpoint idempotent — expose upsert keyed on an External ID instead of insert, or dedupe on a business key. A repeated call then updates the same record rather than creating a second one.

Q: REST API vs Apex REST — when do you build a custom endpoint? A: Use the standard REST API when plain sObject CRUD is enough — no code. Build Apex REST when you need a custom request/response shape, multi-object or transactional logic, or pre/post processing the standard API cannot express.

Talking point to explain it to anyone: "It's a shop counter. Outside customers walk in to our store (Salesforce) and do business — we check who they are, serve the request, and hand back a receipt."


7. Key terms

REST API, SOAP API, Apex REST, @RestResource, webservice keyword, Composite API, Bulk API, Connected App, External Client App, OAuth, idempotency, least privilege, status codes - defined in Module 01 vocabulary and the README. Deeper dive in Module 04 - Inbound APIs.


Sources (Verified June 2026)


Next: 05-data-virtualization.md - reading and writing external data live, without copying it into Salesforce.

05 - Data Virtualization

One-liner: Salesforce reads external data live, on demand, and never stores a copy. Direction: Salesforce → External (outbound, read at view time). Timing: Synchronous, per view. Volume: One list/record at a time, but it can front a huge external store. Use when: You need to see external data inside Salesforce without copying it.

This is Module 02, the integration patterns. New to the vocabulary (sync/async, callout)? See Module 01. For the auth behind the callout, see Module 03.


1. The idea in plain English

Data Virtualization is a window, not a warehouse. Instead of trucking boxes of data into your house and stacking them in the garage, you cut a window in the wall and look at the warehouse next door. The goods stay where they are. You just see them when you glance through the glass.

In Salesforce terms: you define an External Object that looks and feels like a normal object (records, list views, detail pages, even some relationships), but the rows live in SAP, an Oracle archive, or another Salesforce org. When a user opens the list view, Salesforce makes a live callout to the source, fetches the rows for that screen, and shows them. Close the tab and nothing is stored. This is the opposite of Batch Data Synchronization, which copies data on a schedule.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
Data must stay in the system of record (compliance, size, licensing). You need it offline, in reports at scale, or in heavy automation → copy it via 03-batch-data-synchronization.md.
The dataset is huge or rarely viewed. Copying it is wasteful. You need fast, complex SOQL, roll-ups, or triggers on the data.
Users need a real-time view but Salesforce is not the owner. The source is slow or often down. Every view becomes a failed callout.
You want a record detail page that stitches Salesforce + external fields. You only need one value once → just do a 01-request-and-reply.md callout.

Real-world examples: show SAP invoices on the Account page, browse a billions-of-rows order archive that was offloaded from the production DB, surface inventory from an ERP, or read records from another Salesforce org without an ETL job.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant U as User participant SF as Salesforce<br/>(External Object __x) participant SC as Salesforce Connect<br/>(Adapter) participant EXT as External Store<br/>(SAP / Oracle / Org) U->>SF: Open list view or record (Invoice__x) SF->>SC: Need rows for this screen SC->>EXT: Live callout (OData query / REST) Note over SC,EXT: No data stored. Fetched at view time. EXT-->>SC: Rows for this page only SC-->>SF: Map fields to the External Object SF-->>U: Render records live

Walkthrough

  1. A user opens an External Object list view, report, or detail page.
  2. Salesforce hands the request to Salesforce Connect and the configured adapter.
  3. The adapter issues a live callout to the source (an OData query for the OData adapter, a REST call for cross-org/Apex).
  4. The source returns only the rows for that screen (paged).
  5. Salesforce maps the response onto the External Object's fields.
  6. The records render. Nothing is persisted. The next view triggers a fresh callout.

4. How it shows up in Salesforce (the tech)

External data is exposed through Salesforce Connect plus an External Data Source and its External Objects (API names end in __x, vs __c for custom objects). You pick an adapter based on the source.

Tool What it is Use it for
OData 2.0 / 4.0 / 4.01 adapter Salesforce Connect speaks the OData REST protocol to any OData-compliant endpoint. SAP, Microsoft, and middleware that publish an OData service.
Cross-Org adapter Connects to another Salesforce org via the Lightning Platform REST API. Org-to-org data sharing, point and click.
Apex Connector Framework Custom adapter you write in Apex by extending DataSource.Connection and DataSource.Provider. Any non-OData source (legacy DB, custom REST, web service).
External Object (__x) The metadata shell. Fields, page layouts, list views, search. The thing users actually see and click.

Shape of a custom adapter (the framework calls query when a user views records):

global class ArchiveProvider extends DataSource.Provider {
    override global List<DataSource.Capability> getCapabilities() {
        return new List<DataSource.Capability>{ DataSource.Capability.ROW_QUERY };
    }
    override global List<DataSource.AuthenticationCapability> getAuthenticationCapabilities() {
        return new List<DataSource.AuthenticationCapability>{
            DataSource.AuthenticationCapability.ANONYMOUS
        };
    }
    override global DataSource.Connection getConnection(DataSource.ConnectionParams p) {
        return new ArchiveConnection(); // extends DataSource.Connection, implements query()
    }
}

Auth: the OData and cross-org adapters authenticate with a Named Credential + External Credential, the same plumbing as any callout. See Module 03 - Named Credentials.


5. Design considerations and gotchas

Consideration Why it matters What to do
Every view is a callout A list view, report, or detail page each triggers a live request. Slow source = slow page. Keep the source fast. Page results. Don't put External Objects on hot, high-traffic screens.
Callout limits OData callouts are capped (about 10,000 per hour on Enterprise/Performance/Unlimited; higher on premium). Watch volume. Aggressive list views or reports burn the allowance.
No triggers on __x External Objects do not support standard Apex triggers, so you cannot react to a change in the source object directly. For change reactions, use the push pattern → 06-ui-update-based-on-data-changes.md.
Limited feature set No formula fields, no roll-up summaries. Reporting and relationships are limited (only indirect and external lookups). Confirm each feature before you design. Treat External Objects as a read-mostly view, not a full object.
Availability depends on the source If the external system is down, the records simply do not load. Plan for graceful failure. The source's uptime becomes your uptime.
OData requirement The OData adapter needs an OData 2.0 / 4.0 / 4.01 compliant endpoint. If the source is not OData, use the Apex Connector Framework instead.
Writes are limited Some adapters support writable External Objects, many do not. Verify create/update support per adapter before promising edits.

6. Interview Q&A

Q: What is the Data Virtualization pattern? A: Salesforce displays external data live without storing a copy. You define an External Object backed by Salesforce Connect, and Salesforce queries the source on demand when a user views a record or list. It is a window onto the data, not a warehouse copy.

Q: How do you implement it in Salesforce? A: Salesforce Connect with an External Data Source and External Objects (__x). Choose an adapter: the OData adapter for OData endpoints, the Cross-Org adapter for another Salesforce org, or a custom adapter via the Apex Connector Framework for anything else.

Q: When would you copy the data instead (Batch Sync) rather than virtualize it? A: When you need it offline, in large-scale reports, in heavy automation, or with fast complex SOQL and roll-ups. Virtualize when the data must stay in the source, is huge, or is rarely viewed. Copy when Salesforce needs to own and work it.

Q: What are the main limitations of External Objects? A: No standard triggers, no formula fields, no roll-up summaries, limited reporting, and only indirect or external lookup relationships. Each view is a callout subject to limits, and availability tracks the source system's uptime.

Q: The source is slow and the Account page now takes 8 seconds. What do you do? A: Don't virtualize on a hot page. Move the External Object off the critical path (a related list users open deliberately, or a separate tab), page the results, cache where allowed, or switch to a scheduled Batch Sync copy if the page must be fast.

Talking point to explain it to anyone: "It's a window into the warehouse next door. You see the goods live through the glass, but you never haul them into your own garage."


7. Key terms

Salesforce Connect, External Object (__x), External Data Source, OData, Cross-Org adapter, Apex Connector Framework, callout - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 06-ui-update-based-on-data-changes.md - when the UI must refresh the instant data changes.

06 - UI Update Based on Data Changes

One-liner: When data changes, an event is pushed so the Salesforce UI refreshes the instant it happens. Direction: Bidirectional (change inside or outside Salesforce → UI). Timing: Asynchronous push, near real-time. Volume: Stream of small events over an event bus. Use when: A dashboard or LWC must auto-refresh the moment a record changes, with no polling.

This is Module 02, the final integration pattern. New to the vocabulary (async, event bus, push)? See Module 01. For the auth behind external subscribers, see Module 03.


1. The idea in plain English

Old way: you keep refreshing your inbox to see if mail arrived. That is polling. Wasteful, slow, and you still miss things between refreshes.

New way: a notification pops the second the mail lands. You did nothing. The mail came to you.

That is this pattern. Instead of a component asking "anything new?" every few seconds, the data change publishes an event to a shared event bus, and any subscriber that cares gets pushed the news immediately. In Salesforce, a Lightning Web Component sitting in the browser subscribes to that stream and updates the screen the moment an event arrives. The change can originate inside Salesforce (a record edit) or outside it (an external system publishes an event), and the UI reacts either way.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
A screen must reflect a change as it happens (live dashboard, agent console). You need the answer right now in the same transaction → 01-request-and-reply.md.
You want to stop polling the server on a timer. You only need a one-off send with no listener → 02-fire-and-forget.md.
Many subscribers care about the same change (fan-out). You need guaranteed in-order, transactional delivery into a single system.
The trigger lives outside Salesforce and must reach the UI. The data lives elsewhere and you only need to view it → 05-data-virtualization.md.

Real-world examples: a support console that flashes when a case is reassigned, a sales dashboard that updates as opportunities close, an order tracker that moves when a warehouse system publishes a shipment event, a bid screen in an auction app.


3. How it works (flowchart)

flowchart LR A["Data changes<br/>record edit or external publish"] --> B["Event published<br/>CDC or Platform Event"] B --> C["Event Bus<br/>stores + sequences + replay IDs"] C --> D["Subscriber: LWC in browser<br/>via lightning/empApi over CometD"] C --> E["Subscriber: external app<br/>via Pub/Sub API over gRPC"] D --> F["UI updates instantly"] style A fill:#ff9900,color:#fff style B fill:#00A1E0,color:#fff style C fill:#9C27B0,color:#fff style D fill:#4CAF50,color:#fff style E fill:#607D8B,color:#fff style F fill:#4CAF50,color:#fff

Walkthrough

  1. A record changes in Salesforce, or an external system decides something happened.
  2. An event is published. Use Change Data Capture (CDC) for record changes, or a custom Platform Event for business signals.
  3. The event lands on the event bus, which stores and sequences it and tags each with a replay ID.
  4. Subscribers receive a push. Inside the Salesforce UI, an LWC subscribes via lightning/empApi over CometD. Outside Salesforce, services subscribe via the Pub/Sub API.
  5. The LWC handler updates component state and the screen refreshes with no user action.

4. How it shows up in Salesforce (the tech)

Two halves: publish the change as an event, then subscribe to it. The subscriber differs depending on whether it is the Salesforce UI or an external system.

Tool What it is Use it for
Change Data Capture (CDC) Salesforce auto-publishes a change event when a record is created, updated, deleted, or undeleted. "React when this record changes." No code to publish.
Platform Events Custom, schema-defined events you publish from Apex, Flow, or an API. Custom business signals, "OrderShipped," "BidPlaced."
Pub/Sub API The modern unified interface. gRPC + HTTP/2, payloads in Apache Avro. Subscribe to Platform Events, CDC, and monitoring events through one API. External subscribers and publishers. The preferred choice today.
lightning/empApi LWC module to subscribe inside the browser over a shared CometD connection. A component in the Salesforce UI that must live-update.
Streaming API (legacy) PushTopic and generic events over CometD. Salesforce no longer enhances these. Older integrations only. Prefer Pub/Sub API / Platform Events.

Minimal LWC subscriber (note empApi is desktop-only, API 44.0+, and works on the main window only):

import { subscribe, unsubscribe, onError } from 'lightning/empApi';

const CHANNEL = '/data/CaseChangeEvent'; // CDC channel for Case

connectedCallback() {
    subscribe(CHANNEL, -1, (event) => {
        // event.data.payload holds the change. Refresh the UI.
        this.refreshFromEvent(event);
    }).then((sub) => { this.subscription = sub; });

    onError((err) => console.error('Streaming error', err));
}

disconnectedCallback() {
    unsubscribe(this.subscription, () => {});
}

The -1 is a replay option: -1 = only new events from now, -2 = all events still in the retention window, or a specific replay ID to resume exactly where you left off after a disconnect.

Auth: external subscribers using the Pub/Sub API authenticate with OAuth (e.g. the JWT Bearer or Client Credentials flow). See Module 03. The in-browser empApi rides the logged-in user's session, so no extra auth.


5. Design considerations and gotchas

Consideration Why it matters What to do
It is async push, not request/reply The UI reacts after the fact. There is no return value to the caller. Use it to notify, not to fetch a value you need synchronously.
The client must subscribe No subscriber means the event is delivered to no one. Events are not stored forever. Subscribe on component load. Reconnect and replay on disconnect.
Retention / replay window Standard-volume events are retained about 24 hours; high-volume events and the Pub/Sub API stream about 72 hours. Persist the last replay ID and resume from it. Past the window, events are gone.
Delivery limits There are caps on event publishing and delivery (per the org's allocation). Don't fire an event per row in a tight loop. Design coarse-grained events.
empApi is UI-side only Desktop browsers, main window, API 44.0+. Not for server-to-server. Use the Pub/Sub API for external/back-end subscribers.
Pub/Sub API for external, empApi for internal They solve different ends of the same bus. External app or middleware → Pub/Sub API. Component in the Salesforce UI → lightning/empApi.
CDC object enablement CDC must be enabled per object, and orgs have a base allocation of selectable objects. Enable only the objects you need. Buy more allocation if required.
Ordering and duplicates Events can in rare cases be redelivered. Make handlers idempotent and use the replay ID / event UUID to dedupe.

6. Interview Q&A

Q: What is the UI Update Based on Data Changes pattern? A: When data changes, an event is pushed over an event bus and subscribers react in near real time, so the UI refreshes the instant the change happens instead of polling on a timer. The change can come from inside Salesforce (CDC) or outside it (a Platform Event published by another system).

Q: How does a Lightning Web Component get live updates? A: It imports the lightning/empApi module and calls subscribe on a streaming channel (a Platform Event or a CDC channel) over a shared CometD connection. When an event arrives, the callback fires and the component updates its state. It is desktop-only, API 44.0+, main window only.

Q: Pub/Sub API vs the legacy Streaming API. Why does the modern one win? A: Pub/Sub API is gRPC over HTTP/2 with Avro binary payloads and one unified interface to publish and subscribe across Platform Events, CDC, and monitoring events, with flow control and a 72-hour replay window. The legacy Streaming API (PushTopic, generic events over CometD) is no longer enhanced. Use Pub/Sub API for external clients, empApi inside the UI.

Q: What is a replay ID and why does it matter? A: Every event on the bus gets a replay ID marking its position in the stream. If a subscriber disconnects, it reconnects and asks to replay from the last ID it saw, so it catches the events it missed instead of losing them, as long as they are still inside the retention window (about 24 to 72 hours).

Q: CDC or Platform Events? When do you choose which? A: CDC when you want to react to record changes with no publishing code, it auto-fires on create/update/delete/undelete. Platform Events when you need a custom business signal with your own schema, published deliberately from Apex, Flow, or an external system.

Talking point to explain it to anyone: "Stop hitting refresh on your inbox. This is the notification that pops the second mail arrives. The change comes to you."


7. Key terms

Change Data Capture, Platform Event, event bus, Pub/Sub API, lightning/empApi, CometD, replay ID, retention window - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: back to the README.md for the full pattern map. From here, continue to Module 03 - Authentication (already built, the auth behind every callout and external subscriber) and then Module 04 to keep going.

Module 03 - Authentication (Slow Down Here)

Goal: Stop being confused by OAuth. Pick the right flow without thinking, and be able to explain any of them to anyone. API version: v66.0 (Spring '26). Login host: always your My Domain.

This module is a complete, interview-ready reference for Salesforce authentication. Every OAuth flow gets its own file with a plain-English explanation, a Mermaid sequence diagram, real requests/responses, security pitfalls, and interview Q&A. Start with 01-authentication-fundamentals.md — it defines the tokens, scopes, and endpoints that every other file builds on.


How to use this module

  1. Read 01-authentication-fundamentals.md once. It is the spine.
  2. Walk the flows in order (02 → 12), or jump to the one you need via the table below.
  3. Read the four ecosystem files (13 → 17) — these are the containers and surrounding plumbing interviewers love to probe.
  4. Use the comparison table and decision tree below as your pre-interview cram sheet, plus the one-pager in ../12-Cheatsheets/oauth-flows.md.

Map of this module

# File What it covers
01 authentication-fundamentals AuthN vs AuthZ, the 5 roles, the 3 tokens, scopes, endpoints, My Domain, glossary
02 web-server-flow Authorization Code + PKCE — the gold standard for user login
03 user-agent-flow Implicit-style browser flow (⚠️ legacy)
04 jwt-bearer-flow Certificate-signed JWT, server-to-server, CI/CD
05 client-credentials-flow Modern server-to-server default (run-as user + secret)
06 device-flow TVs, CLI, IoT — limited-input devices
07 username-password-flow ⛔ Deprecated/retiring — know it to migrate off it
08 refresh-token-flow Renew an expired access token silently
09 saml-bearer-assertion-flow App self-signs a SAML assertion for a token
10 saml-assertion-flow Reuse a web-SSO SAML response for API access
11 asset-token-flow Bind an IoT device/asset to an identity (token exchange)
12 authorization-code-and-credentials-flow Headless Identity — fully branded customer login
13 connected-apps-vs-external-client-apps The OAuth container — old vs next-gen
14 named-credentials-and-external-credentials Outbound auth from Salesforce (never hardcode secrets)
15 auth-providers Log in with Google/Microsoft/etc. into Salesforce
16 sso-saml-and-openid-connect Single Sign-On: SAML 2.0 vs OpenID Connect
17 session-security-and-token-management What happens after you get the token

The master comparison table (memorize this)

If you only revise one thing before the interview, revise this. Direction = who hosts the flow. User? = is a human interactively present. Refresh? = does it issue a refresh token.

Flow grant_type / response_type Client type User? Refresh? Use it when
Web Server (02) authorization_code Confidential (or public + PKCE) ✅ ✅ Web app logs a user in. The default.
User-Agent (03) response_type=token Public ✅ optional ⚠️ Legacy browser apps. Avoid — use Web Server + PKCE.
JWT Bearer (04) urn:...:jwt-bearer Confidential (cert) ❌ ❌ Server-to-server with a certificate. CI/CD (sf CLI).
Client Credentials (05) client_credentials Confidential (secret) ❌ ❌ ✅ Modern server-to-server default. Run-as integration user.
Device (06) device (after device_code) Public ✅ (2nd screen) ✅ Smart TV, CLI, IoT — no browser/keyboard.
Username-Password (07) password Confidential ✅ (raw creds) ❌ ⛔ Deprecated/retiring. Don't build on it.
Refresh Token (08) refresh_token Confidential/public ❌ (renews) reuses Get a new access token when the old one expires.
SAML Bearer (09) urn:...:saml2-bearer Confidential (cert) ❌ ❌ Server-to-server when you already run SAML.
SAML Assertion (10) assertion (SSO browser) n/a ✅ (via SSO) ❌ API access right after a federated web SSO login.
Asset Token (11) urn:...:token-exchange Device ❌ n/a Bind an IoT device/asset to a Salesforce identity.
Auth Code & Credentials (12) authorization_code (headless) Public + PKCE ✅ ✅ Fully branded, headless customer login (Experience Cloud).

Outbound (Salesforce → external) is a different question entirely: don't hand-code a flow, configure a Named Credential + External Credential.


The decision tree — "which flow do I use?"

flowchart TD Start([I need to authenticate]) --> Dir{Who calls whom?} Dir -->|Salesforce calls OUT to an external API| NC[Named Credential<br/>+ External Credential<br/>see file 14] Dir -->|External app calls INTO Salesforce| User{Is a human user<br/>interactively logging in?} User -->|No, system-to-system| Cred{How does the server<br/>prove its identity?} Cred -->|Shared secret + run-as user| CC[Client Credentials<br/>file 05 - MODERN DEFAULT] Cred -->|X.509 certificate / private key| JWT[JWT Bearer<br/>file 04] Cred -->|Existing SAML signing cert| SAMLB[SAML Bearer<br/>file 09] User -->|Yes, interactive user| Backend{App has a secure<br/>server backend?} Backend -->|Yes| WS[Web Server Flow<br/>file 02] Backend -->|No - mobile or SPA| PKCE[Web Server + PKCE<br/>file 02] User -->|Yes, but no browser/keyboard| DEV[Device Flow<br/>file 06] User -->|Yes, fully branded headless UI| HEAD[Auth Code & Credentials<br/>file 12] WS --> Renew[Access token expired?<br/>Refresh Token flow - file 08] PKCE --> Renew style WS fill:#4CAF50,color:#fff style PKCE fill:#4CAF50,color:#fff style CC fill:#4CAF50,color:#fff style JWT fill:#4CAF50,color:#fff style NC fill:#00A1E0,color:#fff style DEV fill:#ff9900,color:#fff style HEAD fill:#ff9900,color:#fff style SAMLB fill:#9C27B0,color:#fff style Renew fill:#607D8B,color:#fff

Two flows are missing on purpose. User-Agent (03) and Username-Password (07) are legacy/retiring. If a question pushes you toward them, the correct answer is "use Web Server + PKCE or Client Credentials instead, here's why."


What changed in 2025-2026 (sound current)

  • Username-Password flow is retiring (blocked by default for orgs created Summer '23+, full retirement targeted Winter '27). Migrate to Client Credentials or Web Server + PKCE.
  • External Client Apps replace Connected Apps — new Connected App creation is disabled by default starting Spring '26 (rollout from Winter '26). Build new integrations on ECAs.
  • Client Credentials is the recommended server-to-server flow, and PKCE is recommended for all interactive flows.
  • Device Flow was removed from the auto-installed Data Loader connected app (Sept 2, 2025).

Rapid-fire cross-flow interview questions

Q: A backend job needs to sync data nightly, no user. Which flow? → Client Credentials (05) if secret-based, or JWT Bearer (04) if certificate-based. Never Username-Password.

Q: Which flows return a refresh token? → User-context flows: Web Server, Device, Auth Code & Credentials (and User-Agent optionally). Machine flows (JWT Bearer, Client Credentials, SAML Bearer) do not — you just re-run them.

Q: Salesforce needs to call an external REST API. What's the right pattern? → Named Credential + External Credential (14). Never hardcode tokens in Apex.

Q: What makes PKCE necessary? → Public clients can't keep a secret, so an intercepted auth code could be redeemed by an attacker. PKCE binds the code to a one-time verifier. See 02.

Q: Connected App or External Client App for a new build in 2026? → External Client App — Connected App creation is being disabled by default. See 13.


Sources (Verified June 2026)

Every per-file Sources section links the specific official doc for that flow.

01 - Authentication Fundamentals (Read This First)

Goal: Build the mental model that makes every OAuth flow click. If you understand the actors, tokens, scopes, and endpoints here, the 11 flows are just different ways of getting the same access token. API version: v66.0 (Spring '26). Login host: always your My Domain (https://MyDomainName.my.salesforce.com).

This is the shared vocabulary for Module 03. Every flow file links back here instead of re-explaining tokens and scopes.


1. AuthN vs AuthZ (the distinction interviewers test first)

Two different questions, two different words people lazily merge into "auth":

Term Question it answers Salesforce example
Authentication (AuthN) Who are you? You log in to Salesforce with username + password + MFA.
Authorization (AuthZ) What are you allowed to do? A connected app is granted the api scope, so it can call the REST API but not manage users.

One-line answer for an interview: "Authentication proves identity; authorization grants permission. OAuth 2.0 is primarily an authorization framework. OpenID Connect is the thin authentication layer added on top of OAuth, and in Salesforce it shows up as the ID token and the openid scope."


2. The cast of characters (the 5 roles)

Every flow is a conversation between these roles. Learn the roles once and you can read any sequence diagram.

flowchart LR RO[Resource Owner<br/>the human user] -->|grants access| CLIENT CLIENT[Client<br/>your app] -->|asks for token| AS AS[Authorization Server<br/>Salesforce login / identity] -->|issues token| CLIENT CLIENT -->|calls API with token| RS[Resource Server<br/>Salesforce API] RS -->|returns data| CLIENT IDP[Identity Provider<br/>who vouches for the user] -.->|SSO| AS style RO fill:#4CAF50,color:#fff style CLIENT fill:#00A1E0,color:#fff style AS fill:#ff9900,color:#fff style RS fill:#9C27B0,color:#fff style IDP fill:#607D8B,color:#fff
Role OAuth name In Salesforce, this is...
The human Resource Owner The Salesforce user whose data is accessed.
The app Client Your web app, mobile app, backend service, or middleware. Registered as a Connected App or External Client App.
The bouncer Authorization Server Salesforce identity service at /services/oauth2/*. Issues tokens.
The vault Resource Server The Salesforce API (/services/data/...) that the token unlocks.
The voucher Identity Provider (IdP) Whoever authenticates the user — Salesforce itself, or an external IdP (Okta, Entra ID, Google) via SSO.

Note: In Salesforce the Authorization Server and Resource Server are the same platform, just different endpoints. That is why your access token works against the API immediately, with no extra exchange.


3. The three tokens (know these cold)

99% of "what does this flow return?" questions come down to these three strings.

Token What it is Format Typical lifetime Sent how
Access token The key that unlocks the API. Equivalent to a temporary Session ID. Opaque string (e.g. 00D...!AQ...) Until the session timeout (default 2 hours of inactivity; set by the org's session settings) Authorization: Bearer <token> header
Refresh token A long-lived token used to silently get a new access token when the old one expires. Opaque string Until revoked or it hits a Connected App expiration policy (can be indefinite) POST /token with grant_type=refresh_token
ID token An OpenID Connect JWT that describes the user (name, user id, org id). Proof of authentication. Signed JWT (3 base64 parts) Short-lived, validated immediately Returned only when openid scope is requested
sequenceDiagram participant App as Client App participant SF as Salesforce Note over App,SF: 1. Get tokens (via any flow) App->>SF: Authenticate SF-->>App: access_token (+ refresh_token, + id_token) Note over App,SF: 2. Use the API until token expires App->>SF: GET /services/data/v66.0/... (Bearer access_token) SF-->>App: 200 OK + data App->>SF: GET ... (Bearer access_token) SF-->>App: 401 INVALID_SESSION_ID (token expired) Note over App,SF: 3. Silently refresh (no user involved) App->>SF: POST /token (grant_type=refresh_token) SF-->>App: new access_token

Interview trap: "Does the JWT Bearer flow return a refresh token?" No. Certificate-based machine flows (JWT Bearer, Client Credentials) return an access token only — there is no user session to keep alive, so you just re-run the flow to get a fresh token. See 04-jwt-bearer-flow.md and 05-client-credentials-flow.md.


4. Scopes (what the token is allowed to touch)

A scope is a permission string you request when getting a token. Salesforce only grants scopes the Connected App / External Client App has been configured to allow.

Scope Grants access to Notes
api REST, SOAP, Bulk, and other data APIs The workhorse scope for integrations.
web Lets the access token be used in a browser (frontdoor.jsp) Often paired with api.
full Everything the user can do Does NOT include refresh_token — you must add that separately.
refresh_token / offline_access Issues a refresh token for long-lived access Two names, same effect. Required for "stay logged in."
openid Returns an ID token (OpenID Connect) Needed for SSO / login-with-Salesforce.
id Access to the Identity URL (user info endpoint) Returns profile details.
chatter_api Connect (Chatter) REST API only Narrow scope.
custom_permissions Returns the user's custom permissions in the response
wave_api CRM Analytics (Tableau/Wave) REST API
eclair_api Einstein/Analytics charting
lightning Lightning Experience access
content Files/Content REST API
pardot_api Account Engagement (Pardot) API

Least privilege rule: request only what you need. For a typical server integration, api refresh_token is enough. Asking for full on a backend job is a red flag in a security review.

Gotcha: Even when you request full, you still must explicitly request refresh_token (or offline_access) to get a refresh token. This catches people constantly.


5. The OAuth 2.0 endpoints (one base, five doors)

All endpoints live under your My Domain host. Replace MyDomainName with your org's My Domain. (You can still see login.salesforce.com in old docs, but My Domain is the correct, future-proof host and is mandatory for many flows.)

Base: https://MyDomainName.my.salesforce.com/services/oauth2/

Endpoint Path Purpose
Authorize /services/oauth2/authorize Where you send the user's browser to log in and consent. Start of interactive flows.
Token /services/oauth2/token Where the app exchanges a code/assertion/credentials for an access token. The workhorse.
Revoke /services/oauth2/revoke Invalidate an access or refresh token (logout / kill switch).
Introspect /services/oauth2/introspect Ask "is this token still valid?" and get its metadata (RFC 7662). Used by resource servers.
UserInfo /services/oauth2/userinfo OpenID Connect endpoint that returns the user's profile for a valid token.

Test vs prod: sandboxes use https://MyDomainName--SandboxName.sandbox.my.salesforce.com. The legacy test.salesforce.com host maps to sandboxes; login.salesforce.com to production. Prefer My Domain everywhere.


6. My Domain — why every flow says "use your My Domain"

My Domain is your org's unique login subdomain (e.g. acme.my.salesforce.com). Since Spring '23 it is mandatory for all production orgs. It matters for auth because:

  1. It is the issuer of your tokens — external IdPs and partners pin to it.
  2. Enhanced Domains changed the URL format; old hardcoded *.salesforce.com hostnames break. Always build URLs from My Domain.
  3. SSO, branded login pages, and many newer flows (Client Credentials, Auth Code & Credentials) require My Domain.
flowchart LR A[Old / generic<br/>login.salesforce.com] -->|migrate| B[My Domain<br/>acme.my.salesforce.com] B --> C[Branded login page] B --> D[SSO with external IdP] B --> E[Stable token issuer URL] style B fill:#00A1E0,color:#fff style A fill:#F44336,color:#fff

7. The container: Connected App vs External Client App

Every OAuth flow needs a registered app definition that holds the client id (a.k.a. consumer key), client secret (consumer secret), allowed scopes, callback URL, and which flows are enabled.

  • Connected App — the original container (2013+). Being phased out for new creation: Salesforce disabled creating new Connected Apps by default starting Winter '26 / Spring '26.
  • External Client App (ECA) — the next generation (GA 2024+). Separates developer OAuth config from admin policy, is built for 2GP packaging, and is the go-forward choice.

Full comparison, including which flows each supports, is in 13-connected-apps-vs-external-client-apps.md. For now: the app is the door; the flow is how you walk through it.


8. What changed in 2025-2026 (say this and you sound current)

Interviewers reward candidates who know the platform is moving. The three headlines:

Change What it means Status
Username-Password flow retiring The flow that passes raw username+password is being killed. Blocked by default for orgs created Summer '23+, full retirement targeted for Winter '27. Migrate to Client Credentials (server-to-server) or Web Server + PKCE (user login). See 07-username-password-flow.md.
External Client Apps replace Connected Apps New Connected App creation disabled by default (Winter '26 / Spring '26); ECAs are the modern container. Build new integrations on ECAs. See 13.
Client Credentials is the new server default The recommended passwordless server-to-server flow, replacing Username-Password. Run-as user + secret. See 05-client-credentials-flow.md.

9. grant_type cheat table (map flow → token request)

When you POST to /services/oauth2/token, the grant_type parameter tells Salesforce which flow you're running. Memorize this table and you can identify any flow from a raw HTTP request.

Flow grant_type (or response_type) Returns refresh token?
Web Server (Authorization Code) authorization_code Yes
Refresh Token refresh_token reuses it
JWT Bearer urn:ietf:params:oauth:grant-type:jwt-bearer No
SAML Bearer urn:ietf:params:oauth:grant-type:saml2-bearer No
SAML Assertion urn:oasis:names:tc:SAML:2.0:profiles:SSO:browser No
Client Credentials client_credentials No
Device device (after response_type=device_code) Yes
Username-Password (legacy) password No
User-Agent (legacy) response_type=token (browser) Optional
Asset Token urn:ietf:params:oauth:grant-type:token-exchange n/a (asset token)

10. Glossary (quick definitions)

  • Bearer token — any token where possession = access. Whoever holds it can use it, so it must be protected like a password.
  • Consumer Key / Client ID — public identifier of your app.
  • Consumer Secret / Client Secret — private password of your app. Confidential clients can keep it; public clients (mobile, SPA) cannot.
  • Confidential client — an app that can store a secret safely (a backend server).
  • Public client — an app that cannot keep a secret (mobile app, single-page app). Uses PKCE instead.
  • PKCE ("pixie") — Proof Key for Code Exchange. Adds a one-time code_verifier/code_challenge pair so an intercepted auth code is useless. See 02-web-server-flow.md.
  • Callback URL / Redirect URI — where Salesforce sends the browser back after login. Must match the app config exactly.
  • JWT — JSON Web Token. A signed, self-contained token: header.payload.signature, base64url-encoded.
  • Assertion — a signed statement (JWT or SAML) that the token endpoint trusts in place of interactive login.
  • Identity URL — a Salesforce URL unique to each user that returns profile info; delivered with the access token.
  • Named Principal vs Per-User — whether one shared identity or each user's own identity is used for an outbound callout. See 14-named-credentials-and-external-credentials.md.
  • frontdoor.jsp — Salesforce URL that turns a valid access token into a browser UI session (needs web scope).

Sources (Verified June 2026)


Next: 02-web-server-flow.md — the most common flow, used whenever a real user logs in to a web app.

02 - Web Server Flow (Authorization Code) + PKCE

One-liner: A real user clicks "Log in with Salesforce," approves access, and your backend server exchanges a short-lived code for tokens. Use when: A web app with a server backend acts on behalf of a logged-in user. Grant type: authorization_code · Status: ✅ Recommended (the gold standard for user login). Tokens returned: Access token + refresh token (+ ID token if openid).

New here? Read 01-authentication-fundamentals.md first for tokens, scopes, and endpoints.


1. The idea in plain English

Think of a hotel. You (the user) show your ID at the front desk (Salesforce login). The desk doesn't hand your passport to the bellhop — it gives the bellhop (your app) a key card (access token) that opens only certain doors (scopes), for a limited time.

The clever part: the front desk first gives a claim ticket (authorization code) to the user's browser, and your server quietly trades that ticket for the real key card over a back channel. The browser never sees the access token, and the server proves its identity with a secret. That two-step "code then token" exchange is what makes this the most secure interactive flow.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You have a server backend that can keep a client secret safe. Pure single-page app or mobile app with no backend → still use this flow but add PKCE and treat as a public client.
A human user logs in and the app acts as that user. No user involved (cron job, ETL) → use 05-client-credentials-flow.md or 04-jwt-bearer-flow.md.
You need long-lived access (refresh token) without re-prompting. Device with no browser/keyboard → use 06-device-flow.md.

Real-world examples: a customer portal built in Node/Java that reads the user's Salesforce records; a marketing tool where each user connects their own Salesforce; "Log in with Salesforce" on a third-party SaaS.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant U as User (Browser) participant App as Your Server App participant SF as Salesforce<br/>(/services/oauth2) U->>App: Click "Log in with Salesforce" App->>U: 302 Redirect to /authorize<br/>(client_id, redirect_uri, scope, state) U->>SF: GET /authorize SF->>U: Login page + consent screen U->>SF: Enter credentials + Approve SF->>U: 302 Redirect to redirect_uri?code=AUTH_CODE&state=... U->>App: Browser delivers AUTH_CODE Note over App,SF: Back channel (server-to-server, secret never leaves server) App->>SF: POST /token (grant_type=authorization_code,<br/>code, client_id, client_secret, redirect_uri) SF-->>App: access_token + refresh_token + instance_url (+ id_token) App->>SF: GET /services/data/v66.0/... (Bearer access_token) SF-->>App: 200 OK + data

Walkthrough

1-2. App sends the user's browser to the authorize endpoint with its client_id, the redirect_uri, requested scope, and a random state (CSRF guard). 3-5. Salesforce authenticates the user (password, MFA, or SSO) and shows a one-time consent screen. 6-7. Salesforce redirects the browser back to your redirect_uri with a short-lived authorization code (valid ~15 min, single use). 8. Your server (not the browser) POSTs the code plus its client_secret to the token endpoint. 9. Salesforce returns the access token, a refresh token, and instance_url. 10-11. Your app calls the API with Authorization: Bearer <access_token>.


4. PKCE — the mandatory upgrade for public clients

PKCE (Proof Key for Code Exchange, say "pixie", RFC 7636) defends against someone stealing the authorization code in transit. The app generates a random secret per login and proves it owns that secret when redeeming the code.

sequenceDiagram autonumber participant App as App participant SF as Salesforce App->>App: code_verifier = random(43-128 chars) App->>App: code_challenge = BASE64URL(SHA256(code_verifier)) App->>SF: /authorize?...&code_challenge=...&code_challenge_method=S256 SF-->>App: AUTH_CODE (Salesforce stored the challenge) App->>SF: POST /token (code + code_verifier) SF->>SF: SHA256(code_verifier) == stored challenge? SF-->>App: ✅ tokens (❌ if mismatch)

Why it matters: a stolen code is useless without the matching code_verifier, which never left the app. Salesforce recommends PKCE for all clients and it is effectively required for mobile/SPA. Add two params to the authorize request (code_challenge, code_challenge_method=S256) and one to the token request (code_verifier).


5. The actual requests & responses

Step 1 — send the browser here (authorize):

https://MyDomainName.my.salesforce.com/services/oauth2/authorize
  ?response_type=code
  &client_id=3MVG9...CONSUMER_KEY
  &redirect_uri=https://app.example.com/oauth/callback
  &scope=api%20refresh_token%20openid
  &state=xyz123
  &code_challenge=Psd... (PKCE)
  &code_challenge_method=S256

Step 2 — Salesforce redirects back:

https://app.example.com/oauth/callback?code=aPrx...AUTH_CODE&state=xyz123

Step 3 — exchange the code (token), server-side:

curl https://MyDomainName.my.salesforce.com/services/oauth2/token \
  -d grant_type=authorization_code \
  -d code=aPrx...AUTH_CODE \
  -d client_id=3MVG9...CONSUMER_KEY \
  -d client_secret=ABCD...CONSUMER_SECRET \
  -d redirect_uri=https://app.example.com/oauth/callback \
  -d code_verifier=THE_ORIGINAL_RANDOM_STRING

Step 4 — the token response:

{
  "access_token": "00D5g000004...!AQEAQ...",
  "refresh_token": "5Aep861...l4Lo",
  "signature": "k0r...=",
  "scope": "api refresh_token openid",
  "id_token": "eyJraWQiOiI...",
  "instance_url": "https://MyDomainName.my.salesforce.com",
  "id": "https://login.salesforce.com/id/00D.../005...",
  "token_type": "Bearer",
  "issued_at": "1718700000000"
}

Connected App / ECA setup checklist

  1. Create a Connected App (or External Client App — preferred). Enable OAuth Settings.
  2. Set the Callback URL to exactly your redirect_uri.
  3. Select scopes (e.g. Access and manage your data (api), Perform requests at any time (refresh_token, offline_access)).
  4. Keep "Require Proof Key for Code Exchange (PKCE)" checked.
  5. Copy the Consumer Key (client_id) and Consumer Secret (client_secret).
  6. Optionally set IP relaxation, refresh-token policy, and permitted users.

Salesforce as the client (outbound): when Salesforce itself needs to call an external API on behalf of each user, you don't hand-code this flow — you configure a Named Credential with an External Credential of type "OAuth 2.0 — Authorization Code" (Browser Flow). Salesforce runs steps 1-9 for you. See 14-named-credentials-and-external-credentials.md.


6. Security pitfalls & gotchas

Pitfall Why it bites Fix
Skipping state CSRF: attacker injects their own code. Generate a random state, store it in the session, verify on callback.
No PKCE on mobile/SPA Public clients can't hide a secret; codes can be intercepted. Always send code_challenge / code_verifier.
Redeeming the code in the browser Exposes the client secret and tokens to JavaScript. Exchange the code on the server only.
Loose redirect_uri Open-redirect token theft. Register the exact callback; Salesforce matches it strictly.
Requesting full "to be safe" Over-privileged token. Request least privilege (api refresh_token).
Storing tokens in localStorage XSS steals them. Store server-side or in httpOnly cookies.
Forgetting refresh_token scope Access token dies in ~2h, user re-prompted. Add refresh_token/offline_access scope.

7. Interview Q&A

Q: Walk me through the Authorization Code flow. A: App redirects the user's browser to /authorize; user logs in and consents; Salesforce returns a short-lived auth code to the registered callback; the app's server exchanges that code plus its client secret at /token for an access token and refresh token; the app calls the API with the bearer token. The browser never sees the access token.

Q: Why two steps — code then token? Why not return the token directly? A: Returning the token directly (the old User-Agent / implicit flow) exposes it in the browser URL/history where it can leak. The code-then-token split keeps the access token on a secure back channel, authenticated by the client secret.

Q: What is PKCE and what attack does it stop? A: Proof Key for Code Exchange. The app sends a hashed code_challenge up front and the raw code_verifier at redemption. It stops authorization-code interception — a stolen code is useless without the verifier. Required for public clients, recommended for all.

Q: Difference between this and Client Credentials / JWT Bearer? A: Web Server flow is user-context (acts as the logged-in user, returns a refresh token). Client Credentials and JWT Bearer are system-context (no user, no refresh token), for backend integrations.

Q: The access token expired mid-session. What happens? A: API returns 401 INVALID_SESSION_ID. The app silently calls /token with grant_type=refresh_token to get a new access token — no user interaction. See 08-refresh-token-flow.md.

Talking point to explain it to anyone: "It's a coat check. You show ID once, get a claim ticket, and your app swaps the ticket for a key that opens just the doors it needs, for a while."


8. Key terms

authorization_code · state · code_challenge / code_verifier · redirect_uri · confidential vs public client — all defined in 01-authentication-fundamentals.md.


Sources (Verified June 2026)


Next: 03-user-agent-flow.md — the older browser-only flow this one replaced.

03 - User-Agent Flow (Browser Implicit-Style)

One-liner: A browser-based app sends the user to Salesforce to log in, and the access token comes straight back in the URL fragment (#access_token=...) for the browser to read. Use when: (Historically) a mobile, desktop, or JavaScript app with no server backend to keep a secret. Today, prefer the Web Server flow + PKCE instead. Grant type: response_type=token (OAuth 2.0 implicit grant) · Status: ⚠️ Legacy — Salesforce recommends 02-web-server-flow.md + PKCE instead. Tokens returned: Access token (+ optional refresh token) delivered in the URL fragment. No client secret used (public client).

New here? Read 01-authentication-fundamentals.md first for tokens, scopes, and endpoints.


1. The idea in plain English

Imagine a valet stand that hands the car key directly to whoever is standing at the booth. The user walks up (their browser), proves who they are, and Salesforce tosses the key (access token) straight back onto the counter where the browser can grab it. There is no back office, no second handshake, no secret password the app whispers to prove it is trustworthy.

That speed is also the weakness. Because the token lands in the browser's address bar (after the #), it can leak into browser history, server logs, and Referer headers. The newer Web Server flow fixes this by handing over a one-time claim ticket (auth code) instead and swapping it for the real token on a private back channel. That is why Salesforce now steers everyone to Web Server + PKCE.


2. When to use it (and when not)

✅ Historically used when ❌ Avoid / use something else
A public client (mobile, desktop, SPA) had no backend to hold a secret. You have any server at all → use 02-web-server-flow.md.
You needed the token inside the browser/device quickly. A modern mobile or single-page app → use Web Server flow + PKCE (public-client mode).
Quick demos and legacy integrations already built on it. No browser/keyboard (smart TV, CLI) → use 06-device-flow.md.

Reality check: Salesforce Mobile SDK used this flow through version 10.2, then switched its default to the Web Server flow with PKCE in Mobile SDK 11.0. Salesforce explicitly calls the user-agent flow insecure and recommends blocking it for new integrations. Treat any new request to use it as a red flag in a design review.

Real-world examples (legacy): an old single-page dashboard that read Salesforce data directly from JavaScript; an early native mobile app before PKCE existed; a quick internal tool that grabbed a token in the browser.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant U as User Browser participant SF as Salesforce<br/>/services/oauth2 participant API as Salesforce API U->>SF: GET /authorize?response_type=token<br/>client_id, redirect_uri, scope, state SF->>U: Login page + consent screen U->>SF: Enter credentials + Approve SF->>U: 302 Redirect to redirect_uri#access_token=...&state=... Note over U: Token sits in the URL fragment.<br/>JavaScript reads it after the hash. U->>API: GET /services/data/v66.0/... Bearer access_token API-->>U: 200 OK + data

Walkthrough

  1. The app sends the user's browser to the authorize endpoint with response_type=token, its client_id, the redirect_uri, requested scope, and a random state (CSRF guard). 2-3. Salesforce authenticates the user (password, MFA, or SSO) and shows the consent screen.
  2. Salesforce redirects the browser back to the redirect_uri with the access token in the URL fragment (after #), not as a query parameter. The fragment is never sent to a server, so it stays in the browser.
  3. The app's JavaScript reads the token from window.location.hash. Best practice: immediately call window.location.replace() to strip the token out of browser history. 6-7. The app calls the API directly with Authorization: Bearer <access_token>.

Why the fragment (#) and not a query (?): the part of a URL after # is never transmitted to the web server and is omitted from Referer headers. Salesforce deliberately uses the fragment so the token is not leaked to your callback server or to downstream sites. It still ends up exposed to the browser and any script on the page, which is the core security problem.


4. The actual requests & responses

Step 1 — send the browser here (authorize):

https://MyDomainName.my.salesforce.com/services/oauth2/authorize
  ?response_type=token
  &client_id=3MVG9...CONSUMER_KEY
  &redirect_uri=https://app.example.com/oauth/callback
  &scope=api%20web
  &state=xyz123

Step 2 — Salesforce redirects back with the token in the fragment:

https://app.example.com/oauth/callback#access_token=00D5g000004...!AQEAQ...
  &instance_url=https%3A%2F%2FMyDomainName.my.salesforce.com
  &id=https%3A%2F%2Flogin.salesforce.com%2Fid%2F00D...%2F005...
  &issued_at=1718700000000
  &signature=k0r...%3D
  &scope=api+web
  &token_type=Bearer
  &state=xyz123

There is no curl token exchange step here. Unlike the Web Server flow, the app does not POST to /services/oauth2/token. The token is already in the redirect. The browser parses the fragment client-side:

// Read the access token from the URL fragment
const params = new URLSearchParams(window.location.hash.substring(1));
const accessToken = params.get('access_token');
const instanceUrl = params.get('instance_url');

// Immediately remove the token from history (recommended by Salesforce)
window.location.replace('/app-home');

Connected App / ECA setup checklist

  1. Create a Connected App (or External Client App). Enable OAuth Settings.
  2. Set the Callback URL to exactly your redirect_uri.
  3. Select scopes. For the user-agent flow you typically need api and web. Add refresh_token / offline_access only if you intend to receive a refresh token (and accept that storing it in a browser is risky).
  4. Because this is a public client, the Consumer Secret is not sent. Do not embed it in client code.
  5. Strongly consider enabling "Require Proof Key for Code Exchange (PKCE)" and migrating to the Web Server flow instead.

Hybrid alternative: Salesforce also documents a Hybrid User-Agent Token flow, which starts like the user-agent flow but returns an authorization code you can exchange for a refresh token on a back channel. It exists specifically to give browser apps a safer path to long-lived access. If you are stuck on a browser-only architecture, evaluate it before the raw user-agent flow.


5. Security pitfalls & gotchas

Pitfall Why it bites Fix
Token in the URL fragment Leaks into browser history, bookmarks, and shoulder-surfing. Call window.location.replace() immediately; better, move to Web Server flow + PKCE.
No back channel The token is exposed to every script on the page; XSS = full token theft. Avoid this flow for sensitive data; harden against XSS; use httpOnly server-side sessions instead.
Skipping state CSRF: an attacker can splice in their own response. Generate a random state, store it, verify it on return.
Embedding the client secret Public clients cannot hide a secret; it would be extractable from the binary or JS. The flow deliberately omits the secret. Never ship one client-side.
Requesting full Over-privileged token exposed in the browser. Request least privilege (api web).
Storing the token in localStorage Persists the bearer token where XSS can read it. Keep it in memory only, or switch to a server-backed flow.

6. Interview Q&A

Q: What is the User-Agent flow and how does it differ from the Web Server flow? A: It is the OAuth 2.0 implicit grant (response_type=token). Salesforce returns the access token directly in the redirect URL fragment to the browser. The Web Server flow instead returns a one-time authorization code that the server exchanges for tokens on a back channel. User-Agent skips that second step, so it is simpler but less secure.

Q: Why is the token returned in the fragment (#) instead of a query string (?)? A: The fragment is never sent to the server and is excluded from Referer headers, so it does not leak to your callback host or downstream sites. It is still visible to the browser and page scripts, which is why the flow is discouraged.

Q: Why does Salesforce now discourage this flow? A: The token is exposed in the browser URL, history, and to any script on the page. There is no back channel and no client secret, so a stolen token is immediately usable. Salesforce recommends Web Server flow + PKCE, and Mobile SDK switched its default to that in version 11.0.

Q: Does the User-Agent flow use a client secret? A: No. It is a public client flow. Client executables and browser code cannot keep a secret safely, so the flow is designed without one. Anyone shipping a secret in client code has a vulnerability.

Q: A mobile app needs to log a user in today. Should it use the User-Agent flow? A: No. Use the Web Server flow in public-client mode with PKCE. It gives the same "no secret" benefit but keeps the token off the URL and protects the code exchange with a one-time verifier.

Talking point to explain it to anyone: "It is like a valet who hands the car key straight to whoever is at the booth. Fast, but anyone watching the counter can grab the key. The newer flow gives you a claim ticket instead and fetches the key in the back office."


7. Key terms

response_type=token · implicit grant · URL fragment · public client · state · PKCE — all defined in 01-authentication-fundamentals.md.


Sources (Verified June 2026)


Next: 04-jwt-bearer-flow.md — the certificate-signed server-to-server flow that needs no user and no password.

04 - JWT Bearer Token Flow

One-liner: A backend app proves its identity with a private key (no password, no browser) by signing a short-lived JWT that Salesforce verifies against an uploaded certificate. Use when: Server-to-server / CI-CD where a trusted backend acts as a pre-authorized user with zero human interaction. Grant type: urn:ietf:params:oauth:grant-type:jwt-bearer · Status: ✅ Recommended (the classic machine flow; great when you can manage a key pair). Tokens returned: Access token only. No refresh token (you just re-sign a new JWT).

New here? Read 01-authentication-fundamentals.md first for tokens, scopes, and endpoints.


1. The idea in plain English

Think of a wax seal on a letter. Long ago, a noble pressed a signet ring into hot wax. Anyone holding the matching seal-stamp on file could look at the wax and say "yes, this really came from that noble." Nobody had to mail the ring itself.

Here your app is the noble. The private key is the signet ring. The X.509 certificate uploaded to the Connected App is the seal-stamp Salesforce keeps on file. Each time the app wants a token, it writes a tiny note (the JWT) that says "I am app X, acting as user Y, and this note is valid for the next 3 minutes," then presses its private-key ring into it. Salesforce checks the seal against the certificate, sees the user was already pre-approved, and hands back an access token. The private key never leaves your server. There is no password to steal and no browser to redirect.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
A backend service or daemon needs API access with no user present. A real user logs in interactively → use 02-web-server-flow.md.
CI/CD pipelines authorize an org headlessly (the Salesforce CLI uses this). You want the integration to run as a dedicated service identity, not a named human → consider 05-client-credentials-flow.md.
You can securely store a private key and rotate certificates. The client cannot protect a key file (browser, SPA) → public-client flows with PKCE.
You must impersonate a specific Salesforce user (the sub) who is pre-authorized. A device with no key management → use 06-device-flow.md.

Real-world examples: a nightly Java/Node ETL job syncing an ERP into Salesforce; a Jenkins/GitHub Actions pipeline running sf project deploy; a middleware tier that acts as one fixed integration user across all calls.

Interview trap: "Does JWT Bearer return a refresh token?" No. There is no user session to keep alive, so when the access token expires you simply sign a fresh JWT and ask again. Same answer for Client Credentials.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant App as Backend App<br/>(holds private key) participant SF as Salesforce<br/>(/services/oauth2/token) participant API as Salesforce API<br/>(/services/data) Note over App: Build JWT claims:<br/>iss, sub, aud, exp App->>App: Sign JWT with PRIVATE KEY (RS256) App->>SF: POST /token<br/>grant_type=jwt-bearer & assertion=JWT Note over SF: Verify signature with the<br/>uploaded X.509 certificate Note over SF: Check sub user is pre-authorized<br/>and exp is in the future SF-->>App: access_token + instance_url (NO refresh_token) App->>API: GET /services/data/v66.0/... (Bearer access_token) API-->>App: 200 OK + data

Walkthrough

1-2. The app assembles the JWT claims and signs the token with its private key using RS256. 3. The app POSTs to the token endpoint with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer and the JWT in the assertion parameter. 4-5. Salesforce verifies the signature against the X.509 certificate stored on the Connected App / External Client App, confirms the sub user is pre-authorized, and checks that exp is still in the future. 6. Salesforce returns an access token and instance_url. No refresh token is issued. 7-8. The app calls the API with Authorization: Bearer <access_token>.


4. The JWT — header, claims, signature

A JWT is three base64url parts joined by dots: header.payload.signature.

Header (algorithm is fixed to RS256):

{ "alg": "RS256" }

Claims (payload) — the four that matter:

Claim Meaning What to put
iss Issuer Your app's Consumer Key / client_id (the 3MVG9... value).
sub Subject The run-as username the token acts as (e.g. integration@acme.com).
aud Audience The login host: https://login.salesforce.com (prod), https://test.salesforce.com (sandbox), or your My Domain URL.
exp Expiration A Unix timestamp a few minutes ahead (3-5 min). Salesforce rejects long-lived JWTs.

Sample claims set:

{
  "iss": "3MVG9...CONSUMER_KEY",
  "sub": "integration@acme.com",
  "aud": "https://login.salesforce.com",
  "exp": 1718700300
}

Notes: You cannot request scopes in this flow. Scopes come from the app's Permitted Users policy and the org's API Access Control settings. The jti (JWT ID) claim is optional, but if you include it Salesforce checks it has not been seen before, which blocks replay attacks. Keep exp short (the platform's own External Credentials default is 2 minutes, and signed JWTs are typically set to 3-5 minutes).


5. The actual requests & responses

Step 1 — generate a key pair and self-signed certificate (OpenSSL):

# 2048-bit RSA private key (keep this secret, never commit it)
openssl genrsa -out server.key 2048

# Certificate signing request
openssl req -new -key server.key -out server.csr

# Self-signed X.509 certificate, valid 1 year (upload server.crt to Salesforce)
openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt

server.key = the private key your app signs with. server.crt = the public certificate (must be ≤ 4 KB) you upload to the Connected App.

Step 2 — Connected App / ECA setup checklist:

  1. Create a Connected App (or External Client App — preferred). Enable OAuth Settings.
  2. Check "Use digital signatures" and upload server.crt.
  3. Select OAuth scopes (e.g. api, refresh_token is irrelevant here). A callback URL is required by the form but unused by this flow.
  4. Copy the Consumer Key (this becomes iss / client_id).
  5. Under the app's policies, set Permitted Users = "Admin approved users are pre-authorized" and assign the run-as user via a profile or permission set. This is what makes the sub user allowed.

Step 3 — exchange the signed JWT for a token:

curl https://MyDomainName.my.salesforce.com/services/oauth2/token \
  -d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
  -d assertion=eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiIzTVZHOS4uLiJ9.SIGNATURE

Step 4 — the token response (no refresh token):

{
  "access_token": "00D5g000004...!AQEAQ...",
  "scope": "api",
  "instance_url": "https://MyDomainName.my.salesforce.com",
  "id": "https://login.salesforce.com/id/00D.../005...",
  "token_type": "Bearer"
}

Step 5 — the Salesforce CLI does all of this for you (the CLI's org login jwt is the JWT Bearer flow):

sf org login jwt \
  --client-id 3MVG9...CONSUMER_KEY \
  --jwt-key-file ./server.key \
  --username integration@acme.com \
  --instance-url https://MyDomainName.my.salesforce.com \
  --alias ci-org --set-default

--client-id (the consumer key), --jwt-key-file (your server.key), and --username (the run-as user) are required. Use --instance-url for My Domain or a sandbox host. This is the standard way to authorize an org in CI/CD where no browser is available.

Salesforce as the client (outbound): when Salesforce itself calls an external system using a JWT, you don't hand-sign tokens — you configure a Named Credential with an External Credential of type "OAuth 2.0 — JWT Bearer" and edit the JWT claims (iss, sub, aud, exp) in the UI. See 14-named-credentials-and-external-credentials.md.


6. Security pitfalls & gotchas

Pitfall Why it bites Fix
Committing server.key to git The private key is the credential. Anyone with it can mint tokens as the sub user. Store in a secrets manager / CI secret; never in source control.
sub user not pre-authorized Token request fails with invalid_grant: user hasn't approved this consumer. Set Permitted Users = admin pre-authorized and assign the user via profile/permission set.
exp too far in the future Salesforce rejects JWTs whose expiry is not a short window ahead. Set exp to now + 3-5 min (UTC seconds).
Wrong aud for the environment Sandbox JWTs signed with login.salesforce.com fail. Use test.salesforce.com (or the sandbox My Domain) for sandboxes, login.salesforce.com for prod.
Over-privileged run-as user The token inherits all the sub user's permissions. Use a dedicated integration user scoped by permission sets (least privilege).
Expecting a refresh token There isn't one; code that waits for refresh_token hangs. On 401, just sign a new JWT and re-request.
Reusing the same JWT repeatedly Replay risk if intercepted. Optionally add a unique jti per request; always use HTTPS.

7. Interview Q&A

Q: Walk me through the JWT Bearer flow. A: The app builds a JWT with claims iss (client_id), sub (run-as username), aud (login host), and a short exp, signs it with its private key (RS256), and POSTs it to /services/oauth2/token as the assertion with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer. Salesforce verifies the signature against the uploaded X.509 certificate, confirms the sub user is pre-authorized, and returns an access token only.

Q: Why no refresh token? A: There is no interactive user session to keep alive. The app already holds the private key, so getting a fresh access token is as cheap as signing a new JWT. Refresh tokens exist to avoid re-prompting a human — irrelevant here.

Q: How does Salesforce trust the JWT without a password? A: Asymmetric crypto. The app signs with the private key; Salesforce holds only the matching public certificate. A valid signature proves the holder of the private key produced it, and the sub user was pre-approved on the Connected App.

Q: What does each claim do, and what is aud? A: iss = your Consumer Key, sub = the user to run as, exp = short expiry, and aud = the intended recipient, i.e. the Salesforce login host (login/test/My Domain). A mismatched aud is the most common reason the flow fails between sandbox and prod.

Q: JWT Bearer vs Client Credentials — when do you pick which? A: Both are passwordless server-to-server with no refresh token. JWT Bearer uses a key pair and impersonates a named sub user (and powers the CLI). Client Credentials uses a client secret and a configured run-as user with no JWT to build. If you already manage certificates or use the CLI, JWT; if you want the simplest secret-based setup, Client Credentials. See 05-client-credentials-flow.md.

Q: How does sf org login jwt relate to this flow? A: It is this flow. You pass --client-id, --jwt-key-file, and --username; the CLI signs the JWT and exchanges it. It is the standard headless auth for CI/CD.

Talking point to explain it to anyone: "It's a wax seal. Your server presses its private-key ring into a short note, Salesforce checks the seal against the stamp it has on file, and lets it in. No password ever travels."


8. Key terms

urn:ietf:params:oauth:grant-type:jwt-bearer · JWT (header.payload.signature) · assertion · iss / sub / aud / exp · X.509 certificate · private key · pre-authorized user · confidential client — all defined in 01-authentication-fundamentals.md.


Sources (Verified June 2026)


Next: 05-client-credentials-flow.md — the secret-based server-to-server flow that runs as a fixed integration user.

05 - Client Credentials Flow

One-liner: A backend app trades only its client id + secret for an access token that runs as a pre-configured integration user — no JWT, no user login. Use when: Server-to-server integration with no user context that always runs as the same fixed identity. Grant type: client_credentials · Status: ✅ Recommended (the modern replacement for the retiring Username-Password flow). Tokens returned: Access token only. No refresh token, no user login.

New here? Read 01-authentication-fundamentals.md first for tokens, scopes, and endpoints.


1. The idea in plain English

Think of a vending-machine service key. A maintenance company isn't a person — it's a company. But the machine still needs to know "who" opened it. So the building gives the company one master key tied to a single named contractor account on file. Any technician using that key acts as that one contractor, with exactly the access that contractor was granted. No personal logins, no passwords passed around — just the key, and a fixed identity behind it.

Here your app presents its client id + client secret (the master key). Salesforce maps the request to the run-as integration user you configured on the Connected App (the contractor on file) and hands back an access token that has precisely that user's permissions. There is no human, no browser, and no JWT to build — it is the simplest passwordless server-to-server flow Salesforce offers.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
A headless backend (backup tool, analytics job, middleware) calls Salesforce with no user present. A real user logs in and the app acts as them → 02-web-server-flow.md.
The integration always runs as one fixed integration user. You need the token to act as a specific named user via a signed JWT → 04-jwt-bearer-flow.md.
You are replacing the legacy Username-Password flow. A device with no browser/keyboard → 06-device-flow.md.
The client can keep a secret safely. A public client (mobile/SPA) that can't hide a secret.

Real-world examples: an ERP nightly sync running as a dedicated "ERP Integration" user; a data-warehouse extractor; a monitoring service polling records; any system-to-system job that the org wants attributed to a single, auditable service identity.

Why this is the new default: it needs no password and no key pair — just a secret and a configured run-as user. Salesforce positions it as the go-to server-to-server flow as the Username-Password flow retires (blocked by default for newer orgs, full retirement targeted Winter '27). See 07-username-password-flow.md.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant App as Backend App<br/>(client id + secret) participant SF as Salesforce<br/>(/services/oauth2/token) participant API as Salesforce API<br/>(/services/data) App->>SF: POST /token<br/>grant_type=client_credentials<br/>(client_id + client_secret) Note over SF: Validate secret.<br/>Look up the configured RUN-AS user. SF-->>App: access_token + instance_url (NO refresh_token) App->>API: GET /services/data/v66.0/... (Bearer access_token) Note over API: Request executes with the<br/>run-as user's permissions API-->>App: 200 OK + data

Walkthrough

  1. The app POSTs to the token endpoint (on your My Domain host) with grant_type=client_credentials plus its client_id and client_secret.
  2. Salesforce validates the secret and resolves the run-as integration user configured on the app.
  3. Salesforce returns an access token and instance_url. No refresh token. 4-5. The app calls the API; every call executes with the run-as user's profile, permission sets, and data access.

4. The actual requests & responses

My Domain is mandatory. login.salesforce.com and test.salesforce.com are not supported for this flow. The token endpoint must be https://MyDomainName.my.salesforce.com/services/oauth2/token (or your Experience Cloud domain).

Option A — credentials in the POST body:

curl https://MyDomainName.my.salesforce.com/services/oauth2/token \
  -d grant_type=client_credentials \
  -d client_id=3MVG9...CONSUMER_KEY \
  -d client_secret=ABCD...CONSUMER_SECRET

Option B — credentials as HTTP Basic auth (base64 of client_id:client_secret):

curl https://MyDomainName.my.salesforce.com/services/oauth2/token \
  -H "Authorization: Basic MzMVG9...base64(client_id:client_secret)" \
  -d grant_type=client_credentials

The token response (no refresh token):

{
  "access_token": "00D5g000004...!AQEAQ...",
  "signature": "k0r...=",
  "scope": "api",
  "instance_url": "https://MyDomainName.my.salesforce.com",
  "id": "https://login.salesforce.com/id/00D.../005...",
  "token_type": "Bearer",
  "issued_at": "1718700000000"
}

Connected App / ECA setup checklist

  1. Create a Connected App (or External Client App — preferred) and enable OAuth Settings.
  2. Check "Enable Client Credentials Flow."
  3. Select the scopes you need — for pure API access, api is enough (least privilege).
  4. Copy the Consumer Key and Consumer Secret (via Manage Consumer Details).
  5. Open the app's Manage view → Edit Policies. Set Permitted Users = Admin Pre-Approved and choose the "Run-As User for the client credentials flow."
  6. Assign the run-as user (via profile or permission set) so they are permitted to use the app.

The recommended run-as identity — the modern best practice from Salesforce:

  • Create a dedicated user on the Salesforce Integration user license with the Minimum Access – API Only Integrations profile (enables API, restricts to API only).
  • Grant exactly the data/operations needed via a permission set using the Salesforce API Integration permission set license (PSL).
  • One app + one integration user per integration, so access is scoped, auditable, and the secret can be rotated affecting only that one integration.

It acts as the integration user, not the caller. Every action runs as the configured run-as user. If you pick a System Administrator, the token gets admin power — so scope it down hard.


5. Security pitfalls & gotchas

Pitfall Why it bites Fix
Run-as user is an admin The token inherits all that user's permissions; a leaked secret = full org access. Use a dedicated integration user scoped by permission sets (least privilege).
Hitting login.salesforce.com This flow rejects the generic login host. Always use the My Domain token endpoint.
Treating the secret casually Anyone with client_id + client_secret can mint a token — it's a password equivalent. Store in a secrets manager; rotate periodically and immediately if compromised.
Forgetting to enable the flow / set run-as Token request fails; the flow is off by default for security. Enable Client Credentials Flow and set the run-as user in policies (required even for AppExchange apps).
Expecting a refresh token There is none; refresh logic breaks. On 401, just re-run the flow to get a new access token.
Sharing one app across integrations A single leak forces rotation that breaks every integration at once. One app + one integration user per integration for isolation and auditability.

6. Interview Q&A

Q: What is the Client Credentials flow and when is it the right choice? A: A server-to-server OAuth flow where the app exchanges just its client id and secret for an access token, with no user interaction and no refresh token. It's the right choice for headless integrations that always run as the same fixed identity — backups, analytics, ETL — and it's the modern replacement for the Username-Password flow.

Q: Whose permissions does the token have? A: The run-as integration user configured on the Connected App's policies — not the caller. Best practice is a dedicated user on the Integration user license with the Minimum Access – API Only Integrations profile, scoped by a permission set with the API Integration PSL.

Q: Why does Salesforce force you to pick a run-as user for a "userless" flow? A: Everything in Salesforce executes in a user context for sharing, FLS, and auditing. Even a system integration must map to a user so its actions are attributable and its data access is governed.

Q: Client Credentials vs JWT Bearer — compare them. A: Both are passwordless, server-to-server, and return no refresh token. Client Credentials uses a client secret and a configured run-as user — simplest setup, no JWT. JWT Bearer uses a private key and names the user in the JWT's sub, and it powers the Salesforce CLI. Pick Client Credentials for the simplest secret-based service identity; JWT when you already manage certificates. See 04-jwt-bearer-flow.md.

Q: Why is My Domain required here? A: The flow is explicitly not supported on login.salesforce.com / test.salesforce.com. Salesforce ties the integration to your org's branded, stable issuer host, so you must call the My Domain token endpoint.

Q: How do you handle token expiry? A: There is no refresh token, so when a call returns 401, you simply re-run the Client Credentials request with the same id and secret to get a fresh access token. If using JWT-based access tokens, you can also inspect the exp claim locally and refresh proactively.

Talking point to explain it to anyone: "It's a service master key tied to one named maintenance account. The app shows the key, Salesforce knows exactly which limited account it stands for, and grants only that account's access — no personal passwords involved."


7. Key terms

client_credentials · run-as user · integration user · Integration user license · Minimum Access – API Only Integrations profile · API Integration PSL · client secret · least privilege · My Domain — all defined in 01-authentication-fundamentals.md.


Sources (Verified June 2026)


Next: 06-device-flow.md — authorizing input-constrained devices (TVs, CLIs) with a user code.

06 - OAuth 2.0 Device Flow

One-liner: A device with no keyboard or browser (smart TV, CLI, IoT box) shows the user a short code and a URL. The user opens that URL on their phone, types the code, and the device gets a token. Use when: An app runs on hardware with limited input or display that cannot host a full login page. Grant type: response_type=device_code (start), then grant_type=device (poll) · Status: ✅ Supported, but input-constrained devices only. Removed from the auto-installed Data Loader connected app on Sept 2, 2025. Tokens returned: Access token (+ refresh token if refresh_token/offline_access scope requested).

New here? Read 01-authentication-fundamentals.md first for tokens, scopes, and endpoints.


1. The idea in plain English

Think of signing in to a streaming app on a new smart TV. The TV cannot show a comfortable login form, and typing your password with a remote is painful. So the TV puts up a message: "Go to example.com/activate and enter code WDJB-MJHT." You pull out your phone, open that page, log in there once, type the short code, and approve. The TV, meanwhile, keeps quietly asking Salesforce "are we approved yet?" every few seconds until you finish. Then it gets its token.

That split is the whole flow. The constrained device never handles the password. All the typing and authentication happen on a second screen (your phone or laptop) that already has a real browser. The device just polls and waits.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
The device has limited input/display: smart TV, set-top box, appliance, IoT sensor. The device has a normal browser and keyboard → use 02-web-server-flow.md.
A CLI or headless tool wants the user to authenticate on a separate machine. No human user at all (cron, ETL) → use 05-client-credentials-flow.md or 04-jwt-bearer-flow.md.
You want a user-context token (acts as the approving user) on such a device. A mobile/desktop app that can open its own browser → use Web Server flow + PKCE.

Real-world examples: a Salesforce dashboard on a wall-mounted smart display in a sales bullpen; a command-line data tool that asks you to approve on your laptop; an IoT gateway that posts telemetry as a specific Salesforce user.

Heads up — Data Loader change: Salesforce removed OAuth 2.0 Device Flow support from the auto-installed Data Loader connected app on September 2, 2025. Data Loader v53.0.1 introduced device-flow login, but newer Data Loader (e.g. v64.1.0) no longer supports Device Flow (or User-Agent Flow). The Salesforce CLI's default connected app saw a similar device-flow removal in 2025. If a tool used to log in with a device code and suddenly fails, this is a likely cause. Migrate to the Web Server flow or use a dedicated connected app.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant Dev as Device<br/>TV or CLI participant SF as Salesforce<br/>/services/oauth2/token participant U as User on<br/>phone or PC Dev->>SF: POST /token response_type=device_code, client_id SF-->>Dev: device_code, user_code, verification_uri,<br/>interval, expires_in Dev->>U: Show Go to verification_uri,<br/>enter user_code Note over Dev,SF: Device starts polling every interval seconds Dev->>SF: POST /token grant_type=device, code=device_code, client_id SF-->>Dev: authorization_pending, keep waiting U->>SF: Open verification_uri, log in, enter user_code, Approve Dev->>SF: POST /token grant_type=device, code=device_code, poll SF-->>Dev: access_token plus refresh_token, instance_url Dev->>SF: GET /services/data/v66.0/... Bearer access_token

Walkthrough

1-2. Device requests a code. The device POSTs to the token endpoint with response_type=device_code and its client_id. Salesforce returns a device_code (the device's secret handle), a short user_code for the human to type, a verification_uri to visit, a expires_in lifetime, and a polling interval in seconds. 3. Device shows instructions. It displays the user_code and verification_uri on screen (e.g. "Visit ...activate and enter WDJB-MJHT"). 4-5. Device polls. Starting now, the device POSTs to the token endpoint with grant_type=device and code=<device_code> every interval seconds. Until the user approves, Salesforce replies authorization_pending. 6. User approves on a second screen. On a phone or laptop, the user opens the verification_uri, authenticates (password, MFA, SSO), enters the user_code, and grants consent. 7-8. Device receives tokens. The next poll succeeds: Salesforce returns the access token, optionally a refresh token, and instance_url. The device calls the API with the bearer token.


4. The actual requests & responses

Step 1 — device asks for a code:

curl https://MyDomainName.my.salesforce.com/services/oauth2/token \
  -d response_type=device_code \
  -d client_id=3MVG9...CONSUMER_KEY \
  -d scope=api%20refresh_token

Step 1 response — what the device shows the user:

{
  "device_code": "GmRhmhcxhw...EUqIVDf0",
  "user_code": "WDJBMJHT",
  "verification_uri": "https://MyDomainName.my.salesforce.com/setup/connect",
  "interval": 5,
  "expires_in": 600
}

Step 2 — the user goes to verification_uri on a phone/PC, logs in, and enters user_code. (No code on the device side for this step; it is a human action in a browser.)

Step 3 — device polls the token endpoint at the given interval:

curl https://MyDomainName.my.salesforce.com/services/oauth2/token \
  -d grant_type=device \
  -d client_id=3MVG9...CONSUMER_KEY \
  -d code=GmRhmhcxhw...EUqIVDf0

Step 3 response while waiting (HTTP 400, keep polling):

{ "error": "authorization_pending", "error_description": "User has not yet approved the device." }

Step 3 response once approved (HTTP 200):

{
  "access_token": "00D5g000004...!AQEAQ...",
  "refresh_token": "5Aep861...l4Lo",
  "signature": "k0r...=",
  "scope": "api refresh_token",
  "instance_url": "https://MyDomainName.my.salesforce.com",
  "id": "https://login.salesforce.com/id/00D.../005...",
  "token_type": "Bearer",
  "issued_at": "1718700000000"
}

Connected App / ECA setup checklist

  1. Create a Connected App (or External Client App). Enable OAuth Settings.
  2. Enable the device flow. For a Connected App, turn on the device-flow option in Global / OAuth settings (the setting that lets the app issue a device code). For an External Client App, enable the device flow policy (the isDeviceFlowEnabled setting).
  3. Select scopes (api, plus refresh_token/offline_access if you want a refresh token).
  4. A callback URL is not used by the device flow itself, but the app still requires one configured.
  5. Copy the Consumer Key (client_id). The device flow is for public clients; do not rely on shipping a secret to the device.

5. Security pitfalls & gotchas

Pitfall Why it bites Fix
Device-code phishing / illicit consent An attacker starts their own device flow, gets a real user_code, and tricks a victim ("enter this code to verify your account") into approving it on the genuine Salesforce page. The attacker's device then receives the victim's token. No fake site is needed. Train users to never enter a device code they did not personally initiate; verify the app name and requested scopes on the consent screen; restrict who can approve; monitor for unexpected device-flow grants.
Polling too fast Hammering the token endpoint can get you rate-limited or a slow_down error. Respect the returned interval; back off when told to.
Long-lived user_code window A code valid for the full expires_in gives an attacker a wider phishing window. Approve promptly; the code expires (e.g. ~10 min) and must be re-requested after that.
Over-broad scopes on a shared screen A wall-mounted device with full exposes everything if the screen is seen or the device is stolen. Request least privilege; avoid full; consider a dedicated low-privilege user.
Treating the device as confidential Devices cannot keep a client secret. Use it as a public client; protect the refresh token at rest on the device.

Interview-grade nuance: device-flow phishing is the same class of attack that hit large numbers of Microsoft 365 tenants. The danger is that the login page is 100% genuine — there is no lookalike domain to flag — so the only real defense is user awareness plus scope and consent controls. Expect this as a security question.


6. Interview Q&A

Q: When would you choose the Device Flow? A: When the app runs on hardware with limited input or display that cannot present a normal login page: smart TVs, set-top boxes, appliances, IoT devices, and headless CLI tools. The user authenticates on a second device with a real browser.

Q: Walk me through the steps. A: The device POSTs response_type=device_code with its client_id and gets back a device_code, a human-friendly user_code, a verification_uri, an interval, and expires_in. It shows the user the code and URL, then polls the token endpoint with grant_type=device and code=<device_code> every interval seconds. The user opens the URL on a phone, logs in, enters the code, and approves. The next poll returns the access token (and refresh token if requested).

Q: What is device-code phishing and how do you defend against it? A: An attacker starts their own device flow, obtains a legitimate user_code, and socially engineers a victim into entering that code and approving on the real Salesforce page. The attacker's device then receives the victim's token. Because the page is genuine, URL-based phishing filters do not help. Defenses: user training (never approve a code you did not start), scrutinizing the app name and scopes on the consent screen, limiting who can approve device flows, and monitoring for unexpected grants.

Q: Does the Device Flow return a refresh token? A: Yes, if you request the refresh_token (or offline_access) scope and the app allows it. This differs from JWT Bearer and Client Credentials, which return an access token only.

Q: What changed with Data Loader and the Device Flow? A: Salesforce removed device-flow support from the auto-installed Data Loader connected app on September 2, 2025, and recent Data Loader versions no longer support it. The Salesforce CLI's default connected app had a similar device-flow removal. Affected tools must move to another flow or use a dedicated connected app.

Talking point to explain it to anyone: "It is how you log in to Netflix on a new TV. The TV shows a short code, you type it on your phone, and the TV gets let in once you approve."


7. Key terms

device_code · user_code · verification_uri · interval · authorization_pending · slow_down · public client · polling — base definitions in 01-authentication-fundamentals.md.


Sources (Verified June 2026)


Next: 07-username-password-flow.md — the deprecated flow that passes raw credentials, and what to use instead.

07 - Username-Password Flow

⛔ DEPRECATED / RETIRING. This flow passes a user's raw username and password straight to the token endpoint. Salesforce is retiring it and recommends you never build new integrations on it. One-liner: The app POSTs the user's username + password (with security token) and gets back an access token. Use when: Almost never. Understand it only for legacy systems and interviews. For real work, use 05-client-credentials-flow.md (server-to-server) or 02-web-server-flow.md + PKCE (user login). Grant type: password · Status: ⛔ Deprecated. Blocked by default for orgs created Summer '23+; full retirement targeted for Winter '27. Not supported by External Client Apps. Tokens returned: Access token only. No refresh token.

New here? Read 01-authentication-fundamentals.md first for tokens, scopes, and endpoints.


1. The idea in plain English

Imagine giving a delivery driver your actual house key, your alarm code, and your name so they can let themselves in whenever they like. That is this flow. The app holds the user's real Salesforce password and replays it to get a token. There is no claim ticket, no consent screen, no MFA prompt, no back-and-forth. Just credentials in, token out.

That is exactly why it is being killed. A password sitting in a config file or environment variable is a single point of catastrophic failure, and it bypasses multi-factor authentication entirely. Modern Salesforce wants either a passwordless machine identity (Client Credentials) or a proper interactive login (Web Server + PKCE). Learn this flow so you can recognize it and migrate off it, not so you can build with it.


2. When to use it (and when not)

✅ The only acceptable reasons ❌ Do not use it for
Understanding a legacy integration you inherited so you can migrate it. Any new integration. It is deprecated and retiring.
A short-lived internal test in a dev org where the flow is still enabled. Production server-to-server jobs → use 05-client-credentials-flow.md.
Interview discussion of why it is unsafe. User login in an app → use 02-web-server-flow.md + PKCE.

Migration cheat sheet (say this in an interview):

If the old code used Username-Password for... Migrate to
A backend/batch job running as a service identity 05-client-credentials-flow.md (run-as user + secret, passwordless)
A certificate-based server integration 04-jwt-bearer-flow.md (signed JWT, no password)
Interactive user login 02-web-server-flow.md + PKCE

Status details to quote: Username-Password is blocked by default for orgs created in Summer '23 or later. Salesforce announced its retirement, targeted for Winter '27, after which connected-app integrations using this flow will break. It is not supported by External Client Apps at all — only legacy Connected Apps. This is the single most important "what's changing" fact in Module 03.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant App as App or Server participant SF as Salesforce<br/>/services/oauth2/token participant API as Salesforce API Note over App: App already holds the user's<br/>raw password, the core problem App->>SF: POST /token grant_type=password<br/>username, password+token, client_id, client_secret SF-->>App: access_token, instance_url, NO refresh_token App->>API: GET /services/data/v66.0/... Bearer access_token API-->>App: 200 OK + data

Walkthrough

  1. The app already has the user's password stored somewhere. That storage is the core risk.
  2. The app POSTs to the token endpoint with grant_type=password, the username, the password (with the user's security token appended), plus the Connected App's client_id and client_secret. There is no browser, no consent screen, and no MFA.
  3. Salesforce validates the credentials and returns an access token and instance_url. Critically, no refresh token is issued, so when the token expires the app must replay the password again.
  4. The app calls the API with Authorization: Bearer <access_token>.

Notice the auth server is drawn in red. That is intentional: every step here is a security liability, and the absence of a consent/MFA step is exactly what regulators and Salesforce object to.


4. The actual requests & responses (legacy / for understanding only)

Do not build new code from this. It is shown so you can read existing integrations and migrate them.

The token request:

curl https://MyDomainName.my.salesforce.com/services/oauth2/token \
  -d grant_type=password \
  -d client_id=3MVG9...CONSUMER_KEY \
  -d client_secret=ABCD...CONSUMER_SECRET \
  -d username=integration.user@example.com \
  --data-urlencode "password=MyPasswordSECURITYTOKEN"

Key detail — the password field: for a user logging in from an untrusted network / IP, you must concatenate the password and the user's security token into one value: <password><securityToken> with no space. If the calling IP is in the org's trusted IP range (or the user has no security token), the token suffix may be omitted. Always send credentials in the POST body, never in the URL query string.

The token response:

{
  "access_token": "00D5g000004...!AQEAQ...",
  "instance_url": "https://MyDomainName.my.salesforce.com",
  "id": "https://login.salesforce.com/id/00D.../005...",
  "token_type": "Bearer",
  "issued_at": "1718700000000",
  "signature": "k0r...="
}

Note what is missing: there is no refresh_token and (unless you request openid) no id_token. When the access token expires the app has to send the password again, which is precisely why this pattern resists modern security controls.

Connected App setup (legacy):

  1. The flow works only with a Connected App (External Client Apps do not support it).
  2. Enable OAuth Settings and select scopes (e.g. api).
  3. The org must have the username-password flow allowed (it is blocked by default for orgs created Summer '23+; an admin would have to explicitly permit it where still possible).
  4. Copy the Consumer Key and Consumer Secret.
  5. The integration user needs a security token (or the caller's IP must be trusted) and an MFA posture that this flow unfortunately bypasses.

5. Security pitfalls & gotchas

Pitfall Why it bites Fix
Storing a raw password A leaked config file or env var hands over a full user login. Stop using this flow. Move to Client Credentials (secret, not password) or JWT Bearer (key, no password).
Bypasses MFA The flow has no interactive step, so multi-factor and login policies do not apply. Use a flow that honors MFA, or a passwordless machine identity.
No refresh token The app must re-send the password on every expiry, multiplying exposure. Use a flow with proper token lifecycle (Web Server).
Blocked / retiring Orgs created Summer '23+ block it by default; Winter '27 retirement will break it. Migrate now; do not start new builds on it.
Not supported by ECAs New apps should be External Client Apps, which cannot use this flow. Build new integrations on ECAs with a supported flow.
Security token confusion Forgetting to append the security token (untrusted IP) causes silent auth failures. Append securityToken to the password, or trust the IP range; understand the org's policy.

6. Interview Q&A

Q: What is the Username-Password flow and why is it dangerous? A: The app sends the user's raw username and password (security token appended) with grant_type=password to the token endpoint and gets an access token. It is dangerous because it stores and transmits the actual password, bypasses MFA, returns no refresh token, and offers no consent screen. Salesforce is retiring it.

Q: What is the retirement timeline? A: It is blocked by default for orgs created in Summer '23 or later, and Salesforce has announced full retirement targeted for Winter '27, after which connected-app integrations using it will stop working. It is also not supported by External Client Apps.

Q: An old integration uses this flow. How do you migrate it? A: Depends on context. For a backend/service job, move to Client Credentials (run-as user + secret, passwordless). For a certificate-based integration, JWT Bearer. For interactive user login, Web Server flow + PKCE. The goal is to eliminate the stored password.

Q: Why does this flow not return a refresh token? A: There is no user session or consent to keep alive in the OAuth sense. The design assumption was that the app can just replay the password to get a new access token, which is itself the security problem.

Q: Where do you append the security token, and when is it needed? A: You concatenate it to the end of the password (passwordSECURITYTOKEN, no space). It is required when the request comes from an untrusted IP. If the caller's IP is trusted by the org, it can be omitted.

Talking point to explain it to anyone: "It is like giving a contractor your actual house key instead of a temporary guest code. If they lose it, your whole house is exposed, and there is no way to tell who used it. We are replacing it with guest codes that expire and can be revoked."


7. Key terms

grant_type=password · security token · refresh token (absent here) · MFA bypass · confidential client — base definitions in 01-authentication-fundamentals.md.


Sources (Verified June 2026)


Next: 08-refresh-token-flow.md — how apps silently get a fresh access token without bothering the user.

08 - Refresh Token Flow

One-liner: After a user flow already issued a refresh token, the app silently trades that long-lived token for a fresh access token — no user, no re-login. Use when: An access token from a prior Web Server or User-Agent flow has expired and you need a new one without prompting the user. Grant type: refresh_token · Status: ✅ Recommended (the standard way to keep a user session alive). Tokens returned: A new access token (the same refresh token is reused, unless rotation is on).

New here? Read 01-authentication-fundamentals.md first for tokens, scopes, and endpoints.


1. The idea in plain English

Think of a gym membership card. When you join, the front desk checks your ID once and gives you a day pass (access token) plus a membership card (refresh token). The day pass expires every evening. But you don't re-show your ID each morning — you just tap your membership card at the turnstile and get a new day pass. The card itself lasts months, until it's cancelled.

That's this flow. It is not how you first get in — a user already logged in through an interactive flow that handed back a refresh token. From then on, whenever the short-lived access token expires, your app quietly taps the refresh token against the token endpoint and receives a new access token. The user never sees a login screen again until the membership (refresh token) is revoked or expires.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
A user already logged in via 02-web-server-flow.md and you hold a refresh token. This is the first login → use Web Server or User-Agent first; this flow can't start a session.
The access token expired and you need a new one silently. Pure server-to-server with no user → 05-client-credentials-flow.md or 04-jwt-bearer-flow.md (they have no refresh token by design).
You want "stay logged in" without re-prompting. The refresh token was never issued (you forgot the refresh_token/offline_access scope).

Real-world examples: a customer portal keeping a user connected for weeks; a mobile app that refreshes its session at app launch; middleware that maintains a per-user Salesforce session across long-running jobs.

Interview framing: this is a continuation flow, not an initial-login flow. JWT Bearer and Client Credentials never issue a refresh token — they just re-run their flow. Only user flows that requested the refresh_token scope can use this.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant App as App<br/>(holds refresh_token) participant SF as Salesforce<br/>(/services/oauth2/token) participant API as Salesforce API<br/>(/services/data) App->>API: GET /services/data/v66.0/... (Bearer access_token) API-->>App: 401 INVALID_SESSION_ID (access token expired) Note over App: No user prompt — use the refresh token App->>SF: POST /token<br/>grant_type=refresh_token<br/>(refresh_token, client_id, client_secret) Note over SF: Validate refresh token<br/>and app policies SF-->>App: NEW access_token (+ instance_url) App->>API: GET /services/data/v66.0/... (new Bearer token) API-->>App: 200 OK + data

Walkthrough

1-2. The app calls the API with its current access token and gets 401 INVALID_SESSION_ID because the token expired. 3. Instead of prompting the user, the app POSTs to the token endpoint with grant_type=refresh_token, the stored refresh_token, and its client_id + client_secret. 4. Salesforce validates the refresh token against the app's OAuth policies. 5. Salesforce returns a new access token (and instance_url). The same refresh token keeps working unless rotation is enabled. 6-7. The app retries the call with the new token and succeeds.


4. The actual requests & responses

The refresh request — credentials in the body:

curl https://MyDomainName.my.salesforce.com/services/oauth2/token \
  -d grant_type=refresh_token \
  -d refresh_token=5Aep861...l4Lo \
  -d client_id=3MVG9...CONSUMER_KEY \
  -d client_secret=ABCD...CONSUMER_SECRET

You may instead send client_id:client_secret as an HTTP Basic Authorization header rather than in the body. The client_secret is omitted only for public clients that registered without one.

The token response — note there is no new refresh_token in the default case (you reuse the one you have):

{
  "access_token": "00D5g000004...!AQEAQ...",
  "signature": "k0r...=",
  "scope": "api refresh_token openid",
  "instance_url": "https://MyDomainName.my.salesforce.com",
  "id": "https://login.salesforce.com/id/00D.../005...",
  "token_type": "Bearer",
  "issued_at": "1718700000000"
}

Refresh token expiration policy (set on the Connected App's OAuth policies — pick one):

Policy Behavior
Refresh token is valid until revoked Default. Works indefinitely until a user or admin revokes it.
Immediately expire refresh token Invalid right away; the current access token still works until it expires, but no new sessions can be obtained.
Expire refresh token after n Valid for a fixed window (e.g. 24 hours), then dead.
Expire refresh token if not used for n Stays alive only if used within the rolling window; idle tokens die.

Refresh Token Rotation (RTR): if you enable it on the app, each refresh also returns a brand-new refresh token and invalidates the old one. The response then includes a fresh refresh_token, and your app must store it for next time. This shrinks the window a stolen token is usable.

Revoking a token — the kill switch (logout / compromise):

curl https://MyDomainName.my.salesforce.com/services/oauth2/revoke \
  -d token=5Aep861...l4Lo

Revoking the refresh token also invalidates the access tokens derived from it.

Salesforce as the client (outbound): when Salesforce itself holds the refresh token (via a Named Credential / External Credential of type "OAuth 2.0 — Authorization Code"), the platform performs this refresh for you automatically. You don't code the POST. See 14-named-credentials-and-external-credentials.md.


5. Security pitfalls & gotchas

Pitfall Why it bites Fix
Treating the refresh token as low-risk It's a long-lived bearer secret — whoever holds it can mint access tokens for weeks. Encrypt at rest, store server-side, never in browser/localStorage.
Never expiring refresh tokens "Valid until revoked" leaves a stolen token usable indefinitely. Set an expiration policy and/or enable Refresh Token Rotation.
Ignoring rotation responses With RTR on, the new refresh token replaces the old; missing it logs the user out. Always read and persist the refresh_token from each response.
No revocation on logout / breach Old sessions live on after a user logs out or a device is lost. Call /revoke on logout; revocation also fires on password change/policy.
Assuming refresh tokens never die Password resets, admin revocation, or policy can kill them anytime. Handle a failed refresh by falling back to a full interactive login.
Forgetting the refresh_token scope at login No refresh token was ever issued, so this flow can't run. Request refresh_token / offline_access during the initial flow.

6. Interview Q&A

Q: What is the Refresh Token flow and how does it differ from the other flows? A: It renews an access token after a user flow already issued a refresh token. It is not an initial-login flow — you can't start a session with it. The app POSTs grant_type=refresh_token with the stored token and client credentials, and gets a new access token, with no user interaction.

Q: Where does the refresh token come from? A: From an earlier Web Server or User-Agent flow that requested the refresh_token (a.k.a. offline_access) scope. Machine flows (JWT Bearer, Client Credentials) never issue one.

Q: Does each refresh return a new refresh token? A: By default no — you reuse the same one. If Refresh Token Rotation is enabled on the Connected App, each refresh returns a new refresh token and invalidates the previous one.

Q: What are the refresh token expiration policies? A: Valid until revoked (default), immediately expire, expire after n, and expire if not used for n. They're set on the Connected App's OAuth policies and control how long silent refresh keeps working.

Q: A user changed their password — what happens to the refresh token? A: It can be invalidated. A failed refresh should make the app fall back to a fresh interactive login. Admins can also kill tokens via /revoke or by revoking OAuth access on the user/app.

Q: How do you securely store a refresh token? A: It's a long-lived bearer secret — encrypt it at rest, keep it server-side (never in browser storage), scope it minimally, set an expiration policy, prefer rotation, and revoke on logout.

Talking point to explain it to anyone: "It's a gym membership card. You showed ID once at signup; after that you just tap the card each day for a new pass. The card lasts until it's cancelled — so guard it like a key."


7. Key terms

refresh_token · bearer token · Refresh Token Rotation (RTR) · refresh token expiration policy · /revoke endpoint · INVALID_SESSION_ID · offline_access scope — all defined in 01-authentication-fundamentals.md.


Sources (Verified June 2026)


Next: 09-saml-bearer-assertion-flow.md — authenticating with a signed SAML assertion instead of a JWT.

09 - SAML Bearer Assertion Flow

One-liner: Your app builds and digitally signs a single SAML 2.0 assertion, POSTs it to the token endpoint, and gets back an access token. No user, no password, no refresh token. Use when: Server-to-server integration where the org already trusts a SAML signing certificate (common org-to-org or legacy SAML shops). Grant type: urn:ietf:params:oauth:grant-type:saml2-bearer · Status: ⚠️ Works, but JWT Bearer is usually the better modern choice (simpler token, same idea). Tokens returned: Access token only. No refresh token. No client secret needed.

New here? Read 01-authentication-fundamentals.md first for tokens, scopes, and endpoints.


1. The idea in plain English

Imagine a notarized letter. Instead of showing up in person to prove who you are, you write a letter that says "I am acting for user jdoe," then have it stamped by a notary whose seal the other party already recognizes. You mail that one sealed letter and they let you in.

In this flow your app is the letter-writer, the private key is the notary stamp, and Salesforce already holds the matching public certificate (you uploaded it to the Connected App). Your app assembles a SAML assertion, signs it with the private key, and sends it once. Salesforce checks the seal against the stored certificate and, if it matches, hands back an access token. Nobody logs in interactively.

Mental model: This is the JWT Bearer flow (04-jwt-bearer-flow.md) with one swap — the signed credential is a SAML 2.0 XML assertion instead of a compact JWT. Same "sign a statement, trade it for a token" plot; different envelope.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You already have SAML infrastructure (a SAML IdP / cert) and want to reuse it for API auth. Greenfield server-to-server integration → use 04-jwt-bearer-flow.md (lighter, easier to debug).
Org-to-org machine integration on behalf of a known user, no human present. No user identity needed at all → use 05-client-credentials-flow.md (run-as user).
A middleware platform that natively emits signed SAML assertions (some ESB/MDM tools). A real user is logging in to a web app → use 02-web-server-flow.md.

Real-world examples: a legacy enterprise service bus that already mints signed SAML for every system; one Salesforce org calling another org's API as an integration user; an older partner integration standardized on SAML before JWT Bearer was common.

The one-line decision rule: Pick SAML Bearer only because SAML already exists in your landscape. If you are starting fresh, pick JWT Bearer.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant App as Client App<br/>(holds private key) participant SF as Salesforce Auth<br/>(/services/oauth2/token) participant API as Salesforce API<br/>(/services/data) App->>App: Build SAML 2.0 assertion<br/>Issuer, Subject, Audience App->>App: Sign assertion with private key<br/>RSA SHA-256 App->>App: base64url + URL-encode the assertion App->>SF: POST /token<br/>grant_type=saml2-bearer + assertion SF->>SF: Verify signature with uploaded cert<br/>Validate Issuer, Audience, Subject, expiry SF-->>App: access_token + instance_url<br/>NO refresh token App->>API: GET /services/data/v66.0/... Bearer token API-->>App: 200 OK + data

Walkthrough

1-3. Your app constructs a SAML 2.0 assertion, signs the XML with the private key, and base64url-encodes it (then URL-encodes it for the POST body). 4. The app POSTs to /services/oauth2/token with grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer and the encoded assertion in the assertion parameter. No client_secret is sent. 5. Salesforce verifies the signature against the certificate you uploaded to the Connected App, then checks Issuer, Audience, Recipient, Subject, and validity window. 6. If valid (and the app was previously authorized), Salesforce returns an access token and instance_url. There is no refresh token — when it expires you simply sign a fresh assertion and repeat. 7-8. The app calls the API with Authorization: Bearer <access_token>.


4. The actual requests & responses

Token endpoint (always your My Domain):

POST https://MyDomainName.my.salesforce.com/services/oauth2/token
Content-Type: application/x-www-form-urlencoded

The assertion must satisfy these rules (Salesforce validates each):

Element Required value
Issuer The Connected App's Consumer Key (client_id) for which you registered the certificate.
Audience https://login.salesforce.com or https://test.salesforce.com (sandbox).
Recipient (in SubjectConfirmationData) https://login.salesforce.com/services/oauth2/token (prod) or the test/My Domain token endpoint.
Subject NameID The username of the Salesforce user to run as (e.g. the integration user).
Signature XML Signature spec, RSA with SHA-1 or SHA-256.
Encoding on the wire base64url, then URL-encoded in the assertion parameter.

Sample assertion structure (abbreviated — the signed <ds:Signature> block is omitted for brevity):

<saml:Assertion Version="2.0" IssueInstant="2026-06-18T19:25:14.654Z" ...>
  <saml:Issuer>3MVG9...CONSUMER_KEY</saml:Issuer>
  <ds:Signature>...RSA-SHA256 signature over the assertion...</ds:Signature>
  <saml:Subject>
    <saml:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">
      integration.user@example.com
    </saml:NameID>
    <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
      <saml:SubjectConfirmationData NotOnOrAfter="2026-06-18T19:30:14.654Z"
        Recipient="https://login.salesforce.com/services/oauth2/token"/>
    </saml:SubjectConfirmation>
  </saml:Subject>
  <saml:Conditions NotBefore="2026-06-18T19:25:14.654Z" NotOnOrAfter="2026-06-18T19:30:14.654Z">
    <saml:AudienceRestriction>
      <saml:Audience>https://login.salesforce.com</saml:Audience>
    </saml:AudienceRestriction>
  </saml:Conditions>
  <saml:AuthnStatement AuthnInstant="2026-06-18T19:25:14.655Z">...</saml:AuthnStatement>
</saml:Assertion>

The token request (curl):

curl https://MyDomainName.my.salesforce.com/services/oauth2/token \
  -d grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer \
  --data-urlencode assertion=PHNhbWw6QXNzZXJ0aW9u... (base64url of the signed XML)

The token response:

{
  "access_token": "00D5g000004...!AQEAQ...",
  "scope": "api",
  "instance_url": "https://MyDomainName.my.salesforce.com",
  "id": "https://login.salesforce.com/id/00D.../005...",
  "token_type": "Bearer"
}

Note the absence: no refresh_token, no id_token by default. Certificate-based machine flows hand back an access token and nothing to keep a session alive — see the interview trap in 01-authentication-fundamentals.md.

Connected App certificate setup checklist

  1. Generate a key pair and self-signed certificate (e.g. keytool -genkey -keyalg RSA -keysize 2048 ..., export the .crt).
  2. Create a Connected App and enable OAuth Settings.
  3. Check "Use digital signatures" and upload the certificate (.crt) — Salesforce stores the public key; your app keeps the private key.
  4. Add at least one OAuth scope (e.g. api). At least one scope beyond the default is required.
  5. Save and copy the Consumer Key — this becomes the assertion's Issuer.
  6. Authorize the app for the run-as user (admin pre-authorization via permitted users, or a prior interactive consent).

5. Security pitfalls & gotchas

Pitfall Why it bites Fix
Treating the private key like config Anyone with the key can mint assertions = full impersonation of the run-as user. Store the key in a vault/HSM, rotate it, never commit it.
Long assertion validity window A long NotOnOrAfter widens the replay window if an assertion leaks. Keep the validity window short (minutes).
Wrong Audience/Recipient for sandbox Prod values (login.salesforce.com) fail against a sandbox and vice versa. Match Audience/Recipient to the target env (test / My Domain).
Expecting a refresh token There is none in this flow; code that waits for one stalls. Re-sign a new assertion each time the access token expires.
Clock skew between systems NotBefore/NotOnOrAfter checks fail if the app's clock drifts. Sync clocks via NTP; allow a small tolerance.
Confusing it with SAML Assertion flow Different trust model and config (see below). SAML Bearer = app self-signs via Connected App cert. SAML Assertion = IdP signs, uses org SSO config. See 10-saml-assertion-flow.md.

6. Interview Q&A

Q: What is the SAML Bearer Assertion flow in one sentence? A: A server-to-server OAuth flow where the app presents a single, previously-trusted, digitally signed SAML 2.0 assertion to the token endpoint and receives an access token — no interactive login and no refresh token.

Q: How is it different from JWT Bearer? A: Identical pattern — sign a statement, exchange it for a token — but the signed credential is a SAML 2.0 XML assertion instead of a JWT. JWT Bearer is lighter and easier to implement, so SAML Bearer is mainly chosen when existing SAML infrastructure is already in place. Both return an access token only.

Q: Where does Salesforce get the key to verify the signature? A: From the certificate uploaded to the Connected App under "Use digital signatures." Salesforce holds the public certificate; the app holds the matching private key and signs with it. The assertion's Issuer must equal the app's Consumer Key.

Q: Does this flow return a refresh token? A: No. It is a certificate-based machine flow with no user session, so there is nothing to refresh. When the access token expires, the app signs a brand-new assertion and requests another token.

Q: Is a client secret required? A: No. Authentication is proven by the signature on the assertion, not by a client secret. You also do not have to store a refresh token.

Q: When would you pick SAML Bearer over JWT Bearer? A: When the organization already runs SAML — an existing IdP or middleware that natively emits signed SAML assertions — so reusing that machinery is cheaper than introducing JWT. Greenfield, you would pick JWT Bearer.

Talking point to explain it to anyone: "It's a notarized letter. The app writes a statement, stamps it with a private seal Salesforce already recognizes, and trades that one sealed letter for a temporary key — no one logs in."


7. Key terms

urn:ietf:params:oauth:grant-type:saml2-bearer · Assertion · Bearer token · Consumer Key / Client ID · Connected App · base64url — all defined in 01-authentication-fundamentals.md.


Sources (Verified June 2026)


Next: 10-saml-assertion-flow.md — the related flow where an SSO'd user's IdP-signed assertion is exchanged for API access.

10 - SAML Assertion Flow (Access the API After Web SSO)

One-liner: A user who already authenticated through your SAML SSO can exchange that IdP-signed SAML assertion for a Salesforce access token, so the same login that got them into the UI also unlocks the API. Use when: You have SAML SSO configured for the org and a partner app wants API access using the very assertion it already federates with. Grant: Federates the same way as web SSO, using the SSO browser profile (urn:oasis:names:tc:SAML:2.0:profiles:SSO:browser) · Status: ⛔ Rarely used today — JWT or Web Server is almost always preferable. Tokens returned: Access token only. No refresh token. No connected app required.

New here? Read 01-authentication-fundamentals.md first for tokens, scopes, and endpoints.


1. The idea in plain English

You walk into an office building and badge in at the lobby turnstile (web SSO). Normally that badge-in only gets you through the front door. This flow says: take the exact same badge swipe you just used at the lobby and present it at the parking-garage gate (the API) to get in there too, without a second sign-in.

In Salesforce terms: a user logs in via your SAML Identity Provider, which issues a signed SAML response asserting who they are. Instead of only using that response for the web UI, the partner app reuses the same assertion against the OAuth token endpoint to obtain an access token for API calls. The trust comes from the org's existing SAML Single Sign-On settings, not from a Connected App certificate.

Mental model: This is "SSO, but for the API." The user already proved who they are to your IdP; this flow converts that proof into an API token.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
The org already has SAML SSO set up and you want to reuse the same assertion for API access. Server-to-server with no interactive user → use 04-jwt-bearer-flow.md or 09-saml-bearer-assertion-flow.md.
A partner app authenticates the user via an IdP and then needs to act as that user in the API. A normal "log in with Salesforce" web app → use 02-web-server-flow.md (far simpler, returns a refresh token).
You explicitly want to avoid creating a Connected App (this flow doesn't require one). You need long-lived access → this flow gives no refresh token. Use Web Server flow.

Reality check: The strict requirements (a single SubjectConfirmationData whose Recipient must be a Salesforce login/token URL, the correct Audience = your org's SAML Entity ID, IdP signature) make this impractical for most cases. In practice, almost everyone reaches for JWT Bearer, SAML Bearer, or the Web Server flow instead. Treat this as a "know it exists and can explain it" flow.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant U as User (Browser) participant IdP as Identity Provider<br/>external SAML IdP participant App as Partner App participant SF as Salesforce Auth<br/>(/services/oauth2/token) participant API as Salesforce API U->>IdP: Authenticate via SSO login IdP-->>App: Signed SAML response<br/>asserts the user identity Note over App,SF: App reuses that same assertion to get API access App->>App: base64url + URL-encode the SAML response App->>SF: POST /token<br/>assertion + SSO browser grant SF->>SF: Validate against org SAML SSO config<br/>signature, Recipient, Audience SF-->>App: access_token + instance_url<br/>NO refresh token App->>API: GET /services/data/v66.0/... Bearer token API-->>App: 200 OK + data

Walkthrough

1-2. The user authenticates against your SAML IdP (the same IdP your org trusts for web SSO). The IdP returns a signed SAML response asserting the user's identity. 3-4. The partner app takes that response, base64url-encodes it, then URL-encodes it for the POST body. 5. The app POSTs it to /services/oauth2/token, federating the same way as web SSO (the SSO browser profile assertion type). 6. Salesforce validates the assertion against the org's SAML Single Sign-On settings — the IdP signature, the Recipient (must be a Salesforce login/token URL), and the Audience (your org's SAML Entity ID). 7. On success Salesforce returns an access token and instance_url. No refresh token. 8-9. The app calls the API with Authorization: Bearer <access_token>.


4. The actual requests & responses

Token endpoint (your My Domain):

POST https://MyDomainName.my.salesforce.com/services/oauth2/token
Content-Type: application/x-www-form-urlencoded

The assertion must satisfy your org's SSO rules (Salesforce validates against SAML SSO settings):

Element Required value
Signed by Your Identity Provider (not the app, not a Connected App cert).
Recipient (in SubjectConfirmationData) An org-specific Login URL or OAuth 2.0 Token Endpoint shown on the SAML SSO settings page.
Audience Your org's correct Salesforce Entity ID (from SSO config).
Encoding on the wire base64-encoded, then URL-encoded assertion.
Connected App Not required — trust comes from SAML SSO config. SSO must be enabled for the org.

The token request (curl) — illustrative:

curl https://MyDomainName.my.salesforce.com/services/oauth2/token \
  -d grant_type=assertion \
  -d assertion_type=urn:oasis:names:tc:SAML:2.0:profiles:SSO:browser \
  --data-urlencode assertion=PHNhbWxwOlJlc3BvbnNl... (base64 of the IdP-signed SAML response)

Encoding nuance: the assertion is Base-64 encoded, then URL-encoded — the same value normally used for web single sign-on. Because it is the SSO assertion itself, you don't have to hand-craft a new one for the API.

The token response:

{
  "access_token": "00D5g000004...!AQEAQ...",
  "scope": "id api",
  "instance_url": "https://MyDomainName.my.salesforce.com",
  "id": "https://login.salesforce.com/id/00D.../005...",
  "token_type": "Bearer"
}

Setup checklist (org SSO, not a Connected App)

  1. Configure SAML Single Sign-On in Setup: register your Identity Provider, its certificate, Issuer, and Entity ID.
  2. Note the org's Login URL and OAuth 2.0 Token Endpoint from the SSO settings page — these are the valid Recipient values.
  3. Ensure the IdP issues the SAML response with the correct Recipient and Audience for that org.
  4. The partner app captures the IdP-signed assertion at login and reuses it against the token endpoint.

Why this is finicky: Salesforce supports only a single SubjectConfirmationData, and its Recipient must be a Salesforce login URL. An assertion built for a different service provider usually won't satisfy this, which is a big reason teams skip this flow in favor of JWT/SAML Bearer or Web Server.


5. Security pitfalls & gotchas

Pitfall Why it bites Fix
Confusing it with SAML Bearer Different trust model: here the IdP signs and trust comes from SSO config, not a Connected App cert. SAML Assertion = IdP-signed, org SSO. SAML Bearer = app self-signs via Connected App cert. See 09-saml-bearer-assertion-flow.md.
Wrong Recipient / Audience The assertion was minted for a different SP, so Salesforce rejects it. Mint with the org's Login URL / token endpoint as Recipient and the org's Entity ID as Audience.
Expecting a refresh token None is issued; long-lived access isn't supported here. For durable user access, use 02-web-server-flow.md.
Replaying a captured assertion A leaked SSO assertion could be replayed within its window. Keep validity windows short; protect the assertion in transit (TLS).
Assuming it needs a Connected App It doesn't — but it does require SSO to be enabled. Configure SAML SSO; no Connected App needed.
Reaching for this by default It's the most constrained option and rarely the right tool now. Prefer JWT Bearer, SAML Bearer, or Web Server flow.

6. Interview Q&A

Q: What is the SAML Assertion flow for? A: It lets a user who authenticated through SAML SSO exchange that IdP-signed SAML assertion for a Salesforce access token, so an already-SSO'd user can reach the API — "federate with the API the same way you federate for web SSO."

Q: How does it differ from the SAML Bearer Assertion flow? A: Who signs and where trust lives. In SAML Assertion, the Identity Provider signs and Salesforce trusts it via the org's SAML SSO configuration — no Connected App. In SAML Bearer, the app itself signs with a private key whose certificate is uploaded to a Connected App. Bearer is for a server with no user present; Assertion is tied to a user who just did SSO.

Q: Does it need a Connected App? A: No. Trust is established through the org's SAML Single Sign-On settings. SSO must be enabled, and the assertion's Recipient and Audience must match the org. (SAML Bearer, by contrast, requires a Connected App with an uploaded certificate.)

Q: Why is it considered legacy / rarely used? A: The constraints are strict — a single SubjectConfirmationData whose Recipient must be a Salesforce login URL, the right Audience/Entity ID, and an IdP-signed response. For most server integrations JWT Bearer, SAML Bearer, or Web Server flow are simpler and more flexible, so this one is seldom chosen today.

Q: Does it return a refresh token? A: No. Like the certificate-based bearer flows, it returns an access token only. For long-lived user access you'd use the Web Server flow with refresh_token scope.

Talking point to explain it to anyone: "It's reusing your lobby badge-swipe at the parking gate. You proved who you are once at SSO login, and this flow turns that same proof into an API key — no second sign-in."


7. Key terms

Assertion · Identity Provider (IdP) · SAML SSO · Entity ID · Bearer token · urn:oasis:names:tc:SAML:2.0:profiles:SSO:browser — all defined in 01-authentication-fundamentals.md.


Sources (Verified June 2026)


Next: 11-asset-token-flow.md — the token-exchange flow that issues scoped asset tokens for connected devices.

11 - Asset Token Flow

One-liner: A connected device exchanges a Salesforce access token plus its own signed actor token for an asset token (a JWT) that ties the physical device to a Salesforce identity and Asset record. Use when: You build IoT / connected-device integrations and need each device to authenticate to Salesforce as a verifiable "asset." Grant type: urn:ietf:params:oauth:grant-type:token-exchange · Status: ⚠️ Specialized (niche, valid only for connected-device / Service Cloud for IoT scenarios). Tokens returned: A signed asset token (JWT). No refresh token.

New here? Read 01-authentication-fundamentals.md first for tokens, scopes, and endpoints.


1. The idea in plain English

Imagine a fleet of vending machines. Each machine needs to prove "I am machine #4471, and I belong to this company's account" every time it phones home. You do not want to bake a human's password into the machine. Instead the machine carries its own tamper-proof badge.

The Asset Token Flow is OAuth 2.0 Token Exchange specialized for devices. The device (or a backend acting for it) first gets a normal access token, then trades that access token plus a JWT it signed itself for an asset token. The asset token is a JWT that says "this specific device is verified and linked to this Salesforce identity." Salesforce can also auto-create or link a Service Cloud Asset record during the exchange, so your CRM knows exactly which physical unit is talking.

The clever part: the device proves it holds a private key (the cnf claim carries its public key), so the asset token is bound to that one device. A stolen asset token is hard to reuse without the device's key.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You connect physical devices (sensors, machines, appliances) to Salesforce and need each tied to an Asset record. A normal server integration with no device identity → use 05-client-credentials-flow.md.
You want a JWT bound to a device's key that downstream services can validate offline. A user logging in from a browser or app → use 02-web-server-flow.md.
You are building on Service Cloud for IoT and want AssetTokenEvent to fire on registration. A device with a screen and keyboard but no asset concept → use 06-device-flow.md.
Device data must be linked to CRM (customer, account, contact, asset) at registration time. A back-office cron/ETL job → use 04-jwt-bearer-flow.md.

Real-world examples: a smart thermostat that registers itself against a customer's Asset and streams telemetry; connected medical or industrial equipment that must be individually identifiable in Service Cloud; a fleet-management gateway that mints a per-device token before pushing events.

Honesty check: This is a specialized, low-frequency flow. Most integrations never touch it. Know what it is and when it applies. Do not reach for it unless "device = asset" is genuinely part of the design.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant Dev as Device / Backend participant SF as Salesforce<br/>(/services/oauth2/token) participant API as Service Cloud<br/>(Asset + Events) Dev->>SF: Step A - run any OAuth flow<br/>(e.g. JWT Bearer) for an access_token SF-->>Dev: access_token Dev->>Dev: Sign an actor token (JWT)<br/>with the device private key Dev->>SF: Step B - POST /token<br/>grant_type=token-exchange<br/>subject_token=access_token<br/>actor_token=device JWT SF->>SF: Verify access_token + actor JWT signature<br/>read cnf (public key) + asset claims SF->>API: Register / link Asset record<br/>publish AssetTokenEvent SF-->>Dev: asset_token (signed JWT, bound to device key) Dev->>API: Use asset_token for device requests

Walkthrough

1-2. The device (or a backend on its behalf) first obtains a standard access token using any supported OAuth flow. JWT Bearer is the natural partner for an unattended device. 3. The device signs its own JWT (the actor token) using a private key it holds. The matching public key travels in the token's cnf ("confirmation") claim. 4. The device POSTs to the token endpoint with grant_type=urn:ietf:params:oauth:grant-type:token-exchange, passing the access token as the subject token and the device JWT as the actor token. 5. Salesforce validates both tokens, reads the public key from cnf, and reads any asset claims (e.g. a serial number). 6. Salesforce links or registers an Asset record and publishes an AssetTokenEvent platform event so you can automate follow-up. 7. Salesforce returns the asset token: a signed JWT, bound to the device's key, that the device presents on subsequent calls.


4. The actual requests & responses

The token-exchange request (asset token issuance):

curl https://MyDomainName.my.salesforce.com/services/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
  --data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:access_token" \
  --data-urlencode "subject_token=00D5g000004...!AQEAQ..." \
  --data-urlencode "actor_token_type=urn:ietf:params:oauth:token-type:jwt" \
  --data-urlencode "actor_token=eyJraWQiOiI...DEVICE_SIGNED_JWT"

The device-signed actor JWT carries the device's identity and key. Conceptually its payload includes an asset claim (with a serial number to link an Asset) and a cnf claim (the device's public key):

{
  "iss": "device-issuer",
  "sub": "device-4471",
  "asset": { "serialNumber": "VM-4471-AC" },
  "cnf": { "jwk": { "kty": "RSA", "n": "0vx7...", "e": "AQAB" } }
}

The response — an asset token (JWT):

{
  "access_token": "eyJraWQiOiI...ASSET_TOKEN_JWT",
  "issued_token_type": "urn:ietf:params:oauth:token-type:jwt",
  "token_type": "Bearer",
  "expires_in": 3600
}

The returned access_token here is the asset token: a signed JWT whose cnf claim echoes the device's public key, binding the token to that device.

Validating an asset token (any downstream service can do this offline):

  1. Split the JWT into header.payload.signature and base64url-decode the header.
  2. Read the kid from the header and fetch the public signing key from the key endpoint: your site's My Domain URL with /id/keys appended.
  3. Verify the JWS signature per RFC 7515, and validate the device binding using the public key in the cnf claim.
  4. Use a standard JWT library (RFC 7519). Do not hand-roll signature checks.

Connected App setup checklist (asset token settings)

  1. Create a Connected App (or External Client App) and enable OAuth Settings.
  2. Select Enable Asset Tokens in the app's OAuth settings.
  3. Asset tokens require the api and openid scopes. Add both.
  4. Configure the asset token signing certificate (the cert Salesforce uses to sign the issued asset token) and the audiences the token is valid for.
  5. Subscribe to AssetTokenEvent (a platform event) if you want Apex / Flow to react when a device registers. The event also stores the device's public key.

Where to dig deeper: Salesforce documents this under "OAuth 2.0 Asset Token Flow for Securing Connected Devices" and "Using and Validating Asset Tokens." It is part of the broader Token Exchange family. See Sources.


5. Security pitfalls & gotchas

Pitfall Why it bites Fix
Treating the asset token like a normal bearer token It is key-bound via cnf; consumers that ignore the binding lose the device-proof guarantee. Validate the cnf proof-of-possession, not just the signature.
Hardcoding a human user's credentials in firmware Devices outlive employees; credentials leak from extracted firmware. Use the device's own signed actor JWT, never a person's password.
Skipping the access-token (subject) step The exchange needs a valid subject token plus the actor token; one alone fails. Get an access token first (commonly via 04-jwt-bearer-flow.md), then exchange.
Writing your own JWT validator Signature and claim bugs become auth bypasses. Use a vetted JWT library and the /id/keys endpoint for keys.
Expecting a refresh token Token-exchange flows do not return one. Re-run the exchange to mint a fresh asset token when it expires.
Forgetting the required scopes Without api + openid the app cannot issue asset tokens. Grant both scopes on the app.
Reaching for this flow by default It is niche; misuse adds device-identity machinery you do not need. Use it only when the device-as-Asset model is real.

6. Interview Q&A

Q: What problem does the Asset Token Flow solve? A: It securely ties a physical device to a Salesforce identity. The device gets a JWT (asset token) bound to its own key, and Salesforce can link or register a Service Cloud Asset record in the same exchange so the device is identifiable in CRM.

Q: What grant type does it use, and how is it structured? A: urn:ietf:params:oauth:grant-type:token-exchange. You pass a subject token (a normal access token) and an actor token (a JWT the device signed). Salesforce returns an asset token.

Q: Why an actor token and a subject token? A: The subject token establishes the Salesforce authorization context; the actor token proves the device's identity and carries its public key in the cnf claim. The asset token is bound to that key, so possession alone is not enough to impersonate the device.

Q: How does a downstream service trust an asset token without calling Salesforce on every request? A: It validates the JWT offline using standard RFC 7515/7519 steps, fetching the signing key from the /id/keys endpoint by kid, and checks the cnf proof-of-possession.

Q: Is this a mainstream flow? A: No. It is specialized for IoT / Service Cloud for IoT. For ordinary server-to-server work you would use Client Credentials or JWT Bearer. Knowing it exists and when it applies is the point.

Talking point to explain it to anyone: "It's a tamper-proof badge for a machine. The device shows Salesforce a badge it signed itself, Salesforce stamps it and records which machine it is, and the machine carries that stamped badge from then on."


7. Key terms

token-exchange · subject_token / actor_token · cnf (proof-of-possession) · JWT · Asset record · AssetTokenEvent — token and JWT basics are defined in 01-authentication-fundamentals.md.


Sources (Verified June 2026)


Next: 12-authorization-code-and-credentials-flow.md — the headless identity flow for fully branded customer login.

12 - Authorization Code and Credentials Flow (Headless Identity)

One-liner: Your app shows its own login UI (no Salesforce-hosted page), collects the user's credentials, and exchanges them through an authorization-code request for tokens against an Experience Cloud site. Use when: You build a fully branded customer (B2C) login for an Experience Cloud digital experience and refuse to redirect users to a Salesforce login screen. Grant type: authorization_code (credentials submitted directly, headless) · Status: ✅ Recommended for modern headless customer identity. Tokens returned: Access token + refresh token (+ ID token if openid).

New here? Read 01-authentication-fundamentals.md first for tokens, scopes, and endpoints.


1. The idea in plain English

Think of a bank's mobile app. You type your username and password into the bank's screen, with the bank's logo, fonts, and flow. You never get bounced to a generic "log in here" page run by a third party. Behind the scenes the app still does the secure handshake, but the front door is 100% the brand's.

That is the Authorization Code and Credentials Flow. It is part of Salesforce Headless Identity. Normally the Web Server flow (02) redirects the user to a Salesforce-hosted login page. Here, your app owns the entire UI and submits the collected credentials directly. Salesforce still runs a real authorization-code exchange under the hood, so you get the security of the code-then-token pattern without the Salesforce-hosted page. It is the modern, fully branded way to do customer and partner identity on Experience Cloud.

This single flow is the foundation for a family of headless experiences: classic password login, passwordless (OTP) login, self-registration, and guest access.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You run an Experience Cloud site and want a fully branded login built in your own app/SPA. You are fine with the Salesforce-hosted login page → use 02-web-server-flow.md.
You are building B2C / customer or partner digital experiences (mobile, SPA, custom frontend). Internal employee SSO → use SAML/OIDC SSO, not headless.
You need headless login, passwordless, registration, or guest under one flow. A backend system integration with no human → use 05-client-credentials-flow.md.
You can stand up an External Client App and enable the OAuth plugin. You only have a legacy Connected App and cannot create an ECA → this flow expects an External Client App.

Real-world examples: a retailer's branded customer portal where shoppers sign in on the retailer's own screens; a mobile app that registers new customers in-app and logs them in via OTP; a partner community front-end built in React that never shows a Salesforce URL.

Why "headless"? The Salesforce identity service runs with no UI of its own in this flow. Your app is the head. That is the whole selling point: total control of branding and UX.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant U as User participant App as Your App<br/>(own login UI) participant SF as Salesforce / Experience Cloud<br/>(/services/oauth2) U->>App: Enter username + password<br/>in your branded screen App->>App: Generate PKCE<br/>code_verifier + code_challenge Note over App,SF: Step 1 - headless authorize (credentials sent directly) App->>SF: POST /authorize<br/>response_type=code_credentials<br/>+ credentials in headers/body<br/>+ code_challenge SF->>SF: Authenticate the user directly<br/>(no Salesforce login page) SF-->>App: authorization code Note over App,SF: Step 2 - exchange code for tokens App->>SF: POST /token<br/>grant_type=authorization_code<br/>+ code + code_verifier SF-->>App: access_token + refresh_token<br/>(+ id_token) App->>SF: Call APIs as the user (Bearer access_token) SF-->>App: 200 OK + data

Walkthrough

  1. The user types credentials into your branded UI. Salesforce never renders a page.
  2. The app generates a PKCE code_verifier / code_challenge pair (PKCE is required for public clients and strongly recommended generally).
  3. The app POSTs to the authorize endpoint of the Experience Cloud site, submitting the credentials directly (in request headers or the POST body) instead of via an interactive page.
  4. Salesforce authenticates the user headlessly and returns an authorization code.
  5. The app exchanges the code at the token endpoint with grant_type=authorization_code plus the PKCE code_verifier.
  6. Salesforce returns an access token, refresh token, and optionally an ID token. From here it behaves like any user-context session.

The two-step "code then token" structure is the same as the Web Server flow. The difference is who collects the credentials: your app does, headlessly, instead of a Salesforce-hosted page.


4. The actual requests & responses

These calls go to your Experience Cloud site host (https://customersite.my.site.com), not the bare org login host.

Step 1 — headless authorization-code request (credentials submitted directly):

curl https://customersite.my.site.com/services/oauth2/authorize \
  -H "Auth-Request-Type: Named-User" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "response_type=code_credentials" \
  --data-urlencode "client_id=3MVG9...CONSUMER_KEY" \
  --data-urlencode "redirect_uri=https://app.example.com/callback" \
  --data-urlencode "scope=api refresh_token openid" \
  --data-urlencode "username=customer@example.com" \
  --data-urlencode "password=THE_USER_PASSWORD" \
  --data-urlencode "code_challenge=Psd...PKCE_CHALLENGE"

Step 1 response — an authorization code:

{
  "code": "aPrx...AUTH_CODE"
}

Step 2 — exchange the code for tokens:

curl https://customersite.my.site.com/services/oauth2/token \
  -d grant_type=authorization_code \
  -d code=aPrx...AUTH_CODE \
  -d client_id=3MVG9...CONSUMER_KEY \
  -d redirect_uri=https://app.example.com/callback \
  -d code_verifier=THE_ORIGINAL_RANDOM_STRING

Step 2 response — tokens:

{
  "access_token": "00D5g000004...!AQEAQ...",
  "refresh_token": "5Aep861...l4Lo",
  "id_token": "eyJraWQiOiI...",
  "instance_url": "https://customersite.my.site.com",
  "token_type": "Bearer",
  "issued_at": "1718700000000",
  "scope": "api refresh_token openid"
}

Header and parameter names for credential submission vary by headless variation (login, passwordless, registration). Treat the names above as representative and confirm exact headers against the current Headless Identity Implementation Guide before coding. See Sources.

External Client App setup checklist

  1. Create an External Client App (not a classic Connected App). Enable its OAuth settings and the OAuth plugin.
  2. Org-level: set oAuthCdCrdtFlowEnable = true on the OauthOidcSettings metadata type (the "OAuth and OpenID Connect Settings" page in Setup). This enables the Authorization Code and Credentials Flow for the org.
  3. On the app's OAuth config, set isCodeCredFlowEnabled = true. Optionally set isCodeCredPostOnly = true to force credentials into the POST body only.
  4. Keep Require Proof Key for Code Exchange (PKCE) enabled (required for public clients; it covers the Code and Credentials flow and its headless variations).
  5. Select scopes: api, refresh_token / offline_access, and openid as needed.
  6. Configure the Experience Cloud site for Headless Identity (the login/registration handlers and settings that headless flows route through).

Related headless flows (same family, same External Client App):

Variation What it does
Headless Login Username + password collected in your UI. The base case described here.
Headless Passwordless Login User enters email or phone, verifies with a one-time password (OTP). No password stored.
Headless Registration Self-service sign-up entirely in your branded UI, then logs the new user in.
Headless Forgot Password Branded password-reset flow without a Salesforce-hosted page.
Headless Identity for Guest Users Guest / unauthenticated access for public-facing experiences.

5. Security pitfalls & gotchas

Pitfall Why it bites Fix
Using a classic Connected App This flow expects an External Client App with the OAuth plugin. Build it as an ECA. See 13-connected-apps-vs-external-client-apps.md.
Forgetting the org flag Without oAuthCdCrdtFlowEnable = true on OauthOidcSettings, the flow is disabled org-wide. Enable it in Setup or via Metadata API.
Skipping PKCE Public clients (SPA/mobile) can have codes intercepted. Always send code_challenge / code_verifier.
Storing customer passwords in your app Your UI handles raw credentials, so any logging or caching is a breach risk. Submit and discard. Never log or persist credentials. Prefer passwordless where possible.
Putting credentials in the URL query string Query strings land in logs and history. Send credentials in the POST body or headers (set isCodeCredPostOnly).
Confusing this with the legacy Username-Password flow Both take credentials, but that flow is retiring and is not headless identity. Use this flow for branded customer login. See 07-username-password-flow.md.
Pointing at the wrong host Headless identity runs through the Experience Cloud site, not the bare org. Use the *.my.site.com site URL.

6. Interview Q&A

Q: What is the Authorization Code and Credentials Flow and why does it exist? A: It is part of Salesforce Headless Identity. It lets an app build its own login UI for Experience Cloud customers and submit credentials directly, while Salesforce still runs a real authorization-code exchange behind the scenes. It exists so brands get total UX control without a Salesforce-hosted login page.

Q: How is it different from the standard Web Server flow? A: Same code-then-token security, but the Web Server flow redirects the user to a Salesforce-hosted page. Here the app collects the credentials itself (headless) and posts them. The browser never leaves your branded experience.

Q: What do you have to configure to turn it on? A: An External Client App with the OAuth plugin enabled, the org-level flag oAuthCdCrdtFlowEnable = true on OauthOidcSettings, isCodeCredFlowEnabled on the app, and an Experience Cloud site set up for Headless Identity. PKCE should stay on.

Q: Does it return a refresh token? A: Yes. It is a user-context flow, so you get an access token, a refresh token (with the right scope), and an ID token if you request openid. That distinguishes it from server flows like Client Credentials.

Q: What other headless flows are in the same family? A: Headless Passwordless Login (OTP), Headless Registration (self sign-up), Headless Forgot Password, and Headless Identity for Guest Users. They all build on this flow and the same External Client App.

Q: When would you NOT use it? A: For internal employee SSO (use SAML/OIDC), for backend system integrations (use Client Credentials), or when you are happy with the Salesforce-hosted login page (use the Web Server flow).

Talking point to explain it to anyone: "It's the bank-app login. You type your password on the bank's own screen, never a generic third-party page, but the secure handshake still happens behind the curtain."


7. Key terms

authorization_code · code_credentials · PKCE (code_challenge / code_verifier) · External Client App · OauthOidcSettings / oAuthCdCrdtFlowEnable · Headless Identity · Experience Cloud — token, scope, and PKCE basics are in 01-authentication-fundamentals.md.


Sources (Verified June 2026)


Next: 13-connected-apps-vs-external-client-apps.md — the container comparison that explains why headless identity needs an External Client App.

13 - Connected Apps vs External Client Apps (ECA)

One-liner: Both are the container that holds your OAuth identity (consumer key, scopes, callback URL). Connected Apps are the original (2013); External Client Apps are the modern, packaging-friendly replacement (GA 2024). Use when: You need to register any app that talks to Salesforce over OAuth or SSO — pick the right container before you build a flow. Status: New Connected App creation is disabled by default on new orgs starting Spring '26 (rollout began Winter '26). For new builds in 2026, use an External Client App.

New here? Read 01-authentication-fundamentals.md first for tokens, scopes, and endpoints. The flow files (02, 04, 05) all need one of these containers.


1. The idea in plain English

Think of an apartment building. The OAuth flow is how a person walks through the front door (key card, intercom, doorman). But before anyone can walk in, the building has to exist and be registered — it needs an address, a list of who's allowed in, and which doors their key opens. That registration is the app definition.

  • A Connected App is the old building: the developer who built it and the admin who runs it edit the same blueprint. Convenient, but if you ship that blueprint to another building (a package), the master keys (secrets) go with it. That is the packaging headache.
  • An External Client App (ECA) is the new building: the developer's wiring (consumer key/secret) lives in one sealed file, and the building manager's rules (who gets in, session limits) live in a separate file. You can hand out the manager's file safely because the secrets never leave the sealed file.

That single split — developer settings vs admin policy — is the whole reason ECAs exist.


2. Which should I use in 2026?

Situation Use this Why
Any new integration / app External Client App New Connected App creation is off by default on new orgs (Spring '26). ECA is the go-forward container.
You ship a managed package (ISV / 2GP) External Client App Built for second-generation packaging. Secrets stay out of the package.
You have CI/CD (source-driven deploys) External Client App Metadata splits cleanly into dev vs admin files.
You still need Canvas ECA (Spring '26+) or legacy Connected App Canvas plugin for ECA arrived in Spring '26. Before that, Canvas was Connected-App-only.
You need mobile push notifications Connected App (today) Push notification support on ECA is still rolling out. Verify per release before relying on it.
You need the Username-Password flow Neither cleanly ECAs do not support Username-Password, and that flow is retiring anyway. Use Client Credentials.
Existing Connected App already in production Keep it Existing Connected Apps keep working. The Spring '26 change only blocks new creation.

Interview headline: "For anything new in 2026 I default to an External Client App. Connected Apps still run, but Salesforce disabled creating new ones by default on new orgs in Spring '26, and ECAs solve the packaging and CI/CD problems Connected Apps never did."


3. What each one actually is

Connected App (the original container, 2013+)

A Connected App is a single metadata definition that holds:

  • Consumer Key (client id) and Consumer Secret (client secret).
  • Callback URL (redirect URI) for interactive flows.
  • OAuth scopes the app may request (api, refresh_token, openid, etc.).
  • Flow enablement (which OAuth flows are allowed — web server, JWT, device, and so on).
  • Policies: permitted users, IP relaxation, refresh-token expiration, session policies, custom attributes.

Everything — developer config and admin policy — lives in one object. That is simple for a single org, but it is exactly what makes Connected Apps awkward to package and distribute, because the secret-bearing parts cannot be cleanly separated from the admin-controlled parts.

External Client App / ECA (next generation, GA 2024)

An External Client App does the same job but splits the definition in two:

File Holds Who owns it Packageable?
Global OAuth settings Consumer key + consumer secret (the secrets) Developer Never — too sensitive
Local OAuth settings Callback URL, enabled flows, scope selection, PKCE toggle (references the global file, no secrets) Developer Yes
ECA Policies Permitted users, session policy, IP relaxation, refresh-token policy Admin Yes

Because the secret lives only in the global file (which is never packaged) and the local + policy files carry no secrets, you can distribute an ECA through a second-generation managed package (2GP) without leaking credentials. That is the architectural win.

ECAs are also not available to users by default. Even after the app exists, a user gets no access until an admin explicitly grants it — typically by turning on "Admin-approved users are pre-authorized" and assigning a profile or permission set. This is more secure by default than the old "All users may self-authorize" habit.


4. Side-by-side feature comparison

Capability Connected App External Client App (ECA)
First available 2013 GA 2024
New creation default (new orgs) Disabled by default (Spring '26) Enabled — the go-forward container
Dev vs admin separation No — one combined definition Yes — global secrets / local settings / policies are separate metadata
Built for 2GP packaging No (packaging is the pain point) Yes — secrets never enter the package
CI/CD friendly Limited Yes — clean source-driven metadata
Available to users by default Often self-authorize No — access must be granted via permission set / profile
OAuth web server (auth code) + PKCE Yes Yes
OAuth hybrid / Code and Credentials Yes Yes
Client Credentials flow Yes Yes
JWT Bearer flow Yes Yes
Device flow Yes Yes
Headless (registration / passwordless login) Yes Yes
Username-Password flow Yes (legacy, retiring) No — not supported
Canvas apps Yes Yes, from Spring '26 (Canvas plugin for ECA)
Mobile push notifications Yes Rolling out — verify per release
SAML SSO Yes Yes
Distribution Manual / 1GP-style, secrets travel 2GP managed package, secrets stay out

Verify-before-you-quote note: the support article literally titled "New Connected Apps Can No Longer Be Created in Spring '26" is the canonical source for the creation-default change. The Canvas plugin for External Client App release note is a Spring '26 item. Treat push notifications on ECA as "check the current release notes" rather than a settled yes.


5. The developer-vs-admin split (architecture)

flowchart TB subgraph DEV[Developer owns] G[Global OAuth Settings<br/>consumer key + secret<br/>NEVER packaged] L[Local OAuth Settings<br/>callback URL, flows,<br/>scopes, PKCE - no secrets] end subgraph ADMIN[Admin owns] P[ECA Policies<br/>permitted users,<br/>session, IP, refresh policy] PS[Permission Set / Profile<br/>grants user access] end L -->|references| G L --> PKG[2GP Managed Package<br/>safe to distribute] P --> PKG PS -->|explicit grant| USER[End users get access] PKG -->|install| SUB[Subscriber Org] style G fill:#F44336,color:#fff style L fill:#00A1E0,color:#fff style P fill:#ff9900,color:#fff style PS fill:#9C27B0,color:#fff style PKG fill:#4CAF50,color:#fff style USER fill:#4CAF50,color:#fff style SUB fill:#607D8B,color:#fff

Walkthrough

  1. The developer sets the consumer key/secret in the global file. It is sealed and never enters a package.
  2. The developer sets callback URL, enabled flows, scopes, and PKCE in the local file, which only references the global file.
  3. The admin sets runtime policies (who is permitted, session limits, IP rules).
  4. Only the local + policy metadata go into the 2GP package, so a subscriber installs the app with no exposed secrets.
  5. In the subscriber org, the admin grants access by assigning a permission set or profile. Nobody gets in by default.

Contrast with a Connected App, where steps 1-3 are one blob and the secret risks riding along during distribution.


6. Setup / configuration

Create an External Client App (UI)

  1. Setup -> App Manager -> New External Client App (or External Client App Manager).
  2. Enter name, contact email, and distribution state (Local for this org, or Packageable for 2GP).
  3. Enable OAuth and set the Callback URL (your exact redirect_uri).
  4. Select OAuth scopes (for example api, refresh_token offline_access, openid).
  5. Choose the OAuth flows to enable (web server / PKCE, client credentials, JWT, device — note Username-Password is not offered).
  6. Keep Require PKCE on for public clients.
  7. Save, then open Policies and set Permitted Users = Admin-approved users are pre-authorized.
  8. Assign a permission set or profile so real users can use the app. Without this step, access is denied by design.

Grant access (the step people forget)

Setup -> Permission Sets -> [your set] -> Assigned Connected Apps / External Client Apps
  -> Add the ECA -> Save -> Assign the permission set to users

Because ECAs are not on by default, skipping the permission set leaves every user with OAUTH_APP_BLOCKED / access-denied errors even though the app exists.

Salesforce as the client (outbound)

If Salesforce itself is calling an external API, you usually do not hand-build a flow against an ECA. You configure a Named Credential + External Credential instead, which manages tokens for you. See 14-named-credentials-and-external-credentials.md.


7. Security pitfalls & gotchas

Pitfall Why it bites Fix
Expecting to create a new Connected App on a fresh 2026 org Blocked by default since Spring '26; UI and API creation are off (package install excepted) Build an External Client App instead; request an exception from Support only if truly required
Forgetting ECAs are off by default Users hit access-denied even though the app exists Assign a permission set / profile and set "Admin-approved users are pre-authorized"
Trying to use Username-Password with an ECA ECAs do not support it; the flow is also retiring Use Client Credentials for server-to-server
Packaging a Connected App and leaking the secret The combined definition can carry the consumer secret into the package Use an ECA: the secret-bearing global file is never packaged
Assuming Canvas works on any ECA Canvas plugin for ECA only arrived in Spring '26 Confirm the org is on Spring '26+, or keep Canvas on a legacy Connected App
Assuming push notifications are ready on ECA Still rolling out Verify the current release notes; keep push on a Connected App if unsupported
Use Any API Client permission on users Lets a user bypass the "Admin-approved" restriction and reach any app Audit and remove this permission from normal users
Editing ECA secrets after packaging Global secrets are intentionally non-packageable Rotate the consumer key/secret in the global settings, redistribute the package metadata

8. Interview Q&A

Q: What is a Connected App? A: The original (2013) OAuth/SSO container in Salesforce. One metadata definition holding the consumer key/secret, callback URL, allowed scopes, enabled flows, and policies. Every classic OAuth flow registers as a Connected App.

Q: What is an External Client App and why was it introduced? A: The next-generation container (GA 2024). It splits developer settings from admin policy into separate metadata — global secrets, local settings, and policies — so it packages cleanly in 2GP, works with CI/CD, and never ships secrets in a package. It is the go-forward replacement for Connected Apps.

Q: What actually changed in Spring '26? A: New Connected App creation is disabled by default on new orgs (rollout began Winter '26). Creation is blocked through both UI and API, except via package install, unless you request an exception from Support. Existing Connected Apps keep working; new builds should be ECAs.

Q: Name a few differences between Connected Apps and ECAs. A: ECAs separate dev vs admin metadata, are 2GP-packageable without leaking secrets, are not available to users by default (require permission set/profile), and do not support the Username-Password flow. Connected Apps are a single combined definition that is harder to package safely.

Q: Which OAuth flows does an ECA support, and which does it not? A: Web server (auth code) with PKCE, hybrid / Code and Credentials, Client Credentials, JWT Bearer, device, and headless variants. It does not support Username-Password.

Q: When would you still pick a Connected App in 2026? A: Mainly for a capability ECAs do not yet cover in your org's release (historically Canvas before Spring '26, and mobile push notifications while support rolls out), or to keep an existing production app running. For genuinely new work, ECA is the default.

Q: How does an ECA avoid leaking secrets in a package? A: The consumer key/secret live only in the global OAuth settings file, which is never packageable. The local settings and policies carry no secrets and are what ship in the 2GP package, so subscribers install with no exposed credentials.

Talking point to explain it to anyone: "A Connected App is the old apartment building where the developer and the manager share one blueprint, so the master keys travel when you copy it. An External Client App splits that blueprint — the keys stay in a sealed file, the rules go in another — so you can safely hand out a copy and decide exactly who gets in."


9. Key terms

Consumer Key / Client ID, Consumer Secret / Client Secret, Callback URL / Redirect URI, scope, confidential vs public client, PKCE — all defined in 01-authentication-fundamentals.md. For outbound (Salesforce-as-client) containers, see Named Principal vs Per-User in 14-named-credentials-and-external-credentials.md.


Sources (Verified June 2026)


Next: 14-named-credentials-and-external-credentials.md — the modern, secret-free way for Salesforce to call OUT to external APIs.

14 - Named Credentials + External Credentials (Outbound)

One-liner: The modern, secret-free way for Salesforce to call OUT to an external API. A Named Credential holds the endpoint URL; an External Credential holds the authentication. Your Apex just calls callout:My_Named_Credential and Salesforce handles tokens. Use when: Any Salesforce -> external system callout (Apex, Flow, External Services). This is the recommended pattern for all outbound auth. Status: The Winter '23 "split" model (Named + External Credential) is the current standard. Legacy single Named Credentials still work but are no longer enhanced — use the new model.

New here? Read 01-authentication-fundamentals.md first. The previous files cover Salesforce as the server; this file is Salesforce as the client.


1. The idea in plain English

Imagine your office has one mailroom that handles all outgoing packages. You never write the recipient's address or stick on postage yourself — you just drop the package in the chute labeled with a nickname like "VendorX," and the mailroom looks up the real address and the right postage account, then sends it.

  • The Named Credential is the chute with the nickname: it knows the destination URL and which postage account to use.
  • The External Credential is the postage account: it knows how to pay — the auth protocol (OAuth, API key, basic, AWS) and the secrets.

Your Apex code only ever references the nickname (callout:VendorX). It never sees the address details or the secrets. That is the entire point: no hardcoded credentials in code, and you can rotate secrets centrally without touching a single line of Apex.


2. Named Credential vs External Credential

Salesforce split the old single Named Credential into two objects in Winter '23 so the endpoint and the authentication can be reused and secured independently.

Object Answers Holds Reusable?
Named Credential Where do I send the request? The endpoint URL, which External Credential to use, callout options (generate auth header, allow merge fields in body/header) One External Credential can back many Named Credentials
External Credential How do I authenticate? The authentication protocol, the principals (identities + secrets), and the permission-set mappings Shared across endpoints that use the same auth

Interview one-liner: "A Named Credential is the endpoint; an External Credential is the authentication. Split since Winter '23 so one auth definition can serve many endpoints, and so access is gated by permission sets."


3. External Credential authentication protocols

Pick the protocol that matches the external system. Salesforce supports a broad set:

Protocol How it authenticates Typical use
OAuth 2.0 — Browser Flow (Authorization Code Grant) User logs in via browser; callback returns tokens Per-user access to a SaaS that requires interactive consent
OAuth 2.0 — JWT Signed JWT (using a signing cert in the org) exchanged for a token Server-to-server OAuth without a browser
OAuth 2.0 — JWT Bearer JWT bearer assertion exchanged for a token Machine-to-machine, certificate-based
OAuth 2.0 — Client Credentials Client id + client secret exchanged for a token Backend integration, one shared system identity
Basic Static username + password Legacy APIs that take Basic auth
AWS Signature v4 AWS access key + secret sign each request (also AWS STS Roles Anywhere variant using a certificate) Calling AWS services (S3, API Gateway, etc.)
API Key (via Custom header) A secret value placed in a custom header APIs that authenticate with a static key
Custom You define custom headers / parameters Anything non-standard
mTLS (mutual TLS) Client and server prove identity with signed certificates High-security APIs requiring client certs

Gotcha: API Key is not a separate dropdown by itself — you implement it as a Custom external credential with a custom header whose value is the key secret. The same custom-header technique also covers Basic-style schemes some APIs expect.


4. Principals — whose identity makes the call?

A principal is the identity (and its secrets) that the External Credential uses to authenticate. The principal type decides whether everyone shares one identity or each user gets their own.

Principal type One identity or many? When to use Example
Named Principal One shared identity for all Salesforce users The external system does not care which Salesforce user — it just needs the org to authenticate A nightly sync that writes to a vendor API under a single service account
Per-User Principal Each user authenticates individually The external system must know the actual end user (per-user data, per-user audit) A user connects their own DocuSign / Google account
Client Credentials principal The configured client identity (the app's own credentials) OAuth Client Credentials flow — no user context at all Backend service calling under the app's client id/secret
flowchart LR NP[Named Principal<br/>one shared identity] --> EXT[External API] PU[Per-User Principal<br/>each user's own identity] --> EXT CC[Client Credentials<br/>the app's identity] --> EXT style NP fill:#00A1E0,color:#fff style PU fill:#9C27B0,color:#fff style CC fill:#ff9900,color:#fff style EXT fill:#607D8B,color:#fff

Permission Set mapping is REQUIRED. An External Credential principal is not usable until you map it to a permission set (or permission set group / profile) and assign that to the user. No mapping means no access — even for an admin. For Per-User principals, each user also supplies their own credentials (often via "Authenticate" on their personal settings). When a user has multiple mappings, the sequence number (lowest first) decides which principal is used.


5. How it works (sequence)

sequenceDiagram autonumber participant Apex as Apex / Flow participant NC as Named Credential<br/>(endpoint) participant EC as External Credential<br/>(auth + principal) participant API as External API Apex->>NC: callout:My_Named_Credential/path NC->>EC: Which principal? (checks user's<br/>permission-set mapping) EC->>EC: Get/refresh token or build<br/>auth header (OAuth, API key, AWS sig...) EC-->>NC: Authorization header ready NC->>API: HTTPS request to endpoint<br/>+ injected auth header API-->>NC: 200 OK + response NC-->>Apex: HttpResponse (no secrets ever in code)

Walkthrough

  1. Apex (or Flow) issues a callout to callout:My_Named_Credential/path.
  2. The Named Credential resolves which External Credential and principal apply, using the running user's permission-set mapping.
  3. The External Credential obtains or refreshes the token, or constructs the auth header (OAuth token, API key header, AWS v4 signature, etc.).
  4. Salesforce injects the Authorization header automatically.
  5. The request goes to the real endpoint over HTTPS.
  6. The response returns to Apex. At no point did your code hold a secret or a token.

6. Setup / configuration

Step-by-step (UI)

  1. Setup -> Named Credentials -> External Credentials -> New.
  2. Choose the Authentication Protocol (for example OAuth 2.0 — Client Credentials or Custom).
  3. Add a Principal (Named Principal name, or enable Per-User). Enter the credential's secrets here (client id/secret, key, username/password).
  4. Map the principal to a permission set (or PS group/profile). This is mandatory.
  5. Setup -> Named Credentials -> Named Credentials -> New.
  6. Set the URL (endpoint base) and select the External Credential from step 1.
  7. Choose callout options: Generate Authorization Header (usually on), and whether to Allow Formulas in HTTP Header / Body if you need merge fields.
  8. Assign the permission set (from step 4) to the users who will run the callout.

Apex callout — the whole point

Reference the Named Credential by name. No endpoint, no token, no secret in code.

HttpRequest req = new HttpRequest();
// 'callout:' + Named Credential name, then the path
req.setEndpoint('callout:My_Named_Credential/v1/orders');
req.setMethod('GET');

Http http = new Http();
HttpResponse res = http.send(req);
System.debug(res.getStatusCode() + ' ' + res.getBody());

For a POST with a body that needs a per-user value, use a merge field (only if "Allow Formulas in HTTP Body" is enabled on the Named Credential):

HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_Named_Credential/v1/tickets');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
// Named-credential merge field pulls a value from the External Credential at runtime
req.setBody('{"agent":"{!$Credential.My_External_Cred.Username}"}');
Http http = new Http();
HttpResponse res = http.send(req);

Why callout: matters: Salesforce swaps callout:My_Named_Credential for the real URL and injects the auth header at send time. Your code, your repo, your logs, and your packages stay free of secrets. Rotating a credential is a config change in one place — zero code deploys.

Merge fields & named-credential headers

  • {!$Credential.<ExternalCredential>.<field>} injects a stored credential value into a header or body at runtime.
  • You can define custom headers on the Named Credential (great for API Key schemes) so every callout automatically carries the key.

7. Security pitfalls & gotchas

Pitfall Why it bites Fix
Hardcoding tokens/keys in Apex Secrets leak via source control, logs, and packages; rotation means redeploys Use a Named Credential; reference callout: only
Forgetting the permission-set mapping The principal is unusable; callout fails with an auth error Map the External Credential principal to a permission set and assign it
Using Named Principal when the API needs the real user All actions appear as one service account; per-user audit breaks Use a Per-User Principal so each user authenticates individually
Putting the full URL in setEndpoint instead of callout: Loses the auto-injected auth and the secret-free benefit Always use callout:Named_Credential/path
Enabling merge fields in body/header unnecessarily Formula injection / accidental data exposure Only enable "Allow Formulas" when a merge field is genuinely needed
Mixing up which principal runs Multiple mappings can collide for one user Set the sequence number (lowest wins) deliberately
Still using legacy single Named Credentials They are not enhanced and miss new protocols/features Migrate to the Named + External Credential split model
Assuming Per-User works without user action Each user must authenticate their own principal Have users connect via "Authenticate" in their settings

8. Interview Q&A

Q: What is a Named Credential and why use one? A: A Named Credential defines the endpoint URL for an outbound callout and points to an External Credential for auth. You reference it in Apex as callout:Name, so Salesforce manages the URL and auth header for you. The benefit is no hardcoded secrets — secrets live in config, rotate centrally, and never appear in code, logs, or packages.

Q: How is a Named Credential different from an External Credential? A: The Named Credential is the endpoint; the External Credential is the authentication (protocol + principals + permission-set mappings). They were split in Winter '23 so one auth definition can back multiple endpoints and access is gated by permission sets.

Q: What authentication protocols can an External Credential use? A: OAuth 2.0 (Browser/Authorization Code, JWT, JWT Bearer, Client Credentials), Basic, AWS Signature v4 (including STS Roles Anywhere), API Key via a Custom header, Custom, and mTLS. You match the protocol to the external system.

Q: Named Principal vs Per-User Principal? A: A Named Principal is one shared identity for all users — good when the external system does not care who the Salesforce user is. A Per-User Principal has each user authenticate individually — required when the external system needs the actual end user (per-user data or audit). Client Credentials uses the app's own configured identity.

Q: Why is the permission-set mapping required? A: An External Credential principal is inert until mapped to a permission set (or PS group/profile) and assigned to users. The mapping is what grants a user the right to use that principal's secrets, enforcing least privilege. No mapping, no callout.

Q: How do you call a Named Credential from Apex? A: Set the endpoint to callout:My_Named_Credential/path and send a normal HttpRequest. Salesforce substitutes the real URL and injects the auth header. You never write the token or secret.

Q: A teammate hardcoded an API key in Apex. What do you recommend? A: Move it into an External Credential (Custom protocol with a custom header carrying the key), wire it to a Named Credential, map a permission set, and change the Apex to callout:. Then the key rotates in config with zero code change and never lands in source control.

Talking point to explain it to anyone: "It's the office mailroom. Your code just drops the package in a chute labeled 'VendorX' — the mailroom knows the real address and which postage account pays. The address and the postage stay locked in the mailroom, never written on your desk."


9. Key terms

Named Principal vs Per-User, bearer token, JWT, client id / secret, confidential vs public client — all defined in 01-authentication-fundamentals.md. For the OAuth flows these protocols implement, see 02-web-server-flow.md (Browser/Auth Code), 04-jwt-bearer-flow.md, and 05-client-credentials-flow.md. For the inbound app container, see 13-connected-apps-vs-external-client-apps.md.


Sources (Verified June 2026)


Next: 15-auth-providers.md — Auth Providers, the inbound side of social/SSO sign-in that complements outbound Named Credentials.

15 - Auth Providers

One-liner: An Auth Provider lets Salesforce itself act as the client / Service Provider to an external identity provider. This is the "Log in with Google / Microsoft / Apple" button, and the OAuth bridge that backs an External Credential. Use when: You want social or enterprise sign-on INTO Salesforce or Experience Cloud, OR you need Salesforce to make outbound OAuth callouts to a third-party API on a per-user basis. Type: Setup object (Auth. Providers) · Status: ✅ Standard, fully supported. Protocol: OAuth 2.0 / OpenID Connect (contrast with SAML SSO in 16-sso-saml-and-openid-connect.md).

New here? Read 01-authentication-fundamentals.md first for tokens, scopes, and endpoints.


1. The idea in plain English

Every flow so far had Salesforce as the bouncer (the Authorization Server issuing tokens). An Auth Provider flips the roles: now Salesforce walks up to someone else's door and says "log me in." Salesforce becomes the client.

Think of a nightclub that honors other clubs' VIP cards. Instead of making you sign up from scratch, the club at the door says "show me your Google card." It checks with Google, Google vouches for you, and the club creates a guest pass on the spot. Salesforce is the club, Google is the trusted card issuer, and the Registration Handler is the doorman who decides "is this a new guest? Make them a wristband (a Salesforce user). A returning guest? Match them to the existing one."

Two completely different jobs use the same Auth Provider plumbing:

  1. Sign-on INTO Salesforce — a user clicks "Log in with Microsoft" on your Experience Cloud login page.
  2. Outbound OAuth bridge — Salesforce needs to call the GitHub API as each user, so the Auth Provider handles the OAuth dance and hands the token to a Named Credential.

2. When to use it (and which type)

✅ Use an Auth Provider when Type to pick
Social login (consumer-facing portal) Google, Facebook, Apple, Amazon, LinkedIn, Slack, X (Twitter)
Enterprise login from Microsoft Entra ID via OAuth/OIDC Microsoft or OpenID Connect
Any standards-based OIDC provider (Okta, Auth0, PayPal, Ping) OpenID Connect
Log users in from another Salesforce org Salesforce
Third party speaks OAuth 2.0 but not OIDC (no discovery, custom token shape) Custom (Auth.AuthProviderPluginClass in Apex)
Salesforce must call an external API per user (named credential outbound) Any OAuth-capable provider, wired to an External Credential
❌ Don't use an Auth Provider when Use instead
The external IdP only speaks SAML 2.0 (XML assertions) SAML SSO — 16-sso-saml-and-openid-connect.md
You want Salesforce to be the IdP that logs users into other apps Salesforce as Identity Provider — 16
A backend job calls Salesforce with no user 05-client-credentials-flow.md / 04-jwt-bearer-flow.md

The mental split: Auth Provider = Salesforce is the consumer of an external IdP, over OAuth/OIDC. SAML SSO (file 16) is the XML-assertion alternative. Both can log users into Salesforce; the protocol is the difference.


3. How it works — sign-on INTO Salesforce (sequence)

sequenceDiagram autonumber participant U as User (Browser) participant SF as Salesforce<br/>(Auth Provider) participant IDP as External IdP<br/>(Google / Entra / Okta) participant RH as Registration Handler<br/>(Apex) U->>SF: Click "Log in with Google" SF->>U: 302 Redirect to IdP authorize endpoint U->>IDP: Log in and consent IDP->>U: 302 Redirect to Salesforce callback with code U->>SF: Browser delivers code to /services/authcallback/... SF->>IDP: POST token endpoint (code + client secret) IDP-->>SF: access_token (+ id_token, + user info) SF->>RH: createUser or updateUser (Just-In-Time) RH-->>SF: Salesforce User record SF->>U: Logged in to Salesforce / Experience Cloud

Walkthrough

1-2. The user clicks the provider button. Salesforce (acting as client) redirects the browser to the external IdP's authorize endpoint. 3-4. The user authenticates at the IdP and consents. The IdP redirects back to Salesforce's callback URL with an authorization code. 5-7. Salesforce exchanges the code for the IdP's access token (and an ID token if OIDC), then reads the user's profile from the User Info endpoint. 8-9. Salesforce calls your Registration Handler Apex class. Just-In-Time (JIT): if no matching Salesforce user exists, createUser() provisions one. If one exists, updateUser() keeps it in sync. 10. The user lands inside Salesforce with a live session. They never typed a Salesforce password.


4. Setup / config

Callback URL — Salesforce generates this after you save the provider. It has the form:

https://MyDomainName.my.salesforce.com/services/authcallback/<ProviderUrlSuffix>

You paste that URL into the external IdP as its allowed redirect URI (Google calls it "Authorized Redirect URI", PayPal calls it "Return URL").

Steps to define an OpenID Connect provider (verified from the docs):

  1. Register your app with the external IdP. Get the Client ID, Client Secret, Authorize, Token, and User Info endpoint URLs.
  2. Setup → Quick Find → Auth. Providers → New → Provider Type = OpenID Connect (or Google, Microsoft, etc.).
  3. Enter a Name and a URL Suffix (used in the generated client URLs).
  4. Consumer Key = the IdP's Client ID. Consumer Secret = the IdP's Client Secret.
  5. Paste the Authorize, Token, and User Info endpoint URLs.
  6. Check Use Proof Key for Code Exchange (PKCE) Extension to harden the flow.
  7. Set the Registration Handler Apex class (or click Automatically create a registration handler template).
  8. Set Execute Registration As to a running user. That user must have the Manage Users permission.
  9. Save. Copy the generated Callback URL back into the IdP. Test with the Test-Only Initialization URL.

A Registration Handler is required for Salesforce to generate the SSO initialization URL. No handler, no sign-on.

The Registration Handler interface (Auth.RegistrationHandler):

global class MyRegHandler implements Auth.RegistrationHandler {

    // Runs on FIRST login: provision a new Salesforce user (JIT)
    global User createUser(Id portalId, Auth.UserData data) {
        // Optional: look for an existing match first
        List<User> found = [SELECT Id FROM User WHERE Email = :data.email LIMIT 1];
        if (!found.isEmpty()) {
            return found[0]; // link instead of create
        }
        User u = new User();
        u.Username  = data.email + '.myorg';
        u.Email     = data.email;
        u.LastName  = data.lastName;
        u.FirstName = data.firstName;
        u.Alias     = data.firstName.substring(0, 1) + data.lastName.substring(0, 3);
        Profile p   = [SELECT Id FROM Profile WHERE Name = 'Standard User' LIMIT 1];
        u.ProfileId = p.Id;
        // set required locale/language/timezone fields...
        return u;
    }

    // Runs on EVERY subsequent login: keep the user in sync
    global void updateUser(Id userId, Id portalId, Auth.UserData data) {
        User u = new User(Id = userId);
        u.Email     = data.email;
        u.LastName  = data.lastName;
        u.FirstName = data.firstName;
        update u;
    }
}

The Custom type — when an external service speaks OAuth but not OIDC, you write an Apex class that extends Auth.AuthProviderPluginClass and implement:

Method Job
initiate() Return the URL where the user goes to authenticate.
handleCallback() Exchange the returned code for an access token (+ refresh token).
getUserInfo() Return Auth.UserData for the Registration Handler.
refresh() Return a new access token when the old one expires.
getCustomMetadataType() Name the Custom Metadata Type that stores your config (key, secret, URLs).

Outbound use — the OAuth bridge for Named Credentials: an Auth Provider doesn't only log people in. An External Credential of type OAuth 2.0 (Browser Flow) references an Auth Provider. When Salesforce makes a callout through the Named Credential, the Auth Provider performs the OAuth handshake and supplies the token. With a Per-User principal, each user authorizes once via their own browser, and from then on calls run in their personal context. See 14-named-credentials-and-external-credentials.md.


5. Security pitfalls & gotchas

Pitfall Why it bites Fix
Callback URL not registered at the IdP The IdP refuses to redirect back. redirect_uri_mismatch. Copy Salesforce's generated callback URL into the IdP exactly.
Execute Registration As user lacks Manage Users JIT can't create users. Login fails. Use a dedicated system/integration user with Manage Users.
Registration Handler creates a user even when one exists Duplicate users, login on the wrong record. Query for an existing match (Email / FederationIdentifier) before new User().
ID token signature is NOT validated for OIDC auth providers Salesforce validates the ID token against the Token Issuer + User Info, not the signature. Don't assume cryptographic verification. Trust the Token Issuer value and the User Info endpoint; lock both down.
Forgetting PKCE Auth code interception on the redirect. Check Use Proof Key for Code Exchange.
Consumer Secret leaking via Metadata API Old packages exported the secret. Since Nov 2022 the secret is replaced with a placeholder in Metadata API responses. Re-enter on deploy.
Treating an Auth Provider like SAML Wrong protocol, no XML assertion. Auth Provider = OAuth/OIDC. SAML is a different setup. See 16.

6. Interview Q&A

Q: What is an Auth Provider and how is it different from a Connected App? A: A Connected App / External Client App makes Salesforce the Authorization Server — an external app gets a token to call Salesforce. An Auth Provider is the mirror image: Salesforce becomes the client / Service Provider and gets a token from an external IdP. One is "apps logging into Salesforce," the other is "Salesforce logging into someone else."

Q: Name the two big use cases for an Auth Provider. A: (1) Social or enterprise sign-on INTO Salesforce or Experience Cloud ("Log in with Google"). (2) Acting as the OAuth bridge behind an External Credential so Salesforce can make outbound per-user OAuth callouts via a Named Credential.

Q: What does the Registration Handler do? A: It is the Apex class that handles Just-In-Time provisioning. createUser() runs on the first login to provision or link a Salesforce user, and updateUser() runs on later logins to keep it in sync. It is required for the SSO init URL to generate.

Q: When would you pick the Custom (Apex) type over OpenID Connect? A: When the external service supports OAuth 2.0 but not OpenID Connect — no standard discovery document or a non-standard token/userinfo shape. You extend Auth.AuthProviderPluginClass and implement initiate, handleCallback, getUserInfo, and refresh.

Q: Auth Provider vs SAML SSO — when do you use which? A: Both can log users into Salesforce. Use an Auth Provider when the IdP speaks OAuth/OIDC. Use SAML SSO when the IdP issues XML SAML assertions. OIDC is the more modern path; many enterprises still run SAML.

Q: What is the callback URL and where does it go? A: It is the Salesforce-generated redirect endpoint, https://MyDomain.my.salesforce.com/services/authcallback/<suffix>. You register it at the external IdP as the allowed redirect URI so the IdP knows where to send the user back.

Q: Does the Auth Provider validate the ID token's signature? A: For OIDC Auth Providers, no — Salesforce validates the ID token against the Token Issuer value and the User Info endpoint, but the signature itself isn't cryptographically validated. That's why pinning the issuer matters.

Talking point to explain it to anyone: "It's the 'Log in with Google' button, but inside Salesforce. Salesforce trusts Google to confirm who you are, then quietly makes or finds your Salesforce account for you."


7. Key terms

Registration Handler · Just-In-Time (JIT) provisioning · Auth.AuthProviderPluginClass · Service Provider · Identity Provider (IdP) · callback URL · openid scope · Per-User principal — base definitions in 01-authentication-fundamentals.md. Outbound wiring in 14-named-credentials-and-external-credentials.md.


Sources (Verified June 2026)


Next: 16-sso-saml-and-openid-connect.md — the SAML alternative, plus Salesforce as the Identity Provider.

16 - Single Sign-On: SAML 2.0 and OpenID Connect

One-liner: SSO lets a user authenticate once with a trusted Identity Provider (IdP) and get into Salesforce (and other apps) without re-entering credentials. Two protocols do this: SAML 2.0 (XML assertions) and OpenID Connect (OAuth + a JWT ID token). Use when: An enterprise wants central login (one corporate identity for everything), or Salesforce must log users into downstream apps. Two directions: Salesforce as Service Provider (users log INTO Salesforce) vs Salesforce as Identity Provider (Salesforce logs users into OTHER apps). Requirement: My Domain is required to configure SSO.

New here? Read 01-authentication-fundamentals.md first for AuthN vs AuthZ, tokens, and the openid scope.


1. The idea in plain English

Single Sign-On = one login, many doors. You badge in at the lobby once, and every office inside opens without badging again.

The lobby is the Identity Provider (IdP) — Okta, Microsoft Entra ID, Ping, or another Salesforce org. The offices are the Service Providers (SPs) — Salesforce, Slack, Workday. The IdP vouches for you with a signed note, and each SP trusts that note instead of asking for a password.

Direction matters, and interviewers test it:

  • Salesforce as Service Provider — the user's home base is the external IdP. They log in there, and that login gets them into Salesforce. Most common: "Our company uses Entra ID, and Salesforce is just one of the apps it signs us into."
  • Salesforce as Identity Provider — Salesforce is the home base. A user logged into Salesforce clicks a tile and gets logged into a third-party app, with Salesforce issuing the signed note.

Two note formats exist. SAML writes the note in XML. OpenID Connect writes it as a JWT carried on top of OAuth 2.0. Same goal, different envelope.


2. SAML vs OpenID Connect (the comparison interviewers want)

Dimension SAML 2.0 OpenID Connect (OIDC)
Built on Standalone XML standard (2005) OAuth 2.0 + a thin identity layer
Token / assertion SAML assertion (signed XML) ID token = signed JWT (header.payload.signature)
Transport Browser POST/redirect of XML OAuth redirects + JSON over HTTPS
Triggered by scope n/a (no scopes) The openid scope
Trust anchor X.509 certificate signing the assertion Issuer + JWKS keys; validated against Token Issuer
Best for Enterprise web SSO, legacy IdPs Modern web + mobile/SPA, APIs
Also gives you Authentication only Authentication and an access token to call APIs
Modern-ness Mature, widely deployed, heavier Lighter, JSON-native, the go-forward choice

One-line answer: "SAML is XML assertions with certificate trust — battle-tested for enterprise web SSO. OpenID Connect is OAuth 2.0 plus a JWT ID token, triggered by the openid scope — lighter, JSON-based, and better for mobile and APIs because the same flow also yields an access token."

The connective tissue: OIDC SSO is the OAuth flows from earlier files. When you run the Web Server (Authorization Code) flow with the openid scope, the ID token you get back is the SSO assertion. That is why OIDC needs no separate protocol — login is just OAuth with one extra scope.


3. How it works — SP-initiated SAML login (sequence)

This is Salesforce as the Service Provider. The user starts at Salesforce and gets bounced to the corporate IdP.

sequenceDiagram autonumber participant U as User (Browser) participant SF as Salesforce SP<br/>(My Domain) participant IDP as External IdP<br/>(Okta / Entra) U->>SF: Visit MyDomain.my.salesforce.com SF->>U: 302 Redirect with SAML AuthnRequest U->>IDP: Deliver AuthnRequest IDP->>U: Login page (password + MFA) U->>IDP: Enter credentials IDP->>U: Signed SAML assertion + RelayState U->>SF: POST assertion to Salesforce ACS SF->>SF: Validate signature against IdP certificate SF->>SF: Match subject to user (or JIT provision) SF->>U: Logged in, redirect to RelayState target

Walkthrough

1-3. The user hits their My Domain URL. Salesforce (the SP) generates a SAML AuthnRequest and redirects the browser to the IdP. 4-6. The IdP authenticates the user (password, MFA, whatever it enforces) and produces a signed SAML assertion plus a RelayState value (the page to land on). 7. The browser POSTs the assertion to Salesforce's Assertion Consumer Service (ACS) endpoint. 8. Salesforce validates the signature against the IdP's X.509 certificate, confirming the assertion is genuine and untampered. 9. Salesforce matches the assertion subject (Federation ID or username) to a user. If none exists and JIT is on, it provisions one. 10. The user is logged in and sent to the RelayState destination.

SP-initiated vs IdP-initiated: SP-initiated starts at Salesforce (above) and is the cleaner, deep-link-friendly pattern. IdP-initiated starts at the IdP's app launcher — the user clicks a Salesforce tile, and the IdP POSTs an unsolicited assertion to the ACS. SP-initiated is generally preferred because it supports deep links and is less prone to replay.


4. Setup / config

Prerequisite for everything: turn on My Domain first. SSO configuration is keyed to your My Domain.

Salesforce as SAML Service Provider (Setup → Single Sign-On Settings):

  1. Enable SAML.
  2. Gather from your IdP: the Issuer (Entity ID), the Identity Provider Login URL, and the IdP's signing certificate.
  3. Set the SAML Identity Type — Federation ID (recommended, an external-stable key on the User) or the Salesforce Username.
  4. Set the Entity ID (usually your My Domain URL) and upload the IdP certificate.
  5. Salesforce shows your ACS URL and Entity ID — give those to the IdP so it can target Salesforce.
  6. Optionally enable Just-in-Time provisioning to create/update users from the assertion.
  7. Add the SSO option to your My Domain login page (Setup → My Domain → Authentication Configuration).

Just-in-Time (JIT) provisioning — instead of pre-creating every user, Salesforce builds the user from attributes in the SAML assertion (or, for OIDC, from the Registration Handler). Required assertion fields include the subject (Federation ID or username), and for new users the standard required User fields (LastName, Email, Profile, etc.) supplied as SAML attributes.

Salesforce as OpenID Connect Service Provider — you don't configure a SAML setting; you create an Auth Provider of type OpenID Connect (see 15-auth-providers.md). The openid scope makes the IdP return an ID token, and the Registration Handler does JIT.

Salesforce as Identity Provider (the reverse direction) — Setup → Identity Provider → Enable. Salesforce can then issue SAML assertions (or act as an OIDC provider) to log users into downstream apps. Each downstream app is registered as a Connected App with SAML or OIDC service-provider settings. This is how a Salesforce-centric company uses Salesforce credentials to reach Slack, Concur, etc.

flowchart LR subgraph SP[Salesforce as Service Provider] A[User] --> B[External IdP] B -->|SAML assertion / ID token| C[Salesforce] end subgraph IDP[Salesforce as Identity Provider] D[User in Salesforce] --> E[Salesforce issues assertion] E --> F[Third-party app] end style B fill:#607D8B,color:#fff style C fill:#ff9900,color:#fff style E fill:#ff9900,color:#fff style F fill:#00A1E0,color:#fff style A fill:#4CAF50,color:#fff style D fill:#4CAF50,color:#fff

5. Security pitfalls & gotchas

Pitfall Why it bites Fix
Forgetting My Domain SSO can't be configured at all. Deploy and activate My Domain first.
Using Username as the SAML subject Usernames change (email rebrands), breaking SSO. Use a stable Federation ID.
Expired or wrong IdP certificate Signature validation fails. All logins blocked. Track certificate expiry; rotate before it lapses.
Clock skew between IdP and Salesforce Assertion NotBefore / NotOnOrAfter window fails. Sync NTP on the IdP; SAML is time-sensitive.
Confusing the two directions Configuring "Identity Provider" when you wanted to be the SP. SP = users come INTO Salesforce. IdP = Salesforce sends users OUT.
Relying on IdP-initiated only No deep linking, replay risk. Prefer SP-initiated where possible.
Assuming SAML returns an API token SAML authenticates but gives no OAuth access token. If you need API access too, use OIDC (ID token + access token) or pair with an OAuth flow.

6. Interview Q&A

Q: What is SSO and what problem does it solve? A: Single Sign-On lets a user authenticate once with a trusted Identity Provider and access many Service Providers without re-entering credentials. It centralizes identity, cuts password sprawl, and lets you enforce MFA and deprovisioning in one place.

Q: SAML vs OpenID Connect — explain the difference. A: SAML 2.0 exchanges a signed XML assertion, trusted via an X.509 certificate, and does authentication only. OpenID Connect rides on OAuth 2.0, returns a JWT ID token triggered by the openid scope, is JSON-based, and also yields an access token for API calls. OIDC is lighter and better for mobile/SPA; SAML is the entrenched enterprise standard.

Q: Salesforce as Service Provider vs as Identity Provider? A: As SP, an external IdP logs users into Salesforce. As IdP, Salesforce logs users into other apps. Same SAML/OIDC machinery, opposite direction of trust.

Q: SP-initiated vs IdP-initiated SAML? A: SP-initiated starts at Salesforce, which sends a SAML AuthnRequest to the IdP — supports deep links and is preferred. IdP-initiated starts at the IdP's app launcher, which POSTs an unsolicited assertion to Salesforce's ACS.

Q: What is Just-in-Time provisioning? A: Instead of pre-creating users, Salesforce builds or updates the user from attributes in the SAML assertion (or via the OIDC Registration Handler) on first login. New hires get a Salesforce account automatically the first time they SSO in.

Q: How does OAuth from the earlier flows connect to SSO? A: OIDC SSO is an OAuth flow. Run the Authorization Code flow with the openid scope and the ID token you receive is the proof-of-authentication assertion. SSO and OAuth aren't separate worlds — OIDC bolts identity onto OAuth.

Q: Why is My Domain required for SSO? A: My Domain gives the org a stable, brandable login host that serves as the issuer/entity ID and the ACS host. External IdPs pin to it, and the SSO login page lives there.

Talking point to explain it to anyone: "It's badging into the lobby once and every office door opening for the rest of the day. SAML and OpenID Connect are just two formats for the signed badge."


7. Key terms

Identity Provider (IdP) · Service Provider (SP) · SAML assertion · ID token (JWT) · openid scope · Federation ID · Assertion Consumer Service (ACS) · RelayState · Just-in-Time (JIT) provisioning · My Domain — base definitions in 01-authentication-fundamentals.md. OIDC sign-on setup in 15-auth-providers.md.


Sources (Verified June 2026)


Next: 17-session-security-and-token-management.md — what happens to the session and tokens after you log in.

17 - Session Security & Token Management

One-liner: Getting a token is the start, not the end. This file covers everything that happens after: how sessions time out, how to lock them down, and how to refresh, revoke, introspect, and monitor tokens. Use when: You've shipped any flow and now need to operate it safely — set timeouts, enforce IP and assurance policies, and have a kill switch. Status: ✅ Operational essentials for every integration. The final flow file in Module 03.

New here? Read 01-authentication-fundamentals.md first for the three tokens and the OAuth endpoints.


1. The idea in plain English

A token is a hotel key card (we used this image back in 02). Issuing it was the easy part. The hotel's real security work is everything after: the card stops working after checkout time (session timeout), some doors need a second factor like a fingerprint (High Assurance), the card only works inside the building (IP locking), the front desk can deactivate a lost card instantly (revocation), and security can scan a card to see if it's still valid (introspection).

Token management is lifecycle management. A key is born (issued by a flow), lives (used against the API), ages out (expires), and either renews quietly (refresh) or is killed on purpose (revoke). Treat every token like a live credential, because possession alone grants access — it is a bearer token.


2. The token lifecycle (the mental model)

flowchart LR A[Issue<br/>via any flow] --> B[Use<br/>Bearer on API calls] B --> C{Still valid?} C -->|Yes| B C -->|Expired| D[Refresh<br/>grant_type=refresh_token] D --> B C -->|Compromised<br/>or logout| E[Revoke<br/>/services/oauth2/revoke] E --> F[Dead<br/>401 INVALID_SESSION_ID] style A fill:#4CAF50,color:#fff style B fill:#9C27B0,color:#fff style D fill:#ff9900,color:#fff style E fill:#F44336,color:#fff style F fill:#607D8B,color:#fff

Read it as one sentence: a token is issued, used until it expires, then either refreshed to keep going or revoked to kill it — and a dead token returns 401 INVALID_SESSION_ID.


3. Session timeout and security levels

Session timeout (inactivity) — an access token is really a Session ID, so it dies when the session times out. The org default is 2 hours of inactivity (configurable in Setup → Session Settings). You pick a Timeout Value from a dropdown; for portal/community users the selectable range is 15 minutes to 24 hours (External Identity / High-Volume licenses can extend to 7 days). Salesforce updates the "last active" timestamp roughly every few minutes, so the effective session is the timeout plus a few minutes of granularity.

Setting Where What it does
Timeout Value Session Settings (and per Profile) Inactivity window before logout. Default ~2h.
Force logout on timeout Session Settings Actually end the session vs just warn.
Lock sessions to the IP address from which they originated Session Settings Binds the session to the login IP. Hijacked cookies from another IP are rejected.
Lock sessions to the domain in which they were first used Session Settings Prevents the session from being reused on another domain.

Session Security Levels — every session is tagged Standard or High Assurance.

Level How you get it Used for
Standard Plain username + password login. Normal access.
High Assurance Login that included MFA (or a session raised via a login flow). Gating sensitive operations.

You can set "Session Security Level Required at Login" on a Profile to force High Assurance (i.e. require MFA) to log in. You can also require High Assurance for sensitive operations (Setup → Session Settings → policies) — e.g. viewing reports, managing IP ranges, or connecting a Connected App — so a Standard session is prompted to step up to MFA before continuing. Connected Apps have a matching "High assurance session required" option.


4. Connected App IP and refresh-token policies

On the Connected App / External Client App, two policy groups govern operational security after issuance.

IP Relaxation (the dropdown), verified options:

Option Effect
Enforce IP restrictions (default) Honor the org's IP ranges / profile login-IP ranges.
Enforce IP restrictions, but relax for refresh tokens Apply IP limits at login, but skip them when refreshing.
Relax IP restrictions for activated devices Bypass org IP limits if the request comes from the app's own Trusted IP Range (web server flow).
Relax IP restrictions Run the app with no org IP restrictions.

Login IP Ranges live in two places: on the Profile (hard block — outside the range you can't log in at all) and as Trusted IP Ranges (inside the range you skip identity verification). The Connected App's IP Relaxation decides how strictly the app obeys them.

Refresh token policy (OAuth access policies):

Policy Behavior
Refresh token is valid until revoked (default) Long-lived; dies only when revoked or the user is deactivated.
Expire refresh token after n Time-boxed (e.g. 24h). After that, the user must re-authenticate.
Immediately expire refresh token if it's not used for n Idle refresh tokens die.
Refresh Token Rotation (RTR) Single-use refresh tokens: each exchange issues a new one and invalidates the old. Reuse of a spent token revokes the whole chain. Strongest option.

5. Revocation, introspection, and monitoring

Revoke a token — the kill switch. POST to the revoke endpoint:

curl https://MyDomainName.my.salesforce.com/services/oauth2/revoke \
  -d token=THE_ACCESS_OR_REFRESH_TOKEN
  • Pass an access token → that token is invalidated.
  • Pass a refresh token → it and all access tokens issued from it are revoked.
  • Success returns HTTP 200. This is your logout / compromised-credential response.

Introspect a token — ask "is this still valid, and what's in it?" (RFC 7662). Used by a resource server to validate an opaque token it received:

curl https://MyDomainName.my.salesforce.com/services/oauth2/introspect \
  -d token=THE_TOKEN \
  -d token_type_hint=access_token \
  --user CLIENT_ID:CLIENT_SECRET

The response includes active (true/false), plus scope, client_id, username, exp, and other metadata. active: false means expired or revoked — reject the request. The Connected App must have an OAuth introspection policy enabled.

Monitor and block apps — operational visibility:

  • Connected Apps OAuth Usage (Setup) lists every connected app actually being used, with user counts and a "Revoke"/block action. Revoking here kills all of that app's tokens for the org.
  • Login History and the Identity Event Log / Login Forensics show who authenticated, from where, and with which app.
  • A user can revoke their own app access from their personal settings → Connected Apps / OAuth Connected Apps.
sequenceDiagram autonumber participant RS as Resource Server participant SF as Salesforce<br/>(/services/oauth2) participant Admin as Salesforce Admin RS->>SF: POST /introspect (token) SF-->>RS: active=true, scope, exp Note over RS,SF: Token compromised — admin reacts Admin->>SF: Revoke app in "Connected Apps OAuth Usage" SF-->>Admin: All tokens for that app killed RS->>SF: POST /introspect (token) SF-->>RS: active=false (reject the request)

6. MFA, secrets, and best practices

MFA's role — Multi-Factor Authentication is required for direct Salesforce UI/login access and is the gate that produces a High Assurance session. For OAuth, MFA happens during the interactive flows (Web Server, Device); machine flows (JWT Bearer, Client Credentials) have no user, so MFA doesn't apply — which is exactly why those run as a tightly-scoped integration user.

Storing and rotating secrets — tie these back to the flows:

Practice Why
Store client secrets / refresh tokens server-side (vault, encrypted Custom Setting/Named Credential), never in a browser. Bearer tokens = possession is access. XSS/localStorage leaks are game over.
Prefer PKCE for public clients (02). Removes the need to ship a secret to mobile/SPA at all.
Rotate consumer secrets and signing certificates on a schedule. Limits the blast radius of a leak; JWT Bearer certs (04) expire anyway.
Enable Refresh Token Rotation for long-lived clients. Single-use refresh tokens detect and shut down theft.
Request least-privilege scopes (01). A narrow token is a smaller prize.
Set a refresh token expiry + IP relaxation = Enforce for sensitive integrations. Defense in depth around the longest-lived credential.
Have a revocation runbook (/revoke + block in OAuth Usage). You need a fast kill switch when a secret leaks.

7. Interview Q&A

Q: An access token expired mid-session. What does the app do? A: The API returns 401 INVALID_SESSION_ID. A user-context app silently calls /services/oauth2/token with grant_type=refresh_token to mint a new access token — no user interaction. Machine flows (JWT Bearer, Client Credentials) just re-run the flow; they have no refresh token. See 08-refresh-token-flow.md.

Q: How do you revoke a token, and what's the difference between revoking an access vs a refresh token? A: POST the token to /services/oauth2/revoke. Revoking an access token kills that one token. Revoking a refresh token kills it and every access token derived from it. Success is HTTP 200.

Q: What is token introspection and who uses it? A: A call to /services/oauth2/introspect (RFC 7662) that returns whether a token is active plus its metadata (scope, exp, client_id). A resource server uses it to validate an opaque bearer token before trusting it.

Q: Standard vs High Assurance session — what's the difference? A: A Standard session comes from a plain password login; a High Assurance session required MFA. You can force High Assurance at login per profile, or require it only for sensitive operations, prompting a step-up to MFA.

Q: What are the Connected App IP relaxation options? A: Enforce IP restrictions (default), Enforce but relax for refresh tokens, Relax for activated devices (uses the app's Trusted IP Range), and Relax IP restrictions (no org limits). They control how the app honors profile/org login-IP ranges.

Q: What is Refresh Token Rotation and why use it? A: RTR makes refresh tokens single-use — each exchange returns a fresh refresh token and invalidates the previous one. If a stolen token is replayed, the whole chain is revoked. It's the strongest refresh-token policy for long-lived clients.

Q: How do you monitor and shut down a misbehaving integration? A: Connected Apps OAuth Usage in Setup shows every app in use with user counts and a Revoke/block action that kills all its tokens. Pair it with Login History for forensics.

Talking point to explain it to anyone: "After the key card is issued, the hotel still controls everything: when it stops working, which doors need a fingerprint, that it only works inside the building, and a button at the front desk that instantly deactivates a lost card."


8. Key terms

Session ID · session timeout · Standard vs High Assurance · Login IP Ranges · IP Relaxation · Trusted IP Range · refresh token policy · Refresh Token Rotation (RTR) · revoke endpoint · introspect endpoint · bearer token · MFA — base definitions in 01-authentication-fundamentals.md. Endpoints listed in 01 §5.


Sources (Verified June 2026)


Next: head to the OAuth Flows cheat sheet to drill the grant types one more time, or back to the Module 03 README. This completes the Authentication flow files.

Module 04 - Inbound APIs (External → Salesforce)

Goal: Make Salesforce listen. Know every way an external system can push or pull data into Salesforce, and which to pick. API version: v66.0 (Spring '26). Auth: every inbound call authenticates via OAuth, see Module 03.

Inbound means the external system initiates and Salesforce responds. This is the Remote Call-In pattern in practice. This module covers the built-in APIs (no code) and the custom ones you write in Apex.


How to use this module

  1. Start with 01-standard-rest-api.md, the default for most integrations.
  2. Learn the custom options (Apex REST) for when built-in CRUD isn't enough.
  3. Use the decision tree and comparison table below to choose fast.
  4. For how callers log in, see Module 03 - Authentication.

Map of this module

# File What it covers
01 standard-rest-api Auto-generated CRUD + SOQL over JSON. The default.
02 standard-soap-api WSDL-based (Enterprise/Partner) + the login() retirement
03 apex-rest Your own REST endpoint (@RestResource)
04 apex-soap-web-services Your own SOAP endpoint (webservice keyword)
05 composite-api Many operations in one round-trip
06 connect-rest-api Chatter, Experience Cloud, Files
07 ui-api Records + layouts + metadata for building UIs

Which inbound API? (decision tree)

flowchart TD Start([External system calls Salesforce]) --> Q{What do you need?} Q -->|Standard CRUD or SOQL| REST[Standard REST API<br/>JSON, modern default] Q -->|Legacy partner needs a WSDL| SOAP[Standard SOAP API] Q -->|Custom logic, REST contract| AREST[Apex REST] Q -->|Custom logic, SOAP contract| ASOAP[Apex SOAP web services] Q -->|Many operations in one call| COMP[Composite API] Q -->|Chatter / Experience / Files| CONN[Connect REST API] Q -->|Render a Salesforce-like UI| UIAPI[UI API] style REST fill:#00A1E0,color:#fff style AREST fill:#4CAF50,color:#fff style COMP fill:#ff9900,color:#fff style SOAP fill:#9C27B0,color:#fff style ASOAP fill:#9C27B0,color:#fff style CONN fill:#607D8B,color:#fff style UIAPI fill:#607D8B,color:#fff

Comparison table

API What it's for Format Custom logic? When to use
Standard REST (01) CRUD + SOQL on objects JSON No The default for modern clients.
Standard SOAP (02) CRUD on objects via WSDL XML No Legacy partners needing a contract.
Apex REST (03) Custom REST endpoint JSON Yes Tailored logic/contract over REST.
Apex SOAP (04) Custom SOAP endpoint XML Yes Legacy SOAP consumer + custom logic.
Composite (05) Bundle many calls JSON No Cut round-trips and API calls.
Connect REST (06) Chatter / Experience / Files JSON No The social/collaboration layer.
UI API (07) Records + layout + metadata JSON No Build a UI that mirrors Salesforce.

What changed worth knowing (2025-2026)

  • SOAP API login() is retiring in Summer '27 (gone from API v65.0+, disabled by default in new orgs, gated by the new Use Any API Auth permission from Summer '26). Authenticate inbound calls with OAuth via External Client Apps. See 02-standard-soap-api.md and Module 03.
  • REST + JSON is the default for new inbound builds; SOAP is legacy-but-supported.

Interview rapid-fire

Q: Standard REST vs Apex REST? → Standard REST is built-in CRUD/SOQL, no code. Apex REST (@RestResource) is a custom endpoint for business logic or a tailored contract.

Q: How do you avoid duplicate inbound records? → Upsert by External Id, which is idempotent against retries.

Q: Create an Account and its Contacts in one inbound call? → Composite API (sObject Tree or Composite Graph), fewer round-trips and transactional.

Q: Security gotcha with Apex REST? → It runs in system context, so it does not auto-enforce FLS/sharing. Use with sharing and check CRUD/FLS explicitly. (The Standard REST API enforces the calling user's permissions.)


Sources (Verified June 2026)

Each file has its own Sources section with the specific official doc.

01 - Standard REST API

One-liner: A ready-made HTTP API that lets an external system create, read, update, delete, and query Salesforce records, no Apex required. Direction: External → Salesforce (inbound). Format: JSON (XML optional). Auth: OAuth 2.0 Bearer token. Use when: An outside app needs standard CRUD or SOQL on existing objects. The default inbound API.

This is Module 04, inbound APIs (external systems calling into Salesforce). New to the vocabulary? See Module 01. For how the caller authenticates, see Module 03.


1. The idea in plain English

The Standard REST API is a vending machine for your data. Salesforce automatically exposes every object (standard and custom) through predictable HTTP URLs. An external app inserts the right "coins" (an OAuth token and a JSON body) and pulls out exactly what it asked for. You did not build the machine. Salesforce generated it for you the moment the object existed.

Because it is auto-generated and resource-based, you do not write or deploy anything. You point HTTP verbs (GET, POST, PATCH, DELETE) at object URLs and Salesforce does the rest, enforcing the calling user's permissions, validation rules, and sharing along the way.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
Standard CRUD or SOQL on existing objects. You need custom logic / a custom contract → 03-apex-rest.md.
The caller is a modern app that speaks JSON. Legacy partner that requires a WSDL → 02-standard-soap-api.md.
You want zero code on the Salesforce side. Many operations in one round-trip → 05-composite-api.md.
Single or small batches of records. Millions of records → Bulk API 2.0 (Module 07).

Real-world examples: a website posts a new Lead, a billing app patches an Account's status, a mobile app queries Contacts.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant EXT as External App participant SF as Salesforce REST API<br/>/services/data participant DB as Salesforce Data EXT->>SF: POST /sobjects/Account (Bearer token + JSON) SF->>SF: Check OAuth token, perms, validation SF->>DB: Insert record DB-->>SF: New record Id SF-->>EXT: 201 Created + id, success, errors

Walkthrough

  1. The external app sends an HTTP request to a versioned endpoint with an OAuth Bearer token.
  2. Salesforce validates the token, then the running user's permissions, FLS, and validation rules.
  3. The operation hits the database. 4-5. Salesforce returns a status code and a JSON body (the new id on create, the record on read).

4. The actual requests

Base: https://MyDomainName.my.salesforce.com/services/data/v66.0/

Action Method + path
Create POST /sobjects/Account
Read GET /sobjects/Account/{id}
Update PATCH /sobjects/Account/{id}
Delete DELETE /sobjects/Account/{id}
Upsert by external id PATCH /sobjects/Account/External_Id__c/{value}
Query (SOQL) GET /query/?q=SELECT+Id,Name+FROM+Account

Create request

POST /services/data/v66.0/sobjects/Account
Authorization: Bearer 00D...!AQ...
Content-Type: application/json

{ "Name": "Acme Corp", "Industry": "Technology" }

Response

{ "id": "001bn00000ABCDeAAH", "success": true, "errors": [] }

Upsert is your friend: PATCH on an External Id field creates or updates in one idempotent call, so retries do not create duplicates. This is the backbone of safe inbound integrations.


5. Design considerations and gotchas

Consideration Why it matters What to do
Authentication Every call needs a valid OAuth token. Use a flow from Module 03 (JWT Bearer or Client Credentials for server-to-server).
API allocations Calls count against the org's 24-hour API limit. Batch with Composite; avoid chatty per-record loops.
Always version the URL v66.0 pins behavior; omitting it breaks later. Hardcode the version your client was tested on.
Runs as the user The token's user governs permissions, FLS, sharing. Give the integration user least-privilege access.
Idempotency Network retries can double-insert. Prefer upsert by External Id.
No custom logic The API only does CRUD/query. Need branching or orchestration? Use Apex REST.

6. Interview Q&A

Q: What is the Standard REST API? A: An auto-generated, resource-based HTTP API for CRUD and SOQL on Salesforce objects, using standard verbs and JSON, authenticated with OAuth. No code to deploy.

Q: REST API vs Apex REST? A: Standard REST is built-in CRUD/query on objects. Apex REST is a custom endpoint you write with @RestResource when you need business logic or a tailored request/response shape.

Q: How do you avoid duplicates on inbound creates? A: Use upsert by External Id (PATCH /sobjects/Object/ExtId__c/value). It is idempotent, so a retried call updates instead of inserting a duplicate.

Q: How do you reduce API call counts? A: Bundle operations with the Composite API (or sObject Collections), and use Bulk API 2.0 for very large volumes.

Q: Whose permissions apply on an inbound call? A: The user tied to the OAuth token. Salesforce enforces that user's profile, permission sets, FLS, sharing, and validation rules.

Talking point to explain it to anyone: "Salesforce hands every object a standard set of web URLs. Apps read and write records through those URLs with a token, and Salesforce checks the rules each time."


7. Key terms

REST, endpoint, OAuth Bearer token, SOQL, upsert, External Id, API allocation - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 02-standard-soap-api.md - the older WSDL-based API and the login() retirement.

02 - Standard SOAP API

One-liner: The original WSDL-based, XML-over-HTTPS API that lets an external system do CRUD and SOQL against Salesforce with a strongly-typed contract. Direction: External → Salesforce (inbound). Format: XML (SOAP envelope). Auth: OAuth 2.0 Bearer token (legacy login() is retiring). Use when: A legacy partner or tool requires a WSDL or a strongly-typed contract. Otherwise prefer 01-standard-rest-api.md.

This is Module 04, inbound APIs (external systems calling into Salesforce). New to the vocabulary? See Module 01. For how the caller authenticates, see Module 03.


1. The idea in plain English

If the REST API is a vending machine, the SOAP API is a formal contract with a notary. Before you can talk to Salesforce, you download a WSDL (a machine-readable rulebook). Your tooling reads it and generates strongly-typed classes, so every field, type, and operation is fixed and validated up front. You then exchange XML envelopes over HTTPS instead of light JSON.

This formality is the point. Older enterprise platforms, integration middleware, and partner tools were built around WSDLs and strict typing. SOAP API gives them a stable, predictable, typed door into Salesforce. It is mature and fully supported, but it is no longer the modern default. New integrations should reach for REST first.

There are two flavors of WSDL, and choosing the right one is the first real decision.

WSDL Typing Scope Best for
Enterprise WSDL Strongly typed Org-specific (mirrors your exact objects and fields) A tool integrating with one org that wants compile-time type safety.
Partner WSDL Loosely typed (generic name/value) Generic (works across any org) A tool (ISV, middleware) integrating with many orgs with different schemas.

The Enterprise WSDL changes whenever your schema changes, so you re-download it after metadata edits. The Partner WSDL stays generic, so a multi-org product consumes it once per version.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
A legacy partner or middleware requires a WSDL. A modern app that speaks JSON → 01-standard-rest-api.md.
You want a strongly-typed, contract-first integration. You need custom logic or a tailored contract → 03-apex-rest.md.
The tool already has generated SOAP stubs you must reuse. Many operations in one round-trip → 05-composite-api.md.
An ISV product must run generically across many orgs (Partner WSDL). Millions of records → Bulk API 2.0 (Module 07).

Real-world examples: an on-prem ERP that only speaks SOAP upserting Accounts, a legacy MDM tool syncing Contacts via the Enterprise WSDL, an ISV data-quality product using the Partner WSDL across its customer base.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant EXT as External App participant WS as WSDL Contract participant SF as Salesforce SOAP API<br/>/services/Soap participant DB as Salesforce Data EXT->>WS: Download WSDL, generate typed stubs EXT->>SF: SOAP request XML over HTTPS<br/>OAuth Bearer in header SF->>SF: Validate token, perms, validation rules SF->>DB: create / update / upsert / query DB-->>SF: Result rows or IDs SF-->>EXT: SOAP response XML

Walkthrough

  1. The external tool downloads the WSDL (Enterprise or Partner) and generates typed client code from it once.
  2. It sends a SOAP XML envelope over HTTPS to the org endpoint, carrying an OAuth access token for authentication.
  3. Salesforce validates the token, then the running user's permissions, FLS, sharing, and validation rules.
  4. The core operation runs against the database.
  5. Salesforce returns a SOAP XML response with result rows, IDs, success flags, and any errors.

4. The actual requests

Endpoints (My Domain host, versioned). The path segment encodes the WSDL type:

WSDL Endpoint path Example
Enterprise /services/Soap/c/ https://MyDomainName.my.salesforce.com/services/Soap/c/66.0
Partner /services/Soap/u/ https://MyDomainName.my.salesforce.com/services/Soap/u/66.0

Memory hook: c = customer/enterprise (your schema), u = universal/partner (generic).

Core operations (the typed equivalents of CRUD + SOQL):

Operation What it does
create() Insert one or more records.
retrieve() Fetch records by ID for given fields.
update() Update existing records.
upsert() Create or update by External Id (idempotent).
delete() Delete records by ID.
query() Run a SOQL query, returns the first batch.
queryMore() Page through the next batch using the queryLocator cursor.

Sample SOAP request (modern auth: OAuth access token in the SessionHeader, no login() call):

POST /services/Soap/c/66.0 HTTP/1.1
Host: MyDomainName.my.salesforce.com
Content-Type: text/xml; charset=UTF-8
SOAPAction: ""

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:urn="urn:enterprise.soap.sforce.com">
  <soapenv:Header>
    <urn:SessionHeader><urn:sessionId>00D...!AQ...</urn:sessionId></urn:SessionHeader>
  </soapenv:Header>
  <soapenv:Body>
    <urn:create>
      <urn:sObjects xsi:type="urn:Account" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <Name>Acme Corp</Name>
        <Industry>Technology</Industry>
      </urn:sObjects>
    </urn:create>
  </soapenv:Body>
</soapenv:Envelope>

Response (trimmed): a createResponse with a result containing id, success, and any errors. Same data as REST, wrapped in XML.

The sessionId is just your OAuth access token. Get it from a modern OAuth flow (see section 5), then place it in SessionHeader. You do not need the old login() call.


5. Design considerations and gotchas

The single most important current fact lives in the first row. Read it carefully.

Consideration Why it matters What to do
login() is retiring SOAP API login() in versions 31.0-64.0 is being retired in Summer '27. It is not available in API v65.0+, and it is disabled by default in newly created orgs. From Summer '26, in new orgs a user needs the new "Use Any API Auth" permission to authenticate via login(). Migrate API authentication to OAuth via External Client Apps: Web Server, JWT Bearer, or Client Credentials flow. Pass the OAuth access token as the sessionId. Do not build new integrations on login().
Enterprise WSDL drifts It mirrors your exact schema, so adding a field changes the contract. Re-download the WSDL and regenerate stubs after metadata changes.
Partner WSDL is generic No object/field types, so you handle name/value pairs yourself. Use it for multi-org tools. Accept the extra mapping code as the cost of portability.
Verbose XML payloads SOAP envelopes are larger and slower to parse than JSON. Prefer REST for new, chatty, or mobile clients. Reserve SOAP for legacy contracts.
API allocations Calls count against the org's 24-hour API limit, like REST. Batch records per call. Use queryMore() to page instead of huge single pulls.
Runs as the user The token's user governs permissions, FLS, and sharing. Give the integration user least-privilege access.
Idempotency A retried create() double-inserts. Prefer upsert() by External Id.

6. Interview Q&A

Q: What is the SOAP API and how does it differ from REST? A: The SOAP API is the original WSDL-based, strongly-typed, XML-over-HTTPS API for CRUD and SOQL. REST is lighter, resource-based, and uses JSON. Both do the same core work, but REST is the modern default. SOAP is for legacy or contract-first integrations.

Q: Enterprise WSDL vs Partner WSDL? A: Enterprise is strongly typed and org-specific. It mirrors your exact objects and fields, ideal for a single-org integration that wants type safety. Partner is loosely typed and generic, ideal for ISV or middleware tools that must work across many orgs with different schemas.

Q: What is happening to SOAP API login()? A: It is retiring in Summer '27 for versions 31.0-64.0, is gone in v65.0+, and is off by default in new orgs. From Summer '26, new orgs require the "Use Any API Auth" permission to use it. The fix is to authenticate with OAuth through External Client Apps (Web Server, JWT Bearer, or Client Credentials) and pass that token as the session ID.

Q: How do you authenticate a SOAP call without login()? A: Obtain an OAuth access token from a server-to-server flow (JWT Bearer or Client Credentials), then put it in the SOAP SessionHeader as the sessionId. The SOAP operations work unchanged.

Q: What are query() and queryMore()? A: query() runs a SOQL statement and returns the first batch of rows plus a queryLocator cursor. queryMore() uses that cursor to fetch subsequent batches, which is how you page through large result sets in SOAP.

Talking point to explain it to anyone: "SOAP is the old, formal way in. You download a rulebook called a WSDL, your code learns the exact shapes, and you exchange strict XML. It still works, but the modern door is REST, and the old password-style login is being switched off."


7. Key terms

WSDL, SOAP, Enterprise WSDL, Partner WSDL, XML envelope, queryLocator, upsert, External Id, OAuth Bearer token, External Client App - defined in Module 01 vocabulary and the README. For OAuth flows, see Module 03.


Sources (Verified June 2026)


Next: 03-apex-rest.md - building your own custom REST endpoints in Apex.

03 - Apex REST

One-liner: A custom REST endpoint you write in Apex when the standard API can't give you the business logic, validation, or contract you need. Direction: External → Salesforce (inbound). Format: JSON (you control the shape). Auth: OAuth 2.0 Bearer token. Use when: You need custom logic, orchestration, validation, or a tailored request/response that the Standard REST API cannot express.

This is Module 04, inbound APIs (external systems calling into Salesforce). New to the vocabulary? See Module 01. For how the caller authenticates, see Module 03.


1. The idea in plain English

The Standard REST API is a vending machine: predictable, but it only dispenses standard CRUD. Apex REST is your own custom counter where you decide exactly what goes in and what comes out. You write an Apex class, label it as a REST resource, and Salesforce publishes it at a URL you choose.

Behind that URL you can do anything Apex can do: validate the payload, touch several objects in one call, run business rules, shape a clean response that hides your internal data model. The caller still sends plain JSON with an OAuth token. They never see the Apex. They just see a tidy API that does precisely the job you designed.

In short: reach for Apex REST when the answer to "can the standard API do this?" is no, because you need logic or a contract the generic API can't provide.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You need custom business logic or validation on inbound calls. Plain CRUD or SOQL on objects → 01-standard-rest-api.md.
You want a tailored request/response contract that hides internals. A legacy SOAP consumer needs a WSDL → 04-apex-soap-web-services.md.
One call must orchestrate several objects or steps. Generic bundling of standard calls → 05-composite-api.md.
You want to expose only a narrow, purpose-built surface. Millions of records → Bulk API 2.0 (Module 07).

Real-world examples: a partner posts an order that creates an Account, Contact, and Opportunity in one validated call; a "submit application" endpoint that runs eligibility logic; a mobile app fetching a custom-shaped dashboard payload that a single SOQL can't build.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant EXT as External App participant SF as Apex REST Endpoint<br/>/services/apexrest participant APX as Your Apex Class participant DB as Salesforce Data EXT->>SF: POST /services/apexrest/Cases/ JSON + Bearer SF->>SF: Validate OAuth token SF->>APX: Route to @HttpPost method APX->>APX: Validate input, run business logic APX->>DB: Insert / update across objects DB-->>APX: Results APX-->>EXT: Custom JSON response + status code

Walkthrough

  1. The external app sends an HTTP request with a JSON body and an OAuth Bearer token to your /services/apexrest/... URL.
  2. Salesforce validates the token, then routes the request based on the HTTP verb to the matching annotated method.
  3. Your Apex method reads the request via RestContext.request, validates and applies business logic.
  4. It performs DML, possibly across several objects, as one transaction.
  5. It returns an Apex object (auto-serialized to JSON) or writes a custom body and status code to RestContext.response.

4. The actual code

Base path: https://MyDomainName.my.salesforce.com/services/apexrest/ + your urlMapping.

A class becomes an Apex REST resource when it is global and annotated with @RestResource carrying a urlMapping. Methods are mapped to HTTP verbs with annotations:

Annotation HTTP verb Typical use
@HttpGet GET Read / fetch a custom payload.
@HttpPost POST Create, or run an action.
@HttpPatch PATCH Partial update / upsert.
@HttpPut PUT Full update / replace.
@HttpDelete DELETE Delete.

Sample class (custom contract over Case, with sharing enforced for the running user):

@RestResource(urlMapping='/Cases/*')
global with sharing class CaseResource {

    // GET /services/apexrest/Cases/{caseId}
    @HttpGet
    global static Case getCase() {
        RestRequest req = RestContext.request;
        String caseId = req.requestURI.substringAfterLast('/');
        return [SELECT Id, Subject, Status, Priority
                FROM Case WHERE Id = :caseId];
    }

    // POST /services/apexrest/Cases/  with JSON body
    @HttpPost
    global static String createCase(String subject, String priority) {
        if (String.isBlank(subject)) {
            RestContext.response.statusCode = 400;       // custom status
            return 'Subject is required';
        }
        Case c = new Case(Subject = subject, Priority = priority, Origin = 'API');
        insert c;                                         // business logic + DML
        RestContext.response.statusCode = 201;
        return c.Id;                                      // auto-serialized to JSON
    }
}

Request

POST /services/apexrest/Cases/
Authorization: Bearer 00D...!AQ...
Content-Type: application/json

{ "subject": "Login broken", "priority": "High" }

Response: 201 with the new Case Id as a JSON string. Notice the caller never sees a raw Case insert. They see your contract.

Auto-serialization: return an Apex object (sObject, custom class, or list) and Salesforce serializes it to JSON for you. Method parameters are auto-deserialized from the JSON body by name. For full control over headers, body, and status, write to RestContext.response directly.


5. Design considerations and gotchas

Consideration Why it matters What to do
Runs in system context An Apex REST method executes in system context: object permissions, FLS, and sharing are NOT enforced automatically, regardless of the calling user. This is a real security trap. Add with sharing and enforce FLS/CRUD explicitly (e.g. WITH USER_MODE, Security.stripInaccessible, or Schema checks). Put business logic in a with sharing class and call it from the resource.
One method per verb A class can have only one method per HTTP verb (one @HttpGet, one @HttpPost, etc.). Branch inside the method on the URI or body. Split distinct resources into separate classes with their own urlMapping.
Must be global The class and its annotated methods must be global and static to be reachable. Declare the class global; declare each annotated method global static.
Counts against API limits Every call consumes the org's 24-hour API allocation, like the standard API. Design coarse-grained endpoints. Avoid making clients call in tight loops.
Governor limits apply Your Apex runs under normal governor limits (SOQL, DML, CPU). Bulkify. One call should not do per-record DML in a loop.
Expose only what you need A custom endpoint is attack surface. Return the minimum fields. Validate every input. Keep the contract narrow.
Authentication Every call needs a valid OAuth token. Use a flow from Module 03 (JWT Bearer or Client Credentials for server-to-server).

6. Interview Q&A

Q: What is Apex REST and when do you use it over the Standard REST API? A: Apex REST is a custom REST endpoint you build with @RestResource on a global class. Use it when you need business logic, validation, multi-object orchestration, or a tailored contract. The Standard REST API only does generic CRUD and SOQL.

Q: How do you define an Apex REST resource? A: Create a global class annotated with @RestResource(urlMapping='/path/*'), then add global static methods annotated with @HttpGet, @HttpPost, @HttpPatch, @HttpPut, or @HttpDelete. It is published under /services/apexrest/.

Q: In what context does an Apex REST method run? Why does that matter? A: It runs in system context, so object permissions, FLS, and sharing are not enforced by default, even though the request is authenticated as a specific user. You must add with sharing and enforce FLS/CRUD explicitly (user mode, stripInaccessible, or Schema checks) to avoid exposing data the user shouldn't see.

Q: How many methods per HTTP verb can a class have? A: Exactly one per verb. If you need multiple behaviors for the same verb, branch on the URI or payload inside that method, or split into separate resource classes.

Q: How do request and response bodies work? A: Read the incoming request via RestContext.request (or let parameters auto-deserialize from JSON). Return an Apex object and Salesforce auto-serializes it to JSON, or write to RestContext.response for full control over status code, headers, and body.

Talking point to explain it to anyone: "It's a door I build myself. The standard API only does generic CRUD, so when I need real logic or a clean custom shape, I write a little Apex class, give it a web address, and outsiders call it with a token."


7. Key terms

@RestResource, urlMapping, @HttpGet/@HttpPost/@HttpPatch/@HttpPut/@HttpDelete, RestContext, system context, with sharing, FLS, OAuth Bearer token - defined in Module 01 vocabulary and the README. For OAuth flows, see Module 03.


Sources (Verified June 2026)


Next: 04-apex-soap-web-services.md - custom SOAP endpoints in Apex for legacy consumers.

04 - Apex SOAP Web Services

One-liner: A custom SOAP endpoint you write in Apex by adding the webservice keyword. Salesforce generates a WSDL for it so legacy SOAP clients can call your logic. Direction: External → Salesforce (inbound). Format: XML (SOAP). Auth: OAuth 2.0 Bearer token (session ID). Use when: A legacy SOAP consumer needs to call custom Apex logic and must have a WSDL. For new custom services, prefer 03-apex-rest.md.

This is Module 04, inbound APIs (external systems calling into Salesforce). New to the vocabulary? See Module 01. For how the caller authenticates, see Module 03.


1. The idea in plain English

This is the SOAP cousin of Apex REST. You still write a custom Apex method that does whatever you need, but instead of exposing it as a JSON REST endpoint, you expose it as a SOAP service. You mark a method with the webservice keyword, and Salesforce generates a WSDL from your class. A legacy SOAP client downloads that WSDL, generates typed stubs, and calls your method with XML envelopes.

Think of it as publishing a custom function in the formal, contract-first language that older enterprise systems already speak. The standard SOAP API (02-standard-soap-api.md) only gives them generic CRUD. This lets them call your business logic the same WSDL way.

It works and is fully supported, but it is the older approach for custom services. For anything new, Apex REST is preferred because it is lighter, simpler to consume, and easier to evolve. Reach for Apex SOAP only when a consumer genuinely requires SOAP.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
A legacy SOAP client must call custom Apex logic and needs a WSDL. Building a new custom service → 03-apex-rest.md (preferred).
The consumer's tooling is contract-first / strongly typed and SOAP-only. The consumer only needs standard CRUD over SOAP → 02-standard-soap-api.md.
You must match an existing SOAP integration contract. A modern JSON client → 01-standard-rest-api.md.
Bulk or many operations → Bulk API 2.0 / Composite.

Real-world examples: an on-prem billing platform that only generates SOAP stubs calling a custom chargeAccount() method; a legacy middleware flow that must invoke a multi-step Apex routine through a WSDL it already manages.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant DEV as You (Admin/Dev) participant SF as Salesforce participant EXT as Legacy SOAP Client participant APX as Your webservice Method DEV->>SF: Write global class with webservice static method DEV->>SF: Setup, Apex Classes, Generate WSDL SF-->>DEV: Custom WSDL file DEV->>EXT: Hand WSDL to the consumer EXT->>EXT: Generate typed stubs from WSDL EXT->>APX: SOAP request XML + session ID APX->>APX: Run custom logic + DML APX-->>EXT: SOAP response XML

Walkthrough

  1. You write a global Apex class with one or more webservice static methods containing your logic.
  2. In Setup you open the class and click Generate WSDL. Salesforce produces a WSDL describing your method.
  3. You hand the WSDL to the external consumer, who generates typed client stubs from it.
  4. The client sends a SOAP XML request with an OAuth access token as the session ID.
  5. Your method runs (custom logic plus DML) and returns a SOAP XML response.

4. The actual code

Endpoint: custom Apex SOAP services are published under /services/Soap/class/{ClassName}, e.g. https://MyDomainName.my.salesforce.com/services/Soap/class/AccountManager.

The webservice keyword rules (this is the exam-favorite part):

  • The method must use webservice and must be static.
  • Methods with webservice are inherently global, so the enclosing class must be global.
  • You cannot apply webservice to a class itself, only to methods and member variables.
  • It is valid on top-level (outer) class methods and variables, and on inner class member variables.

Sample class:

global class AccountManager {

    // Inner class members exposed in the generated WSDL
    global class AccountResult {
        webservice String accountId;
        webservice String message;
    }

    // Becomes a SOAP operation in the generated WSDL
    webservice static AccountResult createAccount(String name, String industry) {
        AccountResult r = new AccountResult();
        Account a = new Account(Name = name, Industry = industry);
        insert a;                       // your custom logic + DML
        r.accountId = a.Id;
        r.message = 'Created';
        return r;
    }
}

After saving, go to Setup, Apex Classes, open AccountManager, Generate WSDL. Salesforce builds a WSDL exposing createAccount as a typed SOAP operation, with AccountResult as a complex type. The consumer imports it and calls the operation with a SOAP envelope, passing the OAuth access token as the sessionId in the SOAP header (same auth model as the Standard SOAP API).

Two WSDLs, do not confuse them. The Enterprise/Partner WSDL (Module 04-02) describes the standard API. The WSDL you generate here describes only your custom class. A consumer of your method needs your WSDL.


5. Design considerations and gotchas

Consideration Why it matters What to do
Apex REST is preferred Apex SOAP is the older model for custom services. SOAP is heavier and harder to evolve. Default to Apex REST for new work. Use Apex SOAP only for SOAP-only consumers.
Generate the WSDL in Setup Consumers need the WSDL, and it changes when your method signatures change. After signature changes, regenerate the WSDL and re-share it so stubs stay in sync.
Type restrictions on webservice methods Not every Apex type is allowed as a webservice parameter or return (e.g. maps, certain collections and types are restricted). Use supported types (primitives, sObjects, and lists of them, simple custom classes). Keep signatures simple.
webservice keyword placement Must be on a static method in a global class, or a member variable. It cannot decorate a class. Make the class global, methods webservice static. Expose fields with webservice on members.
Runs as the user The session ID's user governs permissions, FLS, and sharing for the request. Use a least-privilege integration user. Still enforce sharing/FLS in your logic where needed.
Authentication The session ID is an OAuth access token. Legacy login() is retiring (see 04-02). Authenticate with OAuth (JWT Bearer or Client Credentials) via External Client Apps. See Module 03.
Counts against API limits + governor limits Calls consume the org's API allocation and run under Apex governor limits. Design coarse, bulk-safe operations.

6. Interview Q&A

Q: What is an Apex SOAP web service? A: A custom Apex method exposed as a SOAP operation by adding the webservice keyword. Salesforce generates a WSDL for the class so legacy SOAP clients can call your custom logic with XML, under /services/Soap/class/.

Q: What are the rules for the webservice keyword? A: The method must be static and marked webservice, which makes it inherently global, so the class must be global. You cannot put webservice on a class, only on methods and member variables. It works on outer-class methods/variables and inner-class members.

Q: How is this different from the standard SOAP API? A: The standard SOAP API gives generic CRUD/SOQL via the Enterprise/Partner WSDL. An Apex SOAP web service exposes your custom logic via a WSDL you generate from your own class.

Q: Apex SOAP or Apex REST for a new custom service? A: Apex REST, almost always. It is lighter, JSON-based, and easier to consume and evolve. Use Apex SOAP only when the consumer is SOAP-only or must match an existing SOAP contract.

Q: How does a consumer get and use the service? A: You click Generate WSDL on the class in Setup, hand them the WSDL, they generate typed stubs, then call the operation with a SOAP envelope, passing an OAuth access token as the session ID.

Talking point to explain it to anyone: "Same idea as a custom REST endpoint, but in the older, formal SOAP language. I tag a method with one keyword, Salesforce writes a rulebook (a WSDL) for it, and a legacy system uses that rulebook to call my code."


7. Key terms

webservice keyword, global class, Generate WSDL, SOAP, session ID, complex type, inner class members, OAuth Bearer token - defined in Module 01 vocabulary and the README. For OAuth flows, see Module 03.


Sources (Verified June 2026)


Next: 05-composite-api.md - bundling many operations into one round-trip.

05 - Composite API

One-liner: Bundle many Salesforce operations into one HTTP round-trip, optionally chaining and rolling them back as a single transaction. Direction: External → Salesforce (inbound). Format: JSON. Auth: OAuth 2.0 Bearer token. Use when: You would otherwise make several Standard REST calls that are related, dependent, or should succeed or fail together.

This is Module 04, inbound APIs (external systems calling into Salesforce). New to the vocabulary? See Module 01. For how the caller authenticates, see Module 03. This builds on 01-standard-rest-api.md.


1. The idea in plain English

The Standard REST API is a vending machine: one coin, one snack. The Composite API is a food court order on a single tray. You write down everything you want, hand it over once, and the kitchen prepares it all in one trip. You can even say "use the table number from my first order for my second order" (chaining), or "if any one dish is out of stock, cancel the whole tray" (all-or-none).

In Salesforce terms: instead of firing five separate HTTP requests (each costing a network round-trip and an API call), you send one request whose body contains a list of subrequests. Salesforce runs them in order and returns one combined response. The headline win is fewer round-trips, fewer API calls, and the option to treat the whole bundle as a single transaction.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
Several related calls that should be one network trip. A single CRUD or SOQL call → 01-standard-rest-api.md.
One call depends on the result of another (chaining). Operations are unrelated and order does not matter → Composite Batch.
You want all-or-none transactional behavior. Millions of records → Bulk API 2.0 (Module 07).
Create a parent and its children in one shot. You need custom orchestration logic → 03-apex-rest.md.

Real-world examples: create an Account plus its Contacts and an Opportunity atomically, look up a record then update a related one using the returned Id, or insert 200 order lines in a single nested payload.


3. The family at a glance

Four resources live under /services/data/v66.0/composite/. Pick by chaining and volume.

Resource Max work per call Chaining? Transaction Best for
Composite Up to 25 subrequests ✅ Yes, @{refId.field} Optional allOrNone Dependent, mixed operations in order
Composite Batch Up to 25 subrequests ❌ No Each independent Unrelated calls, one round-trip
sObject Tree Up to 200 records total Implicit by nesting Always all-or-none One parent with nested children
Composite Graph Up to 500 records per request ✅ Yes, across graphs Each graph is atomic Large, related, multi-graph loads

4. How it works (the Composite resource)

sequenceDiagram autonumber participant EXT as External App participant SF as Composite Endpoint<br/>/composite participant DB as Salesforce Data EXT->>SF: POST /composite (one JSON body, 25 subrequests) SF->>DB: Run subrequest 1 (create Account) DB-->>SF: refId NewAccount returns Id SF->>DB: Run subrequest 2 using @{NewAccount.id} DB-->>SF: Child Contact created Note over SF,DB: If allOrNone=true and any step fails, roll back ALL SF-->>EXT: 200 OK + array of subrequest results

Walkthrough

  1. One POST carries a body with a compositeRequest array of up to 25 subrequests, each tagged with a referenceId. 2-3. Subrequest 1 creates an Account. Its result is stored under its referenceId.
  2. Subrequest 2 references the new Id with @{NewAccount.id}, so the child links to the parent without a second round-trip.
  3. If allOrNone is true, any failure rolls back every completed subrequest.
  4. Salesforce returns one response: an array of results, each with its own HTTP status code and body.

Chaining is the superpower. Only the Composite resource (and Composite Graph) can reference earlier results. Composite Batch runs its 25 subrequests independently with no chaining.


5. The actual requests

Base: https://MyDomainName.my.salesforce.com/services/data/v66.0/

Resource Method + path
Composite POST /composite
Composite Batch POST /composite/batch
sObject Tree POST /composite/tree/{SObjectName}
Composite Graph POST /composite/graph

Composite: create an Account, then a Contact linked to it

POST /services/data/v66.0/composite
Authorization: Bearer 00D...!AQ...
Content-Type: application/json

{
  "allOrNone": true,
  "compositeRequest": [
    {
      "method": "POST",
      "url": "/services/data/v66.0/sobjects/Account",
      "referenceId": "NewAccount",
      "body": { "Name": "Acme Corp", "Industry": "Technology" }
    },
    {
      "method": "POST",
      "url": "/services/data/v66.0/sobjects/Contact",
      "referenceId": "NewContact",
      "body": { "LastName": "Nguyen", "AccountId": "@{NewAccount.id}" }
    }
  ]
}

sObject Tree: one Account with nested Contacts (max 200 records)

POST /services/data/v66.0/composite/tree/Account

{
  "records": [{
    "attributes": { "type": "Account", "referenceId": "ref1" },
    "Name": "Acme Corp",
    "Contacts": { "records": [
      { "attributes": { "type": "Contact", "referenceId": "c1" }, "LastName": "Nguyen" },
      { "attributes": { "type": "Contact", "referenceId": "c2" }, "LastName": "Okafor" }
    ]}
  }]
}

A successful Composite response is an array of per-subrequest results, each with its own status and body, so you can tell exactly which step did what.


6. Design considerations and gotchas

Consideration Why it matters What to do
Subrequest cap Composite and Batch allow 25 subrequests; only a subset may be queries. Split larger workloads, or use Composite Graph.
Record caps sObject Tree is 200 records; Composite Graph is 500 records per request. Choose the resource that fits the volume.
All-or-none Composite is partial-success unless allOrNone:true. sObject Tree is always atomic. Set allOrNone deliberately when you need a clean rollback.
Graph atomicity In Composite Graph, each graph is transactional, not the whole request. Group records that must succeed together into one graph.
Query result cap Across subrequests, fetching 2,000 records triggers query locators for the rest. Page through locators for large result sets.
One API call counts as one The whole bundle is one API request against your allocation. Use Composite to cut chatty per-record loops.
Chaining syntax @{refId.field} only works in Composite and Graph, not Batch. Use Batch only when subrequests are independent.

7. Interview Q&A

Q: What problem does the Composite API solve? A: It collapses several related REST calls into one round-trip, cutting network latency and API call consumption, and it can run them as a single transaction. The Standard REST API is one operation per call.

Q: Composite vs Composite Batch? A: Both bundle up to 25 subrequests. Composite runs them in order and lets later subrequests reference earlier results with @{refId.field}, and supports allOrNone. Composite Batch runs them independently with no chaining.

Q: How do you create an Account and its child Contacts in one call? A: Use sObject Tree for a nested parent-child payload (up to 200 records, always all-or-none), or Composite with chaining, referencing the new Account Id via @{NewAccount.id}.

Q: When would you reach for Composite Graph? A: For larger related loads, up to 500 records per request, where you want each graph rolled back independently. It scales beyond the 25-subrequest Composite resource while keeping per-graph atomicity.

Q: Does a Composite call count as one API request or many? A: One request against the org's API allocation, even though it contains many subrequests. That is a key reason to batch chatty integrations.

Talking point to explain it to anyone: "Instead of five separate trips to the counter, you hand over one tray of orders. You can chain them, and you can say cancel everything if any single item fails."


8. Key terms

Composite, subrequest, referenceId, @{refId.field} chaining, allOrNone, sObject Tree, Composite Graph, query locator, API allocation - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 06-connect-rest-api.md - the API for Chatter, Experience Cloud, and Files.

06 - Connect REST API

One-liner: The HTTP API for Salesforce's social and collaboration layer - Chatter feeds, Experience Cloud sites, and Files - features the sObject APIs do not model well. Direction: External → Salesforce (inbound). Format: JSON (binary for file uploads). Auth: OAuth 2.0 Bearer token. Use when: You need to post to a feed, manage community content, or work with files programmatically rather than read and write plain records.

This is Module 04, inbound APIs (external systems calling into Salesforce). New to the vocabulary? See Module 01. For how the caller authenticates, see Module 03. Compare with 01-standard-rest-api.md and 05-composite-api.md.


1. The idea in plain English

The Standard REST API gives you the filing cabinet: rows, fields, records. But Salesforce is also a social network and a website builder, and a feed post or a community page is not a tidy row you can PATCH. The Connect REST API is the app's own interface for that world. It is the same API the Salesforce mobile app and Lightning use to render a Chatter feed, complete with avatars, mentions, attachments, likes, and comments already assembled for you.

Think of it this way: if you asked the sObject API for a feed, you would get raw FeedItem rows and have to stitch the photos, mentions, and comment threads together yourself. Connect REST hands you the finished, display-ready feed - the way a human would see it. It is structured around features (feeds, groups, files, sites), not around database tables.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
Post or read a Chatter feed, comment, like, or @mention. Plain CRUD or SOQL on records → 01-standard-rest-api.md.
Manage Experience Cloud (Communities) content or membership. Bundling many record operations → 05-composite-api.md.
Upload, share, or download Files / ContentDocuments. Building a record detail UI with layouts → 07-ui-api.md.
Work with groups, followers, notifications, or some CMS. Bulk record loads → Bulk API 2.0 (Module 07).

Real-world examples: an external app posts a status update to a record's Chatter feed, an integration uploads a contract PDF and shares it, or a portal back-end manages Experience Cloud site membership and content.


3. Connect REST vs Standard REST

Aspect Standard REST (sObjects) Connect REST
Models Database rows and fields Collaboration features
Shape Generic record JSON Feature-specific, display-ready JSON
You assemble Relationships yourself Pre-assembled feeds, mentions, attachments
Typical verbs GET/POST/PATCH/DELETE Same verbs, feature-oriented resources
Best for Data Chatter, Experience Cloud, Files

Both are inbound, both use OAuth, both run as the calling user. The difference is what they expose: data versus the social and content layer on top of it.


4. How it works

flowchart LR EXT["External App<br/>OAuth Bearer token"] -->|POST /chatter/feed-elements| CONN["Connect REST API"] CONN --> FEED["Chatter feeds<br/>posts, comments, likes"] CONN --> EXP["Experience Cloud<br/>sites, members, CMS"] CONN --> FILES["Files / Content<br/>upload, share, download"] CONN --> NOTIF["Groups, followers,<br/>notifications"] style EXT fill:#607D8B,color:#fff style CONN fill:#00A1E0,color:#fff style FEED fill:#9C27B0,color:#fff style EXP fill:#ff9900,color:#fff style FILES fill:#4CAF50,color:#fff style NOTIF fill:#607D8B,color:#fff

Walkthrough

  1. The external app authenticates with an OAuth Bearer token, exactly like the Standard REST API.
  2. It calls a feature-oriented resource under /connect/ or /chatter/, for example posting a feed element.
  3. Connect REST enforces the running user's permissions, sharing, and visibility, then performs the action.
  4. The response is display-ready JSON: a feed element already populated with body, actor, mentions, and capabilities.

5. The actual requests

Base: https://MyDomainName.my.salesforce.com/services/data/v66.0/

Connect REST resources live under /chatter/ (the Chatter feature set) and /connect/ (Experience Cloud, CMS, and newer features).

Action Method + path
Read a user's news feed GET /chatter/feeds/news/me/feed-elements
Post a feed item POST /chatter/feed-elements
Comment on a feed element POST /chatter/feed-elements/{feedElementId}/capabilities/comments/items
List Experience Cloud sites GET /connect/communities
Managed Content (CMS) GET /connect/cms/contents

Post a text update to a record's feed

POST /services/data/v66.0/chatter/feed-elements
Authorization: Bearer 00D...!AQ...
Content-Type: application/json

{
  "feedElementType": "FeedItem",
  "subjectId": "001bn00000ABCDeAAH",
  "body": {
    "messageSegments": [
      { "type": "Text", "text": "Contract signed and uploaded. Great work team!" }
    ]
  }
}

The response returns the created feed element with its Id, actor, body, and the capabilities (comments, likes, files) available on it, ready to render in a UI.


6. Design considerations and gotchas

Consideration Why it matters What to do
Feature-oriented, not record-oriented Resources map to Chatter, sites, files, not tables. Browse by feature in the Connect REST guide, not the object reference.
Apex twin exists The ConnectApi namespace mirrors these resources in Apex. Use ConnectApi for in-org logic, Connect REST for external callers.
Per-user context Actions run as the OAuth user and respect Chatter and site visibility. Give the integration user the right community and feed access.
Experience Cloud URLs Community resources may need the site Id in the path. Build the URL with the correct site context.
Files are binary Uploads use multipart, not plain JSON. Send file content as a multipart request part.
Versioned like all REST Behavior is pinned to the API version. Hardcode v66.0 that your client tested against.
Rate limits Some Connect resources have their own per-app rate limits. Read the rate-limit notes in the Connect REST guide.

7. Interview Q&A

Q: What is the Connect REST API for? A: It exposes Salesforce's collaboration layer - Chatter feeds, Experience Cloud sites, Files, groups, followers, notifications, and some CMS - as display-ready JSON. It covers what the plain sObject APIs do not model well.

Q: How is it different from the Standard REST API? A: Standard REST is record-centric (rows and fields). Connect REST is feature-centric and returns pre-assembled structures like a full feed with mentions and attachments, so you do not stitch related records together yourself.

Q: I need to post to a record's Chatter feed from an external system. Which API? A: Connect REST, posting a feed element to /chatter/feed-elements with the record Id as subjectId. The Apex equivalent is the ConnectApi namespace.

Q: Where does Experience Cloud fit? A: Connect REST manages Experience Cloud (Communities) - sites, membership, content, and CMS - under the /connect/ resources, so external systems can drive community experiences.

Q: Same auth and user model as Standard REST? A: Yes. OAuth Bearer token, and every action runs as the calling user, honoring their Chatter and community visibility, sharing, and permissions.

Talking point to explain it to anyone: "The record API gives you the database rows. The Connect API gives you the Facebook-style feed and the community site, already put together the way a person would see them."


8. Key terms

Connect REST API, Chatter, feed element, capability, Experience Cloud, ContentDocument, CMS, ConnectApi namespace - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 07-ui-api.md - the API that returns records plus their layouts and metadata to build Salesforce-like UIs.

07 - User Interface API

One-liner: Returns records plus their layouts, metadata, and theming in one response, so external or custom apps can render Salesforce-style pages without hardcoding metadata. Direction: External → Salesforce (inbound). Format: JSON. Auth: OAuth 2.0 Bearer token. Use when: You are building a custom front-end or mobile app that must mirror Salesforce layouts, picklists, and permissions dynamically.

This is Module 04, inbound APIs (external systems calling into Salesforce). New to the vocabulary? See Module 01. For how the caller authenticates, see Module 03. Compare with 01-standard-rest-api.md and 06-connect-rest-api.md.


1. The idea in plain English

The Standard REST API gives you the ingredients: the field values of a record. But if you want to build a screen that looks like Salesforce, you also need the recipe: which fields go on the layout, in what order, which are required, what the picklist options are, what the record type theme color is, and what this user is allowed to see. Fetching all of that piecemeal is slow and brittle.

The UI API hands you the finished meal photo with the recipe card attached. In a single response you get the record data and the metadata describing how to display it. It is the same API Salesforce uses internally to build Lightning Experience and the mobile app. Best of all, it already respects the user's field-level security, page layouts, sharing, and picklist values, so you do not reimplement those rules. When an admin changes a layout, your custom UI updates automatically because the metadata comes from the org, not from your code.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
Building a custom UI or mobile app that mirrors Salesforce layouts. Plain server-to-server data sync → 01-standard-rest-api.md.
You want picklists, layouts, theming delivered dynamically. Posting to feeds or managing communities → 06-connect-rest-api.md.
You must respect the user's FLS and permissions automatically. Bundling many writes atomically → 05-composite-api.md.
You do not want to hardcode metadata in your client. Bulk record processing → Bulk API 2.0 (Module 07).

Real-world examples: a custom mobile app that shows record pages identical to Salesforce, an embedded record viewer in an external portal, or a low-code tool that renders any object's create form by reading its layout at runtime.


3. What makes it different

Aspect Standard REST (sObjects) UI API
Returns Field values only Data plus layouts, metadata, theming
Metadata-aware No, you hardcode it Yes, layouts and picklists come from the org
Respects FLS / layouts Enforces FLS on data Enforces FLS, layouts, sharing, and shapes the response to them
Built for Integration and data Rendering Salesforce-like UIs
Used internally by General API clients Lightning Experience and Salesforce mobile

The point of the UI API is to make it simple to replicate Salesforce's own UI in a custom app, according to the rules and permissions of the running user.


4. How it works

flowchart LR APP["Custom UI / Mobile App<br/>OAuth Bearer token"] -->|GET /ui-api/record-ui/recordId| UIAPI["UI API"] UIAPI --> REC["Record data<br/>field values"] UIAPI --> LAY["Layout metadata<br/>sections, fields, order"] UIAPI --> OBJ["Object info<br/>picklists, FLS, theme"] REC --> RENDER["One response<br/>app renders the page"] LAY --> RENDER OBJ --> RENDER style APP fill:#607D8B,color:#fff style UIAPI fill:#00A1E0,color:#fff style REC fill:#4CAF50,color:#fff style LAY fill:#9C27B0,color:#fff style OBJ fill:#ff9900,color:#fff style RENDER fill:#607D8B,color:#fff

Walkthrough

  1. The custom app authenticates with an OAuth Bearer token and requests a record-ui resource for a record Id.
  2. The UI API gathers the record data, the layout that applies to this user and record type, and the object info (picklists, field-level security, theme).
  3. All of it is filtered by the running user's permissions and FLS, so the response only contains what they may see.
  4. The app receives one combined response and renders a page that matches Salesforce, no hardcoded metadata required.

5. The actual requests

Base: https://MyDomainName.my.salesforce.com/services/data/v66.0/ui-api/

Resource Method + path
Record data + object metadata GET /ui-api/record-ui/{recordIds}
Just the record (with layout-aware fields) GET /ui-api/records/{recordId}
Object metadata only GET /ui-api/object-info/{apiName}
Picklist values for a field GET /ui-api/object-info/{apiName}/picklist-values/{recordTypeId}/{fieldApiName}
Create a record POST /ui-api/records
Update a record PATCH /ui-api/records/{recordId}

Get a record with its display metadata in one call

GET /services/data/v66.0/ui-api/record-ui/001bn00000ABCDeAAH
Authorization: Bearer 00D...!AQ...

The response bundles three things: records (the field values), layouts (sections, columns, field order, required flags), and objectInfos (picklists, field-level security, theme color). That is everything a client needs to draw the page exactly as Salesforce would.

CRUD is supported, not just reads. You can create and update records through /ui-api/records, and the responses stay metadata-aware so your forms reflect the org's layouts and picklists.


6. Design considerations and gotchas

Consideration Why it matters What to do
Metadata-aware by design The response reshapes to the user's layouts and FLS. Let the org drive your UI, do not hardcode field lists.
Read and CRUD scope Supports get, create, update, delete, but not the full breadth of the data APIs. For bulk or pure data sync, use Standard REST or Bulk.
Per-user response Two users can get different layouts and fields for the same record. Test with the actual integration user's permissions.
Auto-updates with admin changes Layout and picklist changes flow through without a client release. Rely on this instead of shipping metadata in your app.
Lightning Data Service uses it LWC @wire adapters sit on top of the UI API. In-org components should prefer LDS over raw calls.
Versioned Behavior pinned to the API version. Hardcode v66.0 your client tested against.
Not for high volume It is a UI-rendering API, not a bulk pipe. Move large jobs to Bulk API 2.0 (Module 07).

7. Interview Q&A

Q: What does the UI API give you that the Standard REST API does not? A: Metadata alongside data. One response returns the record values plus the layout, picklists, theming, and object info, all filtered by the user's FLS and permissions, so you can render a Salesforce-style page without hardcoding metadata.

Q: Why would I use it instead of just reading sObjects? A: To build a custom or mobile UI that mirrors Salesforce. Reading sObjects gives you values but no layout, no picklist options, and no theme. The UI API delivers all of that dynamically, and it updates when admins change layouts.

Q: Does it respect field-level security and sharing? A: Yes. It runs as the calling user and shapes the response to their FLS, page layouts, and sharing, so the client never sees fields the user cannot access.

Q: Can you create and update records with it, or is it read-only? A: It supports CRUD through /ui-api/records, with metadata-aware responses, though it is not meant for the full breadth or volume that the data APIs and Bulk API handle.

Q: How does it relate to Lightning Web Components? A: Lightning Data Service and the LWC @wire adapters are built on the UI API. Salesforce uses the same API for its own Lightning Experience and mobile app, which is why custom apps can match its behavior.

Talking point to explain it to anyone: "It gives you the record and the instructions for drawing its page, already filtered to what you are allowed to see, so your app looks just like Salesforce without you copying any of Salesforce's setup."


8. Key terms

UI API, record-ui, object-info, layout metadata, picklist values, field-level security, theming, Lightning Data Service - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: back to the Module 04 README. Related: Module 03 for the OAuth these APIs require, and the Outbound module (Module 05) for when Salesforce calls out instead.

Module 05 - Outbound Callouts (Salesforce → External)

Goal: Make Salesforce talk OUT to other systems, securely and within limits. API version: v66.0 (Spring '26). The one rule: authenticate every callout with a Named Credential. Never hardcode a URL or secret.

Outbound means Salesforce initiates the call. This is the Request and Reply (and sometimes Fire and Forget) pattern in practice. This module covers how to make the call, secure it, do it from low-code, handle SOAP, go asynchronous, and stay inside the governor limits.


How to use this module

  1. Start with 01-http-callouts.md, the core Apex callout.
  2. Lock in 02-named-credentials-for-callouts.md, the secure way to authenticate.
  3. Learn when to go async (05) or use a Continuation (06).
  4. Keep 07-callout-limits-and-testing.md open as your reference.

Map of this module

# File What it covers
01 http-callouts Http / HttpRequest / HttpResponse in Apex
02 named-credentials-for-callouts Secure auth, no hardcoding, vs Remote Site Settings
03 external-services Low-code callouts from Flow via OpenAPI
04 soap-callouts-wsdl2apex Outbound SOAP from generated Apex stubs
05 asynchronous-callouts @future, Queueable, Batch Apex
06 continuation-pattern Long-running callouts from the UI, non-blocking
07 callout-limits-and-testing Limits + HttpCalloutMock testing

Which callout approach? (decision tree)

flowchart TD Start([Salesforce needs to call out]) --> Q1{Code or low-code?} Q1 -->|Low-code / Flow| ES[External Services] Q1 -->|Apex| Q2{REST or SOAP target?} Q2 -->|SOAP / WSDL| WSDL[WSDL2Apex stub] Q2 -->|REST| Q3{Execution context?} Q3 -->|Sync and fast| HTTP[HTTP callout] Q3 -->|From a Lightning page, slow API| CONT[Continuation] Q3 -->|After DML, from trigger, or long| ASYNC[Async: future / Queueable / Batch] style HTTP fill:#00A1E0,color:#fff style ES fill:#4CAF50,color:#fff style CONT fill:#ff9900,color:#fff style ASYNC fill:#ff9900,color:#fff style WSDL fill:#9C27B0,color:#fff

Always route auth through a Named Credential (callout:Name/path), whichever approach you pick.


Callout limits cheat sheet (memorize)

Limit Value
Callouts per transaction 100
Request + response size 6 MB sync, 12 MB async
Timeout per callout default 10s, max 120s
Total callout time per transaction 120s
Callout after uncommitted DML Not allowed (move async or call out first)
Continuation up to 3 parallel callouts, 120s each
Concurrent long-running sync requests (>5s) 10

Interview rapid-fire

Q: How do you authenticate an outbound callout? → A Named Credential (with an External Credential). The endpoint is callout:Name/path, and Salesforce injects the auth header. No hardcoded secrets, no Remote Site Setting needed.

Q: "You have uncommitted work pending" — why? → A callout after a DML in the same transaction. Call out before DML, or move it to an async context.

Q: @future vs Queueable? → Queueable (Database.AllowsCallouts) is preferred: it accepts objects/sObjects, is chainable, and is monitorable. @future(callout=true) is the simpler fire-and-forget (primitives only, no return).

Q: A Lightning page calls a slow API. How to avoid blocking? → The Continuation pattern: the callout runs async, the server thread is released, and a callback handles the response. Up to 3 parallel callouts, 120s each.

Q: How do you test callouts? → Implement HttpCalloutMock (or WebServiceMock), register with Test.setMock, and wrap async in Test.startTest()/stopTest(). Live callouts aren't allowed in tests.


Sources (Verified June 2026)

Each file has its own Sources section with the specific official doc.

01 - HTTP Callouts (Apex)

One-liner: Apex sends an HTTP request out to an external REST service and reads the response, using the Http, HttpRequest, and HttpResponse classes. Direction: Salesforce → External (outbound). Auth: a Named Credential (never a hardcoded URL or secret). Use when: Salesforce code needs to call an external REST API.

This is Module 05, outbound callouts (Salesforce calling external systems). For the auth object behind every callout, see Module 03 - Named Credentials. For the design pattern, see Module 02 - Request and Reply.


1. The idea in plain English

A callout is Salesforce placing the phone call instead of answering one. Your Apex builds a request (method, URL path, headers, body), hands it to the Http class, and gets back an HttpResponse with a status code and body. The golden rule: the address and credentials live in a Named Credential, so your code says callout:My_API/users/42, never a raw URL with a token glued on.

That one habit, using callout:, is what separates a clean integration from a security incident. Secrets stay in Salesforce's encrypted store and rotate centrally. Your code stays portable across sandboxes and production.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
Apex needs to call an external REST endpoint. Low-code team wants it in Flow → 03-external-services.md.
You want full control over request/response. External service is SOAP/WSDL → 04-soap-callouts-wsdl2apex.md.
The call is fast and fits a transaction. Long-running from a screen → 06-continuation-pattern.md.
Triggered synchronously and quickly. Fired from a trigger / after DML → 05-asynchronous-callouts.md.

Real-world examples: fetch a credit score, validate an address, push an order to an ERP, call a shipping rate API.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant APX as Apex participant NC as Named Credential participant EXT as External REST API APX->>NC: send(req) with endpoint callout:My_API/users/42 NC->>NC: Inject base URL + auth header (token) NC->>EXT: HTTPS GET /users/42 EXT-->>NC: 200 OK + JSON NC-->>APX: HttpResponse (status + body) APX->>APX: Parse JSON, act on it

Walkthrough

  1. Apex builds an HttpRequest whose endpoint starts with callout:NamedCredentialName. 2-3. At send time Salesforce swaps in the real base URL and adds the auth header from the Named Credential.
  2. The external API responds. 5-6. Apex receives the HttpResponse and parses the body (usually JSON).

4. The actual code

Http http = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_API/users/42'); // base URL + auth from Named Credential
req.setMethod('GET');
req.setHeader('Content-Type', 'application/json');
req.setTimeout(120000); // ms, max 120000 (120s)

HttpResponse res = http.send(req);
if (res.getStatusCode() == 200) {
    Map<String, Object> body =
        (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
    // use body
} else {
    // handle 4xx/5xx, log, retry as appropriate
}

POST with a body

req.setEndpoint('callout:My_API/orders');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(new Map<String,Object>{ 'sku' => 'A-100', 'qty' => 2 }));

Named Credential vs Remote Site Setting: a Named Credential supplies the URL and authentication, and is the recommended approach. If you ever call a raw hardcoded URL (not recommended), that host must be whitelisted as a Remote Site Setting or the callout is blocked. Using callout: avoids that entirely.


5. Design considerations and gotchas

Consideration Why it matters What to do
No callout after DML An uncommitted DML in the same transaction blocks the callout (You have uncommitted work pending). Call out before DML, or move the callout to async.
Callout limits Max 100 callouts per transaction; request/response max 6 MB sync, 12 MB async. Don't loop callouts per record; bulkify.
Timeout Default 10s, max 120s per callout; total 120s per transaction. Set setTimeout; if slow, go async/Continuation.
Synchronous blocks the user A slow API freezes the UI. Use Continuation or async.
Secrets in code Hardcoded tokens leak and don't rotate. Always use callout:NamedCredential.
Testing Real callouts aren't allowed in tests. Implement HttpCalloutMock. See 07-callout-limits-and-testing.md.

6. Interview Q&A

Q: How do you make an HTTP callout in Apex? A: Build an HttpRequest (method, endpoint, headers, body), send it with Http.send(), and read the HttpResponse. The endpoint should be callout:NamedCredential/path so the URL and auth come from a Named Credential.

Q: Why use a Named Credential instead of a raw URL? A: It stores the endpoint and credentials in Salesforce's encrypted store, injects the auth header automatically, keeps secrets out of code, and rotates centrally. A raw URL would also require a Remote Site Setting.

Q: "You have uncommitted work pending" — what causes it? A: Making a callout after a DML statement in the same transaction. Fix by calling out before any DML, or by moving the callout to an asynchronous context.

Q: What are the key callout limits? A: 100 callouts per transaction, 6 MB request/response synchronously (12 MB async), and a 120-second max per callout and per transaction.

Q: How do you test callouts? A: You can't hit a live endpoint in a test. Implement HttpCalloutMock (or Test.setMock) to return a canned response, then assert on the parsed result.

Talking point to explain it to anyone: "It's Salesforce making the phone call out. The phone number and password are stored safely in a Named Credential, so the code just says 'call My_API' and never holds the secret."


7. Key terms

Callout, Named Credential, Remote Site Setting, HttpRequest/HttpResponse, governor limit, HttpCalloutMock - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 02-named-credentials-for-callouts.md - the secure, no-hardcoding way to authenticate every callout.

02 - Named Credentials for Callouts

One-liner: The secure, secret-free way to authenticate every outbound callout. Your code references callout:Name/path, and Salesforce injects the real URL and the auth header at send time. Direction: Salesforce → External (outbound). Auth: a Named Credential (endpoint) backed by an External Credential (auth). Never a hardcoded URL or token. Use when: Any Apex, Flow, or External Services callout needs authentication. This is the recommended pattern for all outbound auth.

This is Module 05, outbound callouts. This file is about using Named Credentials in a callout. The full configuration, every auth protocol, principals, and permission-set mapping live in the deep file: Module 03 - Named Credentials + External Credentials. Read that for the why and how to configure. This file is the how to call view. For the raw HTTP mechanics, see 01-http-callouts.md.


1. The idea in plain English

Think of a speed-dial on an office phone. You press "VendorX" and the phone dials the real number and logs in with the right account. You never type the number or the password. If the vendor changes their number or rotates the password, the admin updates the speed-dial entry once. Every phone keeps working. No one re-learns a number.

A Named Credential is that speed-dial. In Apex you write callout:VendorX/v1/orders. At send time Salesforce swaps callout:VendorX for the real base URL and attaches the authentication header from the linked External Credential. Your code, your repo, your debug logs, and your packaged metadata never hold a secret.

That single habit, always using callout:, is the line between a clean integration and a security incident waiting to happen.


2. When to use it (and when not)

✅ Use a Named Credential when ❌ Avoid / different approach
Any authenticated outbound callout from Apex, Flow, or External Services. A truly public, no-auth, hardcoded URL test (still discouraged) → needs a Remote Site Setting instead.
You want secrets out of code and central rotation. You need the inbound social login / SSO side → Auth Providers.
You want portability across sandbox and production. You only need the protocol theory → Module 03 deep file.
The API uses OAuth, API key, Basic, AWS Sig v4, or mTLS. n/a

Real-world examples: call a payment gateway with OAuth Client Credentials, push to an ERP with an API key in a custom header, sign requests to AWS S3, hit a partner API with a per-user OAuth token.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant APX as Apex / Flow participant NC as Named Credential participant EC as External Credential participant EXT as External API APX->>NC: send req with callout:VendorX/v1/orders NC->>EC: Which principal for this running user? EC->>EC: Get or refresh token, or build auth header EC-->>NC: Authorization header ready NC->>EXT: HTTPS request to real URL + injected header EXT-->>NC: 200 OK + response NC-->>APX: HttpResponse, no secret ever in code

Walkthrough

  1. Apex sets the endpoint to callout:VendorX/v1/orders.
  2. The Named Credential resolves the linked External Credential and which principal applies, using the running user's permission-set mapping. 3-4. The External Credential obtains or refreshes the token, or constructs the header (OAuth token, API key, AWS v4 signature, etc.).
  3. Salesforce injects the Authorization header and sends to the real URL over HTTPS. 6-7. The response returns to Apex. Your code never touched a secret.

4. The actual code and config

The split, in one line

A Named Credential holds the endpoint URL and points to an External Credential, which holds the auth protocol + principals + permission-set mappings. Salesforce split them in Winter '23 so one auth definition can serve many endpoints. Full setup steps are in the Module 03 deep file. Summary: create the External Credential, add a principal, map it to a permission set (mandatory), create the Named Credential pointing at it, then assign the permission set to users.

The callout: syntax

HttpRequest req = new HttpRequest();
// 'callout:' + Named Credential API name + path
req.setEndpoint('callout:VendorX/v1/orders');
req.setMethod('GET');

Http http = new Http();
HttpResponse res = http.send(req);
System.debug(res.getStatusCode() + ' ' + res.getBody());

Salesforce substitutes the base URL and adds the auth header (when Generate Authorization Header is on, the default). No endpoint string, no token, no secret in code.

Principals: Named vs Per-User (quick contrast)

Principal One identity or many? Pick when
Named Principal One shared service identity for all users The external system does not care which Salesforce user called.
Per-User Principal Each user authenticates individually The external system needs the real end user for data or audit.

Details and the Client Credentials principal: see Module 03, section 4.

Merge fields in custom headers and body

A Named Credential can carry custom headers, and you can inject stored values with a merge field. This is the clean way to do an API key scheme: define a custom header on the Named Credential so every callout carries the key automatically.

HttpRequest req = new HttpRequest();
req.setEndpoint('callout:VendorX/v1/tickets');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
// Merge field pulls a stored value at runtime.
// Requires "Allow Formulas in HTTP Body" on the Named Credential.
req.setBody('{"agent":"{!$Credential.VendorX_Cred.Username}"}');
Http http = new Http();
HttpResponse res = http.send(req);

The merge syntax is {!$Credential.<ExternalCredential>.<field>}. Only enable Allow Formulas in HTTP Header / Body when you genuinely need a merge field, to avoid formula-injection risk.

Named Credential vs Remote Site Setting (the key contrast)

Named Credential Remote Site Setting
Supplies the URL? Yes No, it only allowlists a host
Supplies authentication? Yes, injects the header No
Used in code as callout:Name/path The full hardcoded URL
Needed when you use callout:? This is the thing you use Not needed at all
Needed for a raw hardcoded URL? n/a Required, or the callout is blocked

Plainly: a Named Credential is the recommended approach and removes the need for a Remote Site Setting. A Remote Site Setting is only the host allowlist you fall back to if you ever hardcode a raw URL (not recommended). Using callout: gives you the URL and the auth, so you never touch Remote Site Settings.


5. Design considerations and gotchas

Consideration Why it matters What to do
Hardcoding secrets Tokens leak via source control, logs, and packages; rotation means redeploys. Always use callout:Name; store secrets in the External Credential.
Missing permission-set mapping The principal is inert; the callout fails with an auth error even for an admin. Map the External Credential principal to a permission set and assign it.
Full URL in setEndpoint You lose the auto-injected auth and the secret-free benefit. Always callout:Name/path, never the raw URL.
Wrong principal type A Named Principal hides who acted; per-user audit breaks. Use Per-User when the API needs the real user.
Merge fields enabled needlessly Formula injection or accidental data exposure. Only enable Allow Formulas when a merge field is required.
Legacy single Named Credential Not enhanced; misses new protocols and features. Use the Named + External Credential split (Winter '23+).
Relying on Remote Site Settings Means a raw URL with hardcoded auth somewhere. Replace with a Named Credential; drop the Remote Site Setting.

6. Interview Q&A

Q: How do you authenticate an outbound callout without putting secrets in code? A: Use a Named Credential for the endpoint, backed by an External Credential for the auth. In Apex set the endpoint to callout:Name/path. Salesforce injects the real URL and the auth header at send time, so no secret appears in code, logs, or packages.

Q: What is the callout: prefix doing exactly? A: It tells Salesforce to resolve the Named Credential by API name, substitute its base URL, look up the linked External Credential and the running user's principal, build or refresh the auth header, and attach it. The substitution happens at send time, not in your code.

Q: Do you still need a Remote Site Setting when using a Named Credential? A: No. A Named Credential supplies the URL and the auth, so it removes the need for a Remote Site Setting. A Remote Site Setting is only a host allowlist required when you call a raw hardcoded URL, which is discouraged.

Q: Named Principal vs Per-User Principal? A: A Named Principal is one shared identity for all users, fine when the external system does not care who called. A Per-User Principal has each user authenticate individually, required when the API needs the actual end user for data access or audit.

Q: How would you implement an API-key API cleanly? A: Create a Custom External Credential, store the key, and define a custom header on the Named Credential carrying the key (optionally via a {!$Credential...} merge field). Every callout then sends the key automatically, and the key rotates in config with zero code change.

Talking point to explain it to anyone: "It's a speed-dial button. The code presses 'VendorX' and the phone dials the real number and logs in. The number and password stay locked in the phone, never written on a sticky note on the desk."


7. Key terms

Named Credential, External Credential, principal (Named vs Per-User), Remote Site Setting, merge field, callout: syntax - defined in Module 01 vocabulary and the README. For the full protocol and config reference, see the Module 03 deep file.


Sources (Verified June 2026)


Next: 03-external-services.md - call a REST API from Flow with no handwritten Apex.

03 - External Services (Low-Code Callouts from Flow)

One-liner: Register an external REST API from its OpenAPI schema, and Salesforce auto-generates invocable actions that admins drop into Flow with no handwritten Apex. Direction: Salesforce → External (outbound). Auth: a Named Credential supplies the endpoint and auth. Use when: A low-code or admin team needs to call a REST API from Flow without writing Apex callout code.

This is Module 05, outbound callouts. External Services is the declarative sibling of 01-http-callouts.md (handwritten Apex) and 04-soap-callouts-wsdl2apex.md (SOAP). The endpoint and auth still come from a Named Credential, see 02-named-credentials-for-callouts.md and the Module 03 deep file.


1. The idea in plain English

Imagine an external API hands you a printed menu of exactly what it can do: every dish (operation), its ingredients (inputs), and what comes out (outputs). That menu is the OpenAPI schema (formerly called Swagger). You hand the menu to Salesforce once. Salesforce reads it and builds a row of ready-made buttons, one per dish. An admin then drags a button into a Flow and fills in the inputs. No cook (developer) writes a recipe by hand.

So External Services is "register the menu, get the buttons." The buttons are invocable actions. The kitchen's address and the way you pay (auth) live in a Named Credential. The admin never sees a URL or a token, and never writes a single line of Apex.


2. When to use it (and when not)

✅ Use External Services when ❌ Avoid / use something else
Admins or low-code teams must call a REST API from Flow. The endpoint is SOAP/WSDL → 04-soap-callouts-wsdl2apex.md.
The API ships a clean OpenAPI 2.0 or 3.0 schema. No OpenAPI schema exists or it is huge / very complex → handwritten HTTP callout.
You want no handwritten Apex for the callout. You need fine-grained control of retries, chunking, or custom parsing → Apex callout.
The operations map cleanly to inputs and outputs. Deeply nested or polymorphic payloads the generator cannot model → custom Apex.

Real-world examples: an admin Flow that validates an address, fetches a shipping quote, looks up a tax rate, or creates a ticket in a partner system, all from a screen or record-triggered Flow.


3. How it works (flow diagram)

flowchart LR SCHEMA[OpenAPI schema<br/>JSON 2.0 or 3.0] --> ES[External Service<br/>registration] NC[Named Credential<br/>endpoint plus auth] --> ES ES --> ACT[Generated invocable<br/>actions] ACT --> FLOW[Flow Builder<br/>admin drags action] FLOW --> API[External REST API] API -->|response mapped to outputs| FLOW style SCHEMA fill:#ff9900,color:#fff style ES fill:#00A1E0,color:#fff style NC fill:#9C27B0,color:#fff style ACT fill:#4CAF50,color:#fff style FLOW fill:#607D8B,color:#fff style API fill:#F44336,color:#fff

Walkthrough

  1. You provide the API's OpenAPI schema (JSON, version 2.0 or 3.0) by URL or paste.
  2. You point the registration at a Named Credential that holds the base URL and auth.
  3. Salesforce parses each operation into an invocable action, mapping parameters to inputs and the response to outputs.
  4. An admin drags the action into a Flow and binds the inputs to record fields or variables.
  5. At runtime the Flow calls the external API through the Named Credential and reads the mapped outputs.

4. The setup (point and click)

There is no Apex to write. The steps:

  1. Create the Named Credential (and External Credential) for the API's base URL and auth. See 02-named-credentials-for-callouts.md.
  2. Setup → External Services → Add an External Service.
  3. Choose From API Specification, select the Named Credential, and supply the OpenAPI schema (by URL or by pasting the JSON).
  4. Salesforce validates the schema, then shows the operations it found. Select the ones to import.
  5. Finish. Salesforce generates one invocable action per selected operation.
  6. In Flow Builder, add an Action element, search for your External Service, pick the operation, and map inputs and outputs.

The generated actions are also invocable from Apex, Einstein Bots / Agentforce, Orchestrator, and OmniStudio, not only Flow.

Schema basics to verify before you start: External Services targets RESTful APIs described in OpenAPI 2.0 or 3.0. OpenAPI 3.0 JSON support arrived in Spring '22. The schema must satisfy the External Services Considerations (see Sources), and Salesforce derives action and type names from the schema, so clear operationId values produce clean action names.


5. Design considerations and gotchas

Consideration Why it matters What to do
Schema size limit A schema over 100,000 characters fails registration. Trim the spec to the operations you actually need, or split it.
Derived name length Salesforce derives type and action names; each derived name must be fewer than 255 characters. Use short, clear operationId and schema names.
Complexity limits Deeply nested, polymorphic, or unusual constructs may not import cleanly. Simplify the schema, or fall back to a handwritten Apex callout.
Regeneration on API change If the external API changes its contract, the generated actions go stale. Re-edit the External Service to re-import; re-test affected Flows.
Auth lives in the Named Credential The action has no URL or secret of its own. Configure auth once in the Named/External Credential; rotate there.
Governor limits still apply These are real callouts under the hood. Respect the 100 callouts per transaction limit; do not loop actions per record.
Operations must map cleanly Inputs/outputs are auto-generated from the schema. Confirm the generated inputs/outputs match expectations before shipping.

6. Interview Q&A

Q: What is an External Service and what problem does it solve? A: It lets you register an external REST API from its OpenAPI schema so Salesforce generates invocable actions. Admins then call the API from Flow with no handwritten Apex. It brings third-party REST calls into low-code automation.

Q: What schema formats and versions does it support? A: RESTful APIs described in OpenAPI 2.0 or 3.0. OpenAPI 3.0 JSON support was added in Spring '22. The schema must meet the External Services Considerations, including a 100,000-character size limit.

Q: How does an External Service authenticate? A: It uses a Named Credential for the base URL and a backing External Credential for the auth. The generated action carries no URL or secret; everything resolves through the Named Credential at runtime.

Q: What happens when the external API changes? A: The generated actions can go stale. You re-edit the External Service to re-import the updated schema, then re-test any Flows that use the affected actions. It is not automatic.

Q: When would you choose a handwritten Apex callout instead? A: When there is no usable OpenAPI schema, the schema is too large or complex to import, or you need fine control over retries, payload shaping, or custom parsing. External Services trades control for speed of delivery.

Talking point to explain it to anyone: "The API hands Salesforce a printed menu of what it can do. Salesforce turns each menu item into a button. An admin drags the button into a Flow. Nobody writes code, and the address and password stay locked in a Named Credential."


7. Key terms

External Service, OpenAPI / Swagger schema, invocable action, Flow Builder, Named Credential, operation - defined in Module 01 vocabulary and the README. For the auth object behind the endpoint, see 02-named-credentials-for-callouts.md and the Module 03 deep file.


Sources (Verified June 2026)


Next: 04-soap-callouts-wsdl2apex.md - calling an external SOAP service from Apex.

04 - SOAP Callouts (WSDL2Apex)

One-liner: Point Salesforce at a SOAP service's WSDL, let it generate Apex stub classes, then call the generated methods to invoke the external SOAP operations. Direction: Salesforce → External (outbound). Auth: a Named Credential supplies the endpoint and auth. Use when: The external system only offers SOAP. For new work, REST/HTTP callouts are preferred.

This is Module 05, outbound callouts. WSDL2Apex is the SOAP counterpart to handwritten 01-http-callouts.md (REST) and low-code 03-external-services.md (REST from Flow). The endpoint and auth still belong in a Named Credential, see 02-named-credentials-for-callouts.md and the Module 03 deep file.


1. The idea in plain English

A WSDL is a SOAP service's instruction manual written in XML. It lists every operation, the exact shape of each request and response, and where to send them. Reading that manual and hand-coding the XML by hand would be slow and error-prone.

WSDL2Apex is a code generator that reads the manual for you. You upload the WSDL in Setup, and Salesforce writes Apex stub classes: typed methods and objects that mirror the SOAP operations. You then call a generated method like an ordinary Apex method. Under the hood the stub builds the SOAP envelope, sends it, and parses the XML reply back into Apex objects.

So the workflow is: upload WSDL → get generated Apex → call the stub method. Think of it as auto-generating a typed client from a contract.


2. When to use it (and when not)

✅ Use WSDL2Apex when ❌ Avoid / use something else
The external system only speaks SOAP and ships a WSDL. The system offers REST → 01-http-callouts.md or 03-external-services.md.
You want typed Apex stubs instead of hand-building XML. The WSDL uses unsupported constructs (see gotchas) → fix the WSDL or build the envelope manually.
The SOAP contract is stable SOAP 1.1 document/literal. A low-code team needs it in Flow → External Services (REST only; SOAP not supported there).
You are integrating a legacy enterprise partner. Brand-new greenfield integration → prefer REST.

Real-world examples: a legacy ERP, a bank's payment SOAP API, an older shipping or tax provider, a government web service that predates REST.


3. How it works (flow diagram)

flowchart LR WSDL[SOAP WSDL<br/>XML contract] --> GEN[Generate from WSDL<br/>in Setup] GEN --> STUB[Generated Apex<br/>stub classes] STUB --> CALL[Your Apex calls<br/>a stub method] NC[Named Credential<br/>endpoint plus auth] --> CALL CALL --> SOAP[External SOAP<br/>service] SOAP -->|XML response parsed to Apex| CALL style WSDL fill:#ff9900,color:#fff style GEN fill:#00A1E0,color:#fff style STUB fill:#4CAF50,color:#fff style CALL fill:#9C27B0,color:#fff style NC fill:#607D8B,color:#fff style SOAP fill:#F44336,color:#fff

Walkthrough

  1. You obtain the external service's WSDL (the XML contract).
  2. In Setup you run Generate from WSDL. Salesforce parses it.
  3. Salesforce writes Apex stub classes that mirror the operations and types.
  4. Your Apex calls a generated stub method, passing typed inputs.
  5. The stub uses the Named Credential for the endpoint and auth, builds the SOAP envelope, and sends it.
  6. The SOAP reply XML is parsed back into Apex objects you read directly.

4. The setup and code

Generate the stubs

  1. Create the Named Credential (and External Credential) for the SOAP endpoint and auth. See 02-named-credentials-for-callouts.md.
  2. Setup → Apex Classes → Generate from WSDL.
  3. Upload the WSDL file and click Parse WSDL.
  4. Salesforce proposes one or more Apex class names (one per namespace in the WSDL). Adjust the names, then Generate Apex.
  5. The generated stub classes appear in Apex Classes, ready to call.

Call a generated stub method

The exact class and method names come from your WSDL. The shape is always the same: instantiate the generated service stub, point it at the Named Credential, and call the operation.

// Class and method names are generated from the WSDL.
exampleSoap.OrdersPort stub = new exampleSoap.OrdersPort();
// Endpoint and auth come from a Named Credential, never a hardcoded URL.
stub.endpoint_x = 'callout:Vendor_Soap';

try {
    // 'createOrder' is a generated method mirroring the SOAP operation.
    exampleSoap.OrderResult result = stub.createOrder('A-100', 2);
    System.debug(result.orderId);
} catch (CalloutException e) {
    // Handle SOAP faults and transport errors.
    System.debug('SOAP callout failed: ' + e.getMessage());
}

Auth note: set endpoint_x to callout:Named_Credential so the URL and auth header are injected, exactly like a REST callout. Never hardcode the SOAP endpoint URL or credentials in the stub.

Test coverage

The generated stub classes need test coverage like any Apex. Use WebServiceMock (the SOAP analogue of HttpCalloutMock) with Test.setMock to return a canned response, since real callouts are not allowed in tests. See 07-callout-limits-and-testing.md.


5. Design considerations and gotchas

Consideration Why it matters What to do
Unsupported WSDL constructs WSDL2Apex supports document/literal SOAP 1.1 only. SOAP 1.2, RPC/encoded, multiple portTypes, multiple bindings, multiple services, and external schema imports fail to import. Edit the WSDL to a single binding/portType, or hand-build the SOAP envelope with HTTP callouts.
1 million character limit Generation fails if the resulting classes exceed the 1,000,000-character Apex class limit. Trim the WSDL to needed operations, or split into multiple classes.
Whole-WSDL import You import the entire WSDL even if you need one operation. Prune the WSDL before generating if it is large.
Regenerate on contract change If the SOAP contract changes, the stubs go stale. Re-run Generate from WSDL and re-test.
SOAP faults Errors arrive as SOAP fault XML, surfaced as exceptions. Wrap calls in try/catch on CalloutException; inspect the fault.
Hardcoded endpoint/secret Putting the URL or credentials in the stub leaks secrets and breaks portability. Set endpoint_x = 'callout:Name'; keep auth in the Named Credential.
Same callout limits as REST SOAP callouts count against the same governor limits. Respect 100 callouts per transaction; no DML before the callout.
Status: legacy-leaning SOAP is maintained, but REST is preferred for new work. Default to REST/HTTP; use WSDL2Apex only when the partner is SOAP-only.

6. Interview Q&A

Q: How do you call an external SOAP service from Apex? A: Use WSDL2Apex: in Setup, Apex Classes → Generate from WSDL, upload the WSDL, and Salesforce generates Apex stub classes. You then call a generated method, setting endpoint_x to callout:Named_Credential so the URL and auth come from a Named Credential.

Q: What WSDL constructs does WSDL2Apex not support? A: It supports document/literal SOAP 1.1 only. It cannot import SOAP 1.2, RPC/encoded services, WSDLs with multiple portTypes, bindings, or services, or WSDLs that import external schemas. You either edit the WSDL to a single binding/portType or build the SOAP envelope manually.

Q: Is there a size limit on generated classes? A: Yes. Generation fails if the resulting Apex exceeds the 1,000,000-character per-class limit. Trim the WSDL to the operations you need or split into multiple classes.

Q: How do you handle authentication for a SOAP callout? A: The same way as REST. Set the stub's endpoint_x to callout:Named_Credential so Salesforce injects the endpoint and auth header. Secrets stay in the External Credential, never in the stub.

Q: REST or SOAP for a new integration, and why? A: REST/HTTP for new work: it is lighter, JSON-based, and supported by External Services for low-code. Use WSDL2Apex/SOAP only when the external system offers nothing but SOAP, typically a legacy enterprise partner.

Talking point to explain it to anyone: "A WSDL is the SOAP service's instruction manual. Salesforce reads the manual and writes the client code for you. You just call the generated method, and the address and password stay locked in a Named Credential."


7. Key terms

WSDL, WSDL2Apex, SOAP, stub class, SOAP fault, WebServiceMock, Named Credential - defined in Module 01 vocabulary and the README. For the auth object behind the endpoint, see 02-named-credentials-for-callouts.md and the Module 03 deep file.


Sources (Verified June 2026)


Next: 05-asynchronous-callouts.md - making callouts from triggers and async contexts without hitting the DML-then-callout wall.

05 - Asynchronous Callouts

One-liner: Run the callout off the main thread so it does not block the user, can fire from a trigger, and can run after DML. Direction: Salesforce → External (outbound). Timing: Asynchronous (later, separate transaction). Use when: You need a callout but cannot make it synchronously, or the work is too slow to make the user wait.

This is Module 05, outbound callouts. The callout mechanics are in 01-http-callouts.md. The auth object is Module 03 - Named Credentials. For a slow callout from a Lightning screen, see 06-continuation-pattern.md.


1. The idea in plain English

A synchronous callout is making a phone call while the customer waits at your desk. An asynchronous callout is taking their number and calling them back later, after they have walked away. The customer is not stuck watching you dial.

There are three reasons you are forced to call back later instead of calling now:

  1. You cannot call out after uncommitted DML. Once your transaction has changed data but not committed, Salesforce blocks the callout with You have uncommitted work pending. Save now, call later.
  2. Triggers cannot call out directly. A trigger runs inside the DML transaction, so any callout from a trigger must be handed to an async context.
  3. Long work should not block a user or a transaction. A 60-second callout freezing a save is bad UX and risks the concurrent long-running request limit.

So you move the callout to its own transaction that runs in the background.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You must call out from a trigger. A fast call with no DML before it → sync 01-http-callouts.md.
You need a callout after DML in the same flow. A user is at a screen waiting on a slow API → 06-continuation-pattern.md.
The work is slow and the user should not wait. You need the result immediately to continue.
You are processing many records that each call out. The external system cannot handle background bursts.

Real-world examples: a trigger on Order syncs to an ERP after insert, a Lead trigger enriches data via a third-party API, a nightly batch pushes thousands of records to a warehouse.


3. The options and how to choose

Salesforce gives you four async entry points. Three can call out directly. One cannot.

flowchart TD A[Need an async callout] --> B{Simple fire-and-forget<br/>primitive params only?} B -->|Yes| C["@future callout=true"] B -->|No| D{Need to pass sObjects<br/>or chain jobs?} D -->|Yes| E[Queueable<br/>Database.AllowsCallouts] D -->|Bulk, many records| F[Batch Apex<br/>Database.AllowsCallouts] A --> G{Triggered by a schedule?} G -->|Yes| H["Scheduled Apex<br/>delegates to Queueable / @future"] style C fill:#ff9900,color:#fff style E fill:#4CAF50,color:#fff style F fill:#9C27B0,color:#fff style H fill:#607D8B,color:#fff
Option Mark it for callouts Can pass Chainable Monitorable Best for
@future(callout=true) annotation param primitives only No No (just an Id) Simple, single, fire-and-forget callout
Queueable implement Database.AllowsCallouts sObjects, objects Yes Yes (AsyncApexJob) The modern default. Most callouts
Batch Apex implement Database.AllowsCallouts a scope of records Via Finish Yes Bulk: callout per batch of records
Scheduled Apex cannot call out directly n/a n/a Yes Delegate to Queueable/@future to call out

The short rule: reach for Queueable first. Salesforce officially recommends Queueable over @future because it can take rich types, be chained, and be tracked through the AsyncApexJob object. Use @future only for the simplest case. Use Batch when you are calling out across a large data volume. Never try to call out straight from a Schedulable - schedule a job that enqueues a Queueable.


4. The actual code

Queueable with a callout (the preferred pattern)

public class OrderSyncQueueable implements Queueable, Database.AllowsCallouts {
    private List<Order> orders; // Queueable can carry sObjects

    public OrderSyncQueueable(List<Order> orders) {
        this.orders = orders;
    }

    public void execute(QueueableContext ctx) {
        for (Order o : orders) {
            HttpRequest req = new HttpRequest();
            req.setEndpoint('callout:ERP_System/orders'); // URL + auth from Named Credential
            req.setMethod('POST');
            req.setHeader('Content-Type', 'application/json');
            req.setBody(JSON.serialize(o));
            req.setTimeout(120000); // ms, max 120s

            HttpResponse res = new Http().send(req);
            if (res.getStatusCode() != 200) {
                // log, flag the record, or chain a retry job
            }
        }
    }
}

Enqueue it from a trigger handler (callout after the trigger, not inside it)

// In an after-insert trigger handler:
System.enqueueJob(new OrderSyncQueueable(Trigger.new));

The @future shape (primitives only, no return)

public class LeadEnricher {
    @future(callout=true)
    public static void enrich(Set<Id> leadIds) { // primitive/collection of primitives only
        // build and send the HttpRequest here
    }
}

Auth: every async callout still authenticates with a Named Credential. Never hardcode a secret. See Module 03.


5. Design considerations and gotchas

Consideration Why it matters What to do
@future is limited No sObjects, no return value, hard to monitor, cannot chain. Prefer Queueable for anything beyond the trivial.
Callout chain depth A Queueable that calls out can only chain to a limited depth of jobs that also call out. Do not build deep retry chains of calling-out jobs. Use a finite backoff.
Scheduled Apex cannot call out Calling out from execute(SchedulableContext) throws. Have the scheduled job enqueue a Queueable that does the callout.
@future limit per transaction A cap on future invocations per transaction. Bulkify: pass a Set<Id>, do not call @future per record in a loop.
No ordering guarantee Async jobs run when the platform schedules them. Do not assume order across separate enqueues. Chain if order matters.
Still bound by callout limits Async raises size limits but the 100 callouts/transaction cap still applies. Bulkify inside the job. See 07-callout-limits-and-testing.md.
Testing async callouts Real callouts are blocked in tests; async runs at stopTest. Mock with HttpCalloutMock and wrap in Test.startTest()/stopTest().

6. Interview Q&A

Q: Why can't you make a callout after DML in the same transaction? A: An uncommitted DML opens a database transaction, and Salesforce will not let you hold that open across a callout. You get You have uncommitted work pending. Either call out before any DML, or move the callout to an asynchronous context that runs in its own transaction.

Q: How do you call out from a trigger? A: You cannot call out directly inside a trigger. Hand the work to an async context - enqueue a Queueable (implementing Database.AllowsCallouts) or call a @future(callout=true) method from the trigger handler.

Q: Queueable vs @future for callouts - which and why? A: Queueable. It accepts sObjects and complex types, is chainable, and is monitorable through AsyncApexJob. @future only takes primitives, returns nothing, and cannot be tracked or chained. Salesforce officially recommends Queueable over future methods.

Q: Can Scheduled Apex make a callout? A: Not directly. The Schedulable.execute method cannot perform a callout. The pattern is to have the scheduled class enqueue a Queueable (or call a @future(callout=true) method) that performs the actual callout.

Q: What do you implement to allow callouts in a Queueable or Batch class? A: The marker interface Database.AllowsCallouts. It has no methods - it just signals to the platform that callouts are permitted in that async job.

Talking point to explain it to anyone: "Instead of making the customer wait while I dial, I take their number and call them back. That is async - the callout runs in the background, so the save finishes fast and a trigger can still reach the outside world."


7. Key terms

Asynchronous, @future, Queueable, Database.AllowsCallouts, Batch Apex, Scheduled Apex, AsyncApexJob, uncommitted DML - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 06-continuation-pattern.md - making a long-running callout from a Lightning screen without blocking the user.

06 - Continuation Pattern

One-liner: Fire a long-running callout from a Lightning or Visualforce screen without holding an app server thread, then process the answer in a callback. Direction: Salesforce → External (outbound). Timing: Asynchronous from the UI, but the user still gets a result on the same page. Use when: A Lightning page (Aura/LWC) or Visualforce page calls a slow external API and you do not want to freeze a server thread.

This is Module 05, outbound callouts. For the plain callout, see 01-http-callouts.md. For background callouts not tied to a screen, see 05-asynchronous-callouts.md. Auth is Module 03.


1. The idea in plain English

Imagine a receptionist who can only handle a fixed number of calls at once. If one caller is put on hold for two minutes waiting on a slow supplier, that receptionist is stuck and cannot help anyone else. A few of those and the whole front desk jams.

A Continuation is the receptionist saying: "Let me take your request, hang up the internal line, and the supplier will ring us back when they are done. Then I will pick the answer up and finish your request." The slow wait happens off the Salesforce app server thread.

Technically: a synchronous callout from the UI holds a Salesforce application server thread for its whole duration. Salesforce limits how many requests can run longer than 5 seconds at once (the concurrent long-running request limit). A Continuation lets the action return the thread to the pool while the external system works, so a slow API does not eat your concurrency budget. When the response arrives, a callback method runs to process it and update the UI.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
A Lightning or Visualforce page calls a slow external API. The call is fast - just use a normal callout 01-http-callouts.md.
You want to protect the concurrent long-running request limit. The work is not tied to a screen → 05-asynchronous-callouts.md.
The user is waiting on the page for the result. You need to call out after DML in a backend flow → async.
You can live with max 3 parallel callouts per Continuation. You need many parallel callouts or a huge response.

Real-world examples: a Lightning component pulling a slow shipping-rate quote, a Visualforce page calling a sluggish legacy pricing service, an agent console widget fetching an external account 360 view that takes several seconds.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant UI as Lightning / VF Page participant APX as Apex Controller participant CONT as Continuation participant EXT as External API UI->>APX: User action calls @AuraEnabled method APX->>CONT: Build Continuation, add HttpRequest via callout:NamedCredential APX-->>UI: Return Continuation - app server thread released CONT->>EXT: Async HTTPS request Note over CONT,EXT: User thread is free. No 5s concurrency cost EXT-->>CONT: Response arrives later CONT->>APX: Platform invokes the callback method APX->>APX: Read response with getResponse, parse APX-->>UI: Return result, UI updates

Walkthrough

  1. The user triggers an @AuraEnabled (or VF action) method.
  2. The controller creates a Continuation, sets the callback method name, and adds one to three HttpRequest objects whose endpoint is callout:NamedCredential/....
  3. The method returns the Continuation object. Salesforce releases the app server thread back to the pool. 4-5. The platform sends the request asynchronously. The user's thread is not blocked, so it does not count against the concurrent long-running limit. 6-7. When the response returns, Salesforce calls the callback you named.
  4. The callback reads the response via Continuation.getResponse(requestLabel), parses it, and returns the result to the UI.

4. The actual code

Aura/LWC controller with a Continuation and callback

public with sharing class RateController {

    // 1) Action method: returns an Object (the Continuation)
    @AuraEnabled(continuation=true, cacheable=false)
    public static Object startRequest() {
        Continuation con = new Continuation(120); // timeout in seconds, max 120
        con.continuationMethod = 'processResponse'; // name of the callback

        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:Shipping_API/rates'); // URL + auth from Named Credential
        req.setMethod('GET');

        // addHttpRequest returns a label you use to fetch the matching response
        con.addHttpRequest(req);
        return con; // app server thread is released here
    }

    // 2) Callback: runs when the external response arrives
    @AuraEnabled(cacheable=false)
    public static Object processResponse(List<String> labels, Object state) {
        HttpResponse res = Continuation.getResponse(labels[0]);
        if (res.getStatusCode() == 200) {
            return res.getBody(); // hand the result back to the UI
        }
        return null; // handle non-200 in the UI
    }
}

Adding multiple parallel callouts (up to 3)

Continuation con = new Continuation(120);
con.continuationMethod = 'processResponses';
con.addHttpRequest(reqA);
con.addHttpRequest(reqB);
con.addHttpRequest(reqC); // a 4th would exceed the per-Continuation limit

Auth: Continuations support Named Credentials. If the endpoint is a callout: named credential, you do not need a Remote Site Setting. See Module 03.


5. Design considerations and gotchas

Consideration Why it matters What to do
Max 3 parallel callouts A single Continuation can hold at most 3 requests. Batch what you need, or chain Continuations.
120 s per callout Each callout in the Continuation can run up to 120 seconds. Set the Continuation timeout sensibly. Parallel calls finish in the time of the slowest.
Chaining limit You can chain up to 3 Continuations in one request, giving up to ~360 s total. Use chaining only if one round trip is not enough.
Response size cap The Continuation response is capped, commonly documented at 1 MB. Keep responses small. Page or filter on the external side.
Supported contexts only Works from Aura, LWC, and Visualforce, not from generic backend Apex. For non-UI background callouts use async.
Callback is a separate execution State is passed via the Continuation state, not instance variables. Stash anything the callback needs in con.state.
Testing Use Test.setContinuationResponse plus Test.invokeContinuationMethod. Mock the response, then assert the callback output.

6. Interview Q&A

Q: What problem does a Continuation solve? A: A synchronous callout from the UI holds a Salesforce application server thread for its whole duration, and there is a limit on how many requests can run longer than 5 seconds at once. A Continuation releases the thread while the external system works and resumes in a callback when the response arrives, so a slow API does not consume your concurrency budget or freeze the page.

Q: How many callouts can one Continuation make, and for how long? A: Up to 3 parallel callouts per Continuation, each up to 120 seconds. Because they run in parallel, total time is the slowest call. You can chain up to 3 Continuations for longer multi-step work.

Q: Where can you use Continuations? A: From supported UI contexts only - Aura components, Lightning Web Components, and Visualforce. They are not for generic backend Apex; for that you use @future, Queueable, or Batch.

Q: Do Continuations support Named Credentials? A: Yes. If the request endpoint is a callout: Named Credential, the URL and auth are injected and you do not need a Remote Site Setting.

Q: How is the response handled? A: Not inline. You name a callback method on the Continuation. When the response returns, the platform invokes that callback, which reads the response with Continuation.getResponse(label) and returns the result to the UI.

Talking point to explain it to anyone: "The receptionist takes your request, hangs up the internal line so they can help others, and the slow supplier rings back when ready. Salesforce frees the thread during the wait, then picks up the answer to finish your page."


7. Key terms

Continuation, callback, concurrent long-running request limit, app server thread, parallel callouts, Named Credential - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 07-callout-limits-and-testing.md - the full limits reference and how to test callouts.

07 - Callout Limits and Testing

One-liner: The limits that govern every outbound callout, and how to test callouts when real network calls are not allowed. Direction: Salesforce → External (outbound). Scope: Reference + reliability + testing. Use when: You are designing for scale, debugging a limit error, or writing test coverage for callout code.

This is the final outbound file in Module 05. It ties together 01-http-callouts.md, 05-asynchronous-callouts.md, and 06-continuation-pattern.md. Auth lives in Module 03.


1. The idea in plain English

Every callout runs inside a speed limit and a weight limit. Salesforce caps how many calls you make, how big the payload is, how long each call may take, and how many slow calls the whole org runs at once. Cross a line and you get a runtime exception, not a slow response.

And there is a second rule that trips everyone: you cannot make a real network call inside an Apex test. Tests must run fast and deterministically with no external dependency, so the platform forces you to supply a fake response with a mock. Knowing both - the limits and the mocks - is what separates an integration that survives production from one that fails on the first busy Monday.


2. The limits (the table to memorize)

Limit Value Notes
Callouts per transaction 100 Across all callouts (HTTP and web service) in one Apex transaction. Bulkify, do not loop.
Max request or response size (sync) 6 MB Synchronous transaction heap context.
Max request or response size (async) 12 MB Higher ceiling in async (Queueable, Batch, @future).
Timeout per callout 120 s Default is 10 s. Set with req.setTimeout(ms), max 120000.
Total callout time per transaction 120 s Sum of all callout time in the transaction.
Concurrent long-running requests 10 Synchronous requests running > 5 s count; org-wide. Newer orgs scale higher by license, minimum 10.
No callout after uncommitted DML rule A pending DML in the same transaction blocks the callout.
Continuation parallel callouts 3 Per Continuation, each up to 120 s. Up to 3 Continuations chained. See 06.
flowchart LR A[Outbound callout] --> B{Sync or async?} B -->|Sync| C[6 MB payload<br/>counts toward<br/>10 concurrent if > 5s] B -->|Async| D[12 MB payload<br/>does not count toward<br/>concurrent limit] A --> E[100 callouts / txn] A --> F[120s per callout<br/>120s total / txn] style C fill:#00A1E0,color:#fff style D fill:#4CAF50,color:#fff style E fill:#ff9900,color:#fff style F fill:#9C27B0,color:#fff

The no-callout-after-DML rule. Once your transaction issues DML and has not committed, a callout throws You have uncommitted work pending. Fix it by calling out before any DML, or by moving the callout to async so it runs in its own transaction. This is the single most common callout error in interviews and in real code.

The concurrent long-running request limit. Any synchronous request that runs longer than 5 seconds counts against an org-wide pool of (historically) 10. Batch Apex, @future, scheduled jobs, and Bulk API do not count. This is exactly why slow UI callouts should use a Continuation, which releases the thread.


3. Testing callouts (mocks are mandatory)

Real callouts are blocked in tests. A test that calls a live endpoint fails with "Methods defined as TestMethod do not support Web service callouts." You must inject a fake response.

Tool For What it does
HttpCalloutMock HTTP/REST callouts Interface you implement to return a fake HttpResponse.
WebServiceMock SOAP callouts Interface for mocking generated WSDL stub responses.
StaticResourceCalloutMock HTTP from a file Built-in class that returns a static resource as the response body.
Test.setMock wiring Registers your mock so the platform uses it during the test.
Test.startTest() / Test.stopTest() async Forces queued async work (Queueable, @future, Batch) to run, so async callouts execute.

HttpCalloutMock example

@isTest
private class OrderSyncTest {

    // 1) The mock returns a canned response
    private class MockResponse implements HttpCalloutMock {
        public HttpResponse respond(HttpRequest req) {
            HttpResponse res = new HttpResponse();
            res.setHeader('Content-Type', 'application/json');
            res.setBody('{"status":"ok","id":"A-100"}');
            res.setStatusCode(200);
            return res;
        }
    }

    @isTest
    static void testQueueableCallout() {
        Test.setMock(HttpCalloutMock.class, new MockResponse()); // register mock

        Test.startTest();
        System.enqueueJob(new OrderSyncQueueable(/* test orders */ null));
        Test.stopTest(); // async callout runs here, using the mock

        // assert the side effects (records flagged, fields set, etc.)
    }
}

For SOAP, implement WebServiceMock and register it the same way with Test.setMock(WebServiceMock.class, ...). For Continuations, use Test.setContinuationResponse and Test.invokeContinuationMethod instead.


4. Reliability: error handling, retries, idempotency, logging

A callout can return a bad status, time out, or hit a down service. Production code plans for all of it.

Concern What can go wrong What to do
4xx errors Bad request, auth failure, not found. Do not blindly retry. Log, surface, fix the request. 401 may mean rotate/refresh the credential.
5xx errors The external service failed. Retry with backoff, capped attempts. These are often transient.
Timeouts Service too slow or hung. Catch CalloutException. Retry async or fail gracefully. Consider a shorter setTimeout.
Retries A naive retry storm worsens an outage. Limit attempts, add exponential backoff, ideally route through a calling-out Queueable.
Idempotency A retry double-creates or double-charges. Send an idempotency key or check-before-create so repeats are safe.
Logging Silent failures are invisible. Persist request/response metadata to a custom object or platform event. Never log secrets.
try {
    HttpResponse res = new Http().send(req);
    Integer code = res.getStatusCode();
    if (code == 200) {
        // success
    } else if (code >= 500) {
        // transient: enqueue a bounded retry
    } else {
        // 4xx: log and stop, do not retry blindly
    }
} catch (System.CalloutException e) {
    // timeout or connection failure: log and retry async or fail gracefully
}

5. Design considerations and gotchas

Consideration Why it matters What to do
Bulkification A trigger calling out per record blows the 100/txn cap fast. Aggregate, then call out once per batch. Move to Queueable/Batch.
Sync vs async size A 9 MB payload fails sync but works async. Choose the context that fits the payload (6 MB sync, 12 MB async).
Slow calls and concurrency Sync calls > 5 s eat the org-wide pool of 10. Use a Continuation or push to async.
DML ordering Callout after DML throws. Call out first, or async. See 05.
No live calls in tests Tests fail or are non-deterministic. Always mock with HttpCalloutMock / WebServiceMock.
Async runs at stopTest Forgetting startTest/stopTest means async never executes. Wrap enqueue/batch between Test.startTest() and Test.stopTest().

6. Interview Q&A

Q: What are the core callout limits? A: 100 callouts per transaction. Request/response max 6 MB synchronous, 12 MB asynchronous. 120 s max per callout and 120 s total per transaction (default per-callout timeout is 10 s). Plus the org-wide concurrent long-running request limit.

Q: What is the concurrent long-running request limit? A: Synchronous Apex requests running longer than 5 seconds count against an org-wide pool (historically 10). Exceeding it throws a runtime exception on new sync requests. Async work - Batch, @future, scheduled, Bulk API - does not count, which is why slow UI callouts use a Continuation.

Q: How do you test a callout if live calls are blocked? A: Implement HttpCalloutMock (or WebServiceMock for SOAP), register it with Test.setMock, and have it return a canned HttpResponse. You can also use StaticResourceCalloutMock to serve a response from a static resource. Then assert on the result.

Q: How do you test an asynchronous callout specifically? A: Same mock, but wrap the enqueue or batch call between Test.startTest() and Test.stopTest(). The async job runs synchronously at stopTest, executing the callout against the mock so you can assert side effects.

Q: How do you make callouts reliable? A: Handle 4xx (log and fix, do not retry blindly) and 5xx/timeouts (bounded retry with backoff). Make requests idempotent with a key so retries are safe. Log request/response metadata - never secrets - for traceability.

Talking point to explain it to anyone: "Every call has a speed limit, a weight limit, and a rule that you cannot call out after you have started changing data. And you can never make a real call in a test, so you hand the code a pretend answer and check it does the right thing with it."


7. Key terms

Governor limit, callout limit, concurrent long-running request limit, uncommitted DML, HttpCalloutMock, WebServiceMock, StaticResourceCalloutMock, Test.setMock, idempotency, backoff - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: back to the README.md for the module map. From here, Module 06 (Event-Driven) covers Platform Events and Change Data Capture, and Module 07 (Bulk and Async) goes deeper on Batch, Queueable chaining, and high-volume processing - the natural continuation of outbound integration.

Module 06 - Event-Driven Integration

Goal: Stop polling. Use events. This is the modern way to integrate in real time. API version: v66.0 (Spring '26). Retention: high-volume Platform Events and CDC are kept on the event bus for 72 hours (not 96). Standard-volume events (24h) are retired.

Event-driven means a publisher broadcasts a change and subscribers react, with no direct coupling and no polling. Start with 01-event-driven-basics.md for the event bus and pub/sub model. New to push vs pull? See Module 01.


How to use this module

  1. Read 01-event-driven-basics.md (event bus, replay, retention).
  2. Learn the two event sources: Platform Events (you define) and CDC (automatic).
  3. Learn the modern subscriber API: Pub/Sub API.
  4. Know the legacy mechanisms and the how-to of publishing/subscribing/replay.

Map of this module

# File What it covers
01 event-driven-basics Event bus, pub/sub, replay, 72h retention
02 platform-events Custom events you define and publish (__e)
03 change-data-capture Automatic events on record changes
04 pub-sub-api Modern gRPC + Avro publish/subscribe
05 streaming-api-and-outbound-messages Legacy push (PushTopic, generic, SOAP)
06 publishing-subscribing-and-replay The how-to across all event types

Which event mechanism? (decision tree)

flowchart TD Start([Need event-driven integration]) --> Q1{What is the source?} Q1 -->|Any record create/update/delete| Q2{Custom payload needed?} Q2 -->|No - mirror the change| CDC[Change Data Capture] Q2 -->|Yes - business signal| PE1[Platform Event] Q1 -->|An app, Flow, or Apex action| PE2[Platform Event] Q1 -->|Legacy, already built| LEG[Streaming API / Outbound Messages] CDC --> SUB{Who subscribes?} PE1 --> SUB PE2 --> SUB SUB -->|External app / microservice| PUB[Pub/Sub API] SUB -->|Inside Salesforce| INT[Apex trigger / Flow / empApi] style CDC fill:#00A1E0,color:#fff style PE1 fill:#ff9900,color:#fff style PE2 fill:#ff9900,color:#fff style PUB fill:#9C27B0,color:#fff style INT fill:#4CAF50,color:#fff style LEG fill:#F44336,color:#fff

Comparison table

Mechanism Who creates the event Schema Subscribers When to use
Platform Events (02) You (intentional business event) Custom fields you define Apex, Flow, empApi, Pub/Sub "OrderShipped", "PaymentReceived"
Change Data Capture (03) Salesforce (auto on record change) Fixed: header + changed fields Apex, Flow, empApi, Pub/Sub Mirror data to an external store
Pub/Sub API (04) n/a (transport for PE + CDC) Avro per topic External apps, microservices Efficient external publish/subscribe
Streaming API (05) PushTopic (SOQL) / generic JSON CometD clients ⚠️ Legacy. Avoid for new builds
Outbound Messages (05) Workflow / Flow (declarative) Configured field set A SOAP endpoint ⚠️ Legacy declarative push

Platform Events vs Change Data Capture (the classic question)

Question Platform Events CDC
Who creates the event? You (a deliberate signal) Salesforce (automatic)
Schema You define the fields Fixed: ChangeEventHeader + changed fields
Best for Business events Replicating record changes
Customizable payload? Yes No

What changed worth knowing (2025-2026)

  • Retention is 72 hours for high-volume Platform Events and CDC. The "96 hours" figure is a myth. (Legacy standard-volume events were 24h and are retired.)
  • Pub/Sub API (gRPC + Avro) is the modern subscriber API. The Streaming API (CometD) is legacy for new builds.
  • Managed Event Subscriptions (Beta) let the Pub/Sub API track Replay IDs server-side.
  • Outbound Messages remain, but Workflow Rules / Process Builder are no longer enhanced; build new declarative push in Flow.

Interview rapid-fire

Q: Platform Events vs CDC? → Platform Events are custom business signals you publish on purpose. CDC is automatic, fixed-schema events on record changes, ideal for data replication.

Q: How do subscribers recover missed events? → Store the Replay ID and resubscribe from it within the 72-hour window (-1 new-only, -2 all retained).

Q: How should an external microservice subscribe today? → The Pub/Sub API (gRPC + Avro), not the legacy CometD Streaming API.

Q: Is delivery exactly-once? → No, at-least-once. Subscribers must be idempotent.


Sources (Verified June 2026)

Each file has its own Sources section with the specific official doc.

01 - Event-Driven Basics

One-liner: Instead of one system constantly asking "anything new yet?", the producer broadcasts an event the moment something happens, and any interested system just listens. Why it matters: This is the modern, efficient alternative to polling. It decouples systems and enables real-time integration. Goal of this file: Understand the event bus, the publish/subscribe model, and replay, so every other file in this module clicks.

This is Module 06. New to push vs pull? See Module 01 - push, pull, and webhooks. For where this sits among patterns, see Module 02 - UI Update / Fire and Forget.


1. The idea in plain English

Event-driven integration is a radio station. The station (publisher) broadcasts a show (event) without knowing or caring who is listening. Anyone with a radio (subscriber) can tune to that frequency (channel) and hear it. The station does not call each listener; it broadcasts once, and the infrastructure delivers to everyone tuned in.

Contrast that with polling, which is calling the radio station every 30 seconds to ask "are you broadcasting anything?" Polling wastes calls, adds lag, and tightly couples the two sides. Events flip it around: publish once, many subscribers react, nobody waits on anybody. That decoupling is the whole point.


2. Polling (pull) vs event-driven (push)

Polling (pull) Event-driven (push)
Who initiates Consumer asks repeatedly Producer broadcasts on change
Freshness Laggy (next poll cycle) Near real-time
Efficiency Wasteful (most polls find nothing) Efficient (only fires on real change)
Coupling Tight (consumer knows the source) Loose (publisher and subscriber independent)
Salesforce example A job querying every 5 min for new Cases A Platform Event fired when a Case is escalated

Rule of thumb: if you find yourself scheduling a job to "check for changes," an event is almost always the better design.


3. The event bus and the pub/sub model

At the center sits the event bus: a multitenant, time-ordered event log. Publishers write to it, subscribers read from it, and the two never talk directly.

flowchart LR A[Apex<br/>EventBus.publish] --> BUS F[Flow] --> BUS R[REST / API] --> BUS REC[Record change] --> CDC[Change Data Capture] --> BUS BUS{{Event Bus<br/>time-ordered log<br/>72-hour retention}} BUS --> S1[Apex trigger] BUS --> S2[Flow] BUS --> S3[LWC via empApi] BUS --> S4[External app<br/>via Pub/Sub API] style BUS fill:#ff9900,color:#fff style CDC fill:#00A1E0,color:#fff style S4 fill:#9C27B0,color:#fff

Key properties of the bus

  • Time-ordered: events are stored and delivered in the order Salesforce received them.
  • Durable with replay: each event gets a Replay ID marking its position. Subscribers store the last Replay ID they processed and can resubscribe from there to catch up.
  • Retention window: high-volume platform events and Change Data Capture events are retained for 72 hours (3 days). After that they age out of the bus.
  • At-least-once delivery: a subscriber may occasionally receive an event more than once, so handlers should be idempotent.

4. The Salesforce event family (what's in this module)

Mechanism Who creates it File
Platform Events You define and publish a custom event 02-platform-events.md
Change Data Capture (CDC) Salesforce auto-fires on record create/update/delete/undelete 03-change-data-capture.md
Pub/Sub API The modern gRPC API to publish and subscribe (incl. external apps) 04-pub-sub-api.md
Streaming API + Outbound Messages Legacy push (PushTopic, generic, declarative SOAP) 05-streaming-api-and-outbound-messages.md
Publishing & subscribing & replay The how-to across all of the above 06-publishing-subscribing-and-replay.md

5. Key concepts to carry forward

  • Channel / topic: the named stream a subscriber listens to (e.g. /event/Order_Shipped__e, /data/AccountChangeEvent).
  • Replay ID: the cursor into the event stream. -1 = only new events, -2 = all retained events from the start of the window, or a specific ID to resume.
  • Retention = 72 hours for high-volume events and CDC. (Legacy standard-volume platform events retained only 24 hours and can no longer be created. They are being retired.)
  • Decoupling: publishers and subscribers deploy, scale, and fail independently.
  • Idempotency: design subscribers to tolerate duplicate or replayed events.

6. Common confusions and interview traps

Confusion The clarification
"Events are guaranteed exactly once." Delivery is at-least-once. Handle possible duplicates.
"Platform Events and CDC are the same." You design Platform Events for business signals. Salesforce auto-generates CDC from record changes. See 02 vs 03.
"Retention is 96 hours." It is 72 hours for high-volume events and CDC. (A common myth.)
"Streaming API is the way to subscribe." For new builds use the Pub/Sub API. Streaming API (PushTopic/generic) is legacy.
"Events replace all integration." Events suit notifications and real-time sync, not request/reply or bulk loads.

7. Interview Q&A

Q: What is event-driven integration and why use it over polling? A: A publisher broadcasts an event to an event bus and subscribers react, with no direct coupling. It beats polling because it is real-time, avoids wasted "anything new?" calls, and lets both sides evolve independently.

Q: What is the event bus? A: A multitenant, time-ordered log where Salesforce stores published events and delivers them to subscribers in order, with a 72-hour retention window and Replay IDs for catch-up.

Q: What is a Replay ID? A: An opaque marker of an event's position in the stream. Subscribers persist the last one they processed and resubscribe from it to recover missed events within the 72-hour window. -1 means new-only, -2 means everything retained.

Q: Is delivery guaranteed exactly once? A: No. It is at-least-once, so subscribers must be idempotent to handle occasional duplicates or replays.

Q: Platform Events vs Change Data Capture? A: Platform Events are custom business events you define and publish intentionally. CDC is automatic, fixed-schema events Salesforce fires whenever records change, ideal for replicating data to an external store.

Talking point to explain it to anyone: "It's a radio broadcast. Salesforce announces 'this happened' once, and any system tuned in reacts, instead of everyone phoning in to ask every few seconds."


8. Key terms

Event bus, publish/subscribe, channel, Replay ID, retention, at-least-once, idempotency, CDC - defined here and in the Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 02-platform-events.md - the custom events you define and publish.

02 - Platform Events

One-liner: You define a custom event (like Order_Shipped__e), publish it when something meaningful happens, and any number of subscribers react. Why it matters: This is the purpose-built way to broadcast intentional business signals inside and outside Salesforce, decoupled and near real-time. Use when: You want to announce "this business thing just happened" and let other systems decide what to do about it.

This is Module 06. New to the event bus, channels, and replay? Start with 01-event-driven-basics.md. For the auto-generated cousin, see 03-change-data-capture.md.


1. The idea in plain English

A Platform Event is a press release. You decide exactly what it says (the fields), you decide when to issue it (publish), and you send it to the wire (the event bus). You do not phone each reporter. Anyone subscribed to that wire picks it up and acts: one team writes a story, another files it, a third ignores it. You, the publisher, never know or care who is listening.

Contrast this with Change Data Capture, where Salesforce auto-issues a "this record changed" bulletin for you. With Platform Events you design the message and you choose the moment. That is the whole distinction: Platform Events are you-defined business signals, CDC is Salesforce-defined data changes.

In Salesforce terms: you create a Platform Event object with the __e suffix, add custom fields, then publish instances via Apex EventBus.publish(), Flow, or the REST/SOAP API. Subscribers react via Apex triggers, Flow, LWC, or external apps.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You want to broadcast a business signal you control, like Order_Shipped__e. You just need to mirror record changes to an external store → 03-change-data-capture.md.
Multiple decoupled subscribers should react independently. You need a synchronous answer back → Request and Reply.
You want fire-and-forget notifications that survive load spikes. You are doing a bulk data load → Batch sync.
You need internal process-to-process decoupling (publish in one transaction, handle in another). You need guaranteed exactly-once. Delivery is at-least-once, so subscribers must be idempotent.

Real-world examples: Order_Shipped__e notifying a fulfillment portal, Payment_Received__e triggering downstream provisioning, Print_Job__e fanning a request to an on-prem printer service, a microservice publishing Inventory_Low__e back into Salesforce.


3. How it works (Mermaid + walkthrough)

flowchart LR P1[Apex<br/>EventBus.publish] --> BUS P2[Flow<br/>Create Records] --> BUS P3[REST / SOAP API] --> BUS P4[External app<br/>via Pub/Sub API] --> BUS BUS{{Event Bus<br/>channel /event/Order_Shipped__e<br/>72-hour retention}} BUS --> S1[Apex trigger<br/>after insert] BUS --> S2[Platform Event<br/>triggered Flow] BUS --> S3[LWC via empApi] BUS --> S4[External app<br/>via Pub/Sub API] style BUS fill:#ff9900,color:#fff style P4 fill:#9C27B0,color:#fff style S4 fill:#9C27B0,color:#fff

Walkthrough

  1. You publish an event instance to its channel, /event/Order_Shipped__e.
  2. Salesforce persists it to the event bus (time-ordered, 72-hour retention for high-volume events).
  3. Each event gets a Replay ID marking its position in the stream.
  4. All subscribers receive the event independently and asynchronously.
  5. Apex triggers and Flows run server-side; LWC and external apps stream it down.
  6. A slow or failed subscriber does not block the publisher or other subscribers.

4. The actual code and config

Define the event object

In Setup → Platform Events → New Platform Event. The object gets the __e suffix automatically (for example Order_Shipped__e). Add custom fields like Order_Number__c (Text) and Tracking__c (Text). You also pick a Publish Behavior:

Publish Behavior When the event is published Use when
Publish After Commit Only after the current transaction commits successfully. If the transaction rolls back, the event is not published. You need the event to reflect data that actually committed (the common, safe default).
Publish Immediately At the moment of the publish call, outside the database transaction, even if the transaction later fails. You want a signal regardless of outcome, for example logging or audit, fully decoupled from the DML result.

Publish via Apex

List<Order_Shipped__e> events = new List<Order_Shipped__e>();
events.add(new Order_Shipped__e(
    Order_Number__c = 'SO-1001',
    Tracking__c     = '1Z999AA10123456784'
));

List<Database.SaveResult> results = EventBus.publish(events);

for (Database.SaveResult sr : results) {
    if (!sr.isSuccess()) {
        for (Database.Error err : sr.getErrors()) {
            System.debug('Publish failed: ' + err.getMessage());
        }
    }
}

A successful SaveResult means the event was accepted for publishing, not yet delivered. For confirmed final results, use a publish callback (EventBus.publish(events, callback)).

Publish via Flow

Use a Create Records element and pick the Platform Event as the record type, or a dedicated platform-event action. No Apex needed.

Publish via REST API

POST to the sObject endpoint, same shape as any record:

POST /services/data/v66.0/sobjects/Order_Shipped__e/
{ "Order_Number__c": "SO-1001", "Tracking__c": "1Z999AA10123456784" }

Subscribe four ways

Subscriber How Notes
Apex trigger trigger OnOrderShipped on Order_Shipped__e (after insert) Only after insert is valid. Runs async; up to 2,000 events per execution. Use EventBus.TriggerContext + setResumeCheckpoint(replayId) to resume after an error.
Flow Platform Event-Triggered Flow Low-code subscriber, one event per run.
LWC empApi (subscribe) on /event/Order_Shipped__e In-browser, uses CometD under the hood.
External app Pub/Sub API (gRPC) The modern path for microservices. See 04-pub-sub-api.md.
trigger OnOrderShipped on Order_Shipped__e (after insert) {
    for (Order_Shipped__e evt : Trigger.New) {
        // idempotent handler: safe if this event arrives twice
        System.debug('Shipped order ' + evt.Order_Number__c);
    }
}

High-volume vs standard-volume

High-volume platform events are the modern default and are retained 72 hours. Standard-volume platform events (24-hour retention) can no longer be created and are being retired. Always design on high-volume.


5. Design considerations and gotchas

Consideration Why it matters What to do
Publish Behavior choice After Commit ties the event to a successful transaction; Immediately decouples it entirely. Default to After Commit unless you truly want the signal even on rollback.
At-least-once delivery A subscriber may receive the same event twice. Make handlers idempotent (dedupe on a key).
Trigger is after insert only Events are immutable; there is no update/delete trigger. Put all logic in after insert.
2,000 events per trigger run A burst is chunked. An uncaught error can re-fire the whole chunk. Use setResumeCheckpoint(replayId) to resume past processed events.
Publish and delivery allocations Orgs have limits on events published per hour and event deliveries per 24 hours (delivery to CometD/empApi). Monitor PlatformEventUsageMetric; see the allocations page.
No SOQL on event objects You cannot query past events with SOQL. Replay from the bus via Replay ID within 72h, or persist to a record if you need history.
Field types are limited Not every field type is allowed on a __e object. Use supported types (Text, Number, Checkbox, Date/Time, etc.).

6. Interview Q&A

Q: What is a Platform Event and how is it different from CDC? A: A Platform Event is a custom event you define (__e object) and publish intentionally to signal a business occurrence. CDC is auto-fired by Salesforce on record create/update/delete/undelete with a fixed schema. Platform Events are you-defined signals; CDC is data replication.

Q: Publish After Commit vs Publish Immediately? A: After Commit publishes the event only if the transaction commits, so subscribers can trust the committed data. Immediately publishes outside the transaction, even if it later rolls back, fully decoupling publish from the DML result. After Commit is the safe default.

Q: How do you publish and subscribe in Apex? A: Publish with EventBus.publish(listOfEvents). Subscribe with an after insert trigger on the event object. The trigger is asynchronous and processes up to 2,000 events per run.

Q: Delivery is occasionally duplicated. How do you handle it? A: Delivery is at-least-once, so handlers must be idempotent, for example by checking a dedupe key or external ID before acting, so a replayed event causes no double effect.

Q: A burst of events causes a trigger error midway. How do you avoid reprocessing everything? A: Use EventBus.TriggerContext and setResumeCheckpoint(replayId) to checkpoint the last good Replay ID, so the retry resumes after the processed events instead of from the start of the chunk.

Talking point to explain it to anyone: "It's a press release. I write the message, I choose when to send it to the wire, and anyone subscribed reacts on their own. I never call them directly."


7. Key terms

Platform Event, __e object, Publish After Commit, Publish Immediately, EventBus.publish, empApi, high-volume vs standard-volume, Replay ID, idempotency - defined here and in 01-event-driven-basics.md and the README.


Sources (Verified June 2026)


Next: 03-change-data-capture.md - the events Salesforce fires automatically when records change.

03 - Change Data Capture

One-liner: Turn it on for an object and Salesforce automatically fires an event every time a record changes, so you never write publish code. Why it matters: This is the purpose-built way to mirror Salesforce data into an external store or warehouse in near real-time. Use when: You need an external system to stay in sync with Salesforce record changes without polling.

This is Module 06. New to the event bus and replay? See 01-event-driven-basics.md. For events you design and publish, see 02-platform-events.md.


1. The idea in plain English

Change Data Capture (CDC) is a security camera on your data. Once you point it at an object, it records every change automatically: a record is created, updated, deleted, or undeleted, and CDC quietly fires an event describing what happened. You did not press record each time; the camera just runs.

Contrast this with Platform Events, where you write the message and choose the moment, like issuing a press release. CDC is the opposite: Salesforce decides the schema and the timing for you. The payload is fixed, you cannot add custom fields to it, and it fires on the database change itself.

The killer use case is replication. You stand up an external database or data warehouse, subscribe to CDC, and apply each change as it arrives. The external copy stays current without a single scheduled query.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You want to replicate / mirror Salesforce records to an external store or warehouse. You want a custom business signal with your own fields → 02-platform-events.md.
You need to react to any create/update/delete/undelete on an object. You only care about one specific business moment, not raw record churn.
You want no publish code: enable per object and you are done. You need to customize the payload. CDC schema is fixed.
You need the before/after-style change detail (which fields changed). You need a synchronous answer back → Request and Reply.

Real-world examples: keeping a reporting data warehouse in sync, feeding an external search index, mirroring Accounts and Contacts into a customer data platform, driving an audit/replication pipeline in a microservice.


3. How it works (Mermaid + walkthrough)

flowchart LR REC[Record create / update<br/>delete / undelete] --> CDC[Change Data Capture<br/>auto-fires] CDC --> BUS{{Event Bus<br/>channel /data/AccountChangeEvent<br/>72-hour retention}} BUS --> S1[Apex trigger<br/>on AccountChangeEvent] BUS --> S2[Flow] BUS --> S3[External warehouse<br/>via Pub/Sub API] style CDC fill:#00A1E0,color:#fff style BUS fill:#ff9900,color:#fff style S3 fill:#9C27B0,color:#fff

Walkthrough

  1. A record changes (create, update, delete, or undelete).
  2. CDC automatically generates a change event, no publish code from you.
  3. The event lands on the object channel, for example /data/AccountChangeEvent.
  4. It carries a ChangeEventHeader plus the changed record fields.
  5. Subscribers (Apex, Flow, or external apps via Pub/Sub API) apply the change.
  6. Events are retained 72 hours; a subscriber can replay within that window.

4. The actual config and channels

Enable CDC

In Setup → Change Data Capture, move objects from Available Entities into Selected Entities. (You can also enable it via Metadata API with a PlatformEventChannelMember.) When you select an object, Salesforce creates its change event behind the scenes, for example Account → AccountChangeEvent, custom Shipment__c → Shipment__ChangeEvent.

Channels

Channel Listens to Format
Single standard object One standard object /data/AccountChangeEvent
Single custom object One custom object /data/Shipment__ChangeEvent
All changes Every CDC-enabled object /data/ChangeEvents
Custom channel A curated set of objects you group /data/MyChannel__chn

Use a custom channel when different subscribers need different subsets of objects, or to scope event enrichment.

The ChangeEventHeader

Every change event includes a ChangeEventHeader. Key fields:

Field What it tells you
changeType CREATE, UPDATE, DELETE, or UNDELETE.
entityName The object, for example Account.
recordIds The record IDs affected (a list; one event can cover several records in a transaction).
changedFields The fields that changed (for updates), so you apply only deltas.
commitNumber The system change number of the commit, for ordering.
commitTimestamp When the change was committed.
commitUser The user ID that made the change.
transactionKey / sequenceNumber Group and order changes within the same transaction.

Sample change event payload (conceptual):

{
  "ChangeEventHeader": {
    "entityName": "Account",
    "changeType": "UPDATE",
    "recordIds": ["001xx000003DGb2AAG"],
    "changedFields": ["Phone", "Website"],
    "commitNumber": 10282948327,
    "commitTimestamp": 1718700000000,
    "commitUser": "005xx000001Sv6BAAS"
  },
  "Phone": "+1-415-555-0100",
  "Website": "https://example.com"
}

Subscribe

Same four ways as Platform Events: Apex trigger on the change event object (after insert), Flow, LWC empApi, and external apps via Pub/Sub API (the usual choice for replication). See 04-pub-sub-api.md.

Gap events and overflow

  • Gap event: when Salesforce cannot generate a normal change event (for example certain errors or field conversions), it sends a gap event with a header but no field values. Its changeType is suffixed, like GAP_UPDATE. Subscribers should query the record to resync.
  • Overflow event: when a single transaction changes more records than a threshold, CDC sends an overflow event instead of one event per record. Treat it as a signal to reload the affected scope.

Both exist so your replica can self-heal instead of silently drifting.


5. Design considerations and gotchas

Consideration Why it matters What to do
Fixed schema You cannot add custom fields to the change event payload. If you need extra context, use event enrichment on a custom channel, or switch to a Platform Event.
72-hour retention Events older than 72h are purged from the bus. Keep subscribers healthy; replay within the window or do a full reload.
Gap events A gap means data could be out of sync. On a gap, re-query the affected records to resync the replica.
Overflow events A bulk transaction collapses into one overflow event. Reload the affected object set rather than expecting per-record events.
At-least-once + ordering Duplicates are possible; ordering is per commitNumber/commitTimestamp. Make the apply step idempotent and order by commit metadata.
Allocations CDC shares event delivery allocations and has its own limits. See CDC Allocations.
Deletes carry few fields A DELETE event mostly has the header and ID. Use recordIds + changeType to remove the row in the replica.

6. Interview Q&A

Q: What is Change Data Capture? A: An event feature where Salesforce automatically fires a change event whenever a record is created, updated, deleted, or undeleted on an enabled object. The payload has a fixed schema with a ChangeEventHeader plus changed fields. It is built for replicating Salesforce data to external stores.

Q: How is CDC different from Platform Events? A: CDC is auto-generated by Salesforce on record changes with a fixed schema, you write no publish code. Platform Events are custom events you define and publish intentionally. CDC = data replication; Platform Events = you-defined business signals.

Q: What is in the ChangeEventHeader? A: It identifies the change: changeType (create/update/delete/undelete), entityName, recordIds, changedFields, and commit metadata like commitNumber, commitTimestamp, and commitUser. The changedFields list lets you apply only the delta.

Q: What are gap and overflow events? A: A gap event fires when Salesforce cannot produce a normal change event, signaling the subscriber to re-query and resync. An overflow event fires when one transaction changes more records than a threshold, replacing per-record events; the subscriber reloads the affected scope.

Q: How long are CDC events retained, and how do you catch up after downtime? A: 72 hours. A subscriber stores the last Replay ID it processed and resubscribes from it within that window. If it falls outside 72 hours, do a full reload of the data.

Talking point to explain it to anyone: "It's a security camera on your records. Flip it on for an object and Salesforce automatically broadcasts every change, so an outside system can mirror your data without ever asking 'what's new?'"


7. Key terms

Change Data Capture, change event, ChangeEventHeader, changeType, changedFields, gap event, overflow event, /data/AccountChangeEvent, /data/ChangeEvents, custom channel - defined here and in 01-event-driven-basics.md and the README.


Sources (Verified June 2026)


Next: 04-pub-sub-api.md - the modern gRPC API for publishing and subscribing at scale.

04 - Pub/Sub API

One-liner: One modern gRPC API to publish, subscribe, and fetch schemas for every Salesforce event type, built for external systems at scale. Why it matters: It is the modern replacement for the CometD Streaming API for new builds, with flow control and efficient binary streaming. Use when: An external system (a microservice, a data pipeline) needs to publish or subscribe to Salesforce events efficiently and at volume.

This is Module 06. New to channels, Replay IDs, and retention? See 01-event-driven-basics.md. For the event types it carries, see 02-platform-events.md and 03-change-data-capture.md.


1. The idea in plain English

The Pub/Sub API is a universal remote for Salesforce events. Before it, you reached for different tools for different jobs: one API to publish, another (CometD Streaming API) to subscribe, a third to fetch schemas. The Pub/Sub API is one device that does all of it, for Platform Events, Change Data Capture, and real-time event monitoring events alike.

It speaks gRPC over HTTP/2 and ships event payloads as Apache Avro binary, so it is compact and fast. And it is pull-based: the subscriber tells Salesforce "send me N events" and only gets what it asked for. That is flow control, and it stops a downstream service from being flooded during a publish spike.

If you are building anything external today, this is the API. The older CometD Streaming API still works but is legacy for new integrations.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
An external system publishes or subscribes to Salesforce events. The subscriber lives inside Salesforce → use an Apex trigger or Flow.
You need high throughput with backpressure (flow control). You need a quick in-browser stream in a component → LWC empApi.
You want one API across Platform Events, CDC, and event monitoring. You are committed to legacy CometD clients already → 05-streaming-api-and-outbound-messages.md.
You want to replay missed events within the 72-hour window. You need a synchronous request/reply → Request and Reply.

Real-world examples: a Node microservice subscribing to Order_Shipped__e, a Python data pipeline consuming /data/AccountChangeEvent into a warehouse, an inventory service publishing Inventory_Low__e back into Salesforce, a cross-cloud integration reacting to real-time event monitoring.


3. How it works (sequence diagram + walkthrough)

sequenceDiagram autonumber participant C as External Client<br/>(gRPC) participant PS as Pub/Sub API participant BUS as Event Bus C->>PS: GetSchema / GetTopic (Avro schema + topic info) C->>PS: Subscribe (FetchRequest, numRequested = N, replayPreset) PS->>BUS: Read up to N events from topic BUS-->>PS: Events (Avro binary) PS-->>C: FetchResponse: events + Replay ID each C->>C: Decode Avro, process, store last Replay ID C->>PS: FetchRequest (request next N) flow control Note over C,PS: Pull-based: client never gets more than it asked for

Walkthrough

  1. The client fetches the Avro schema (GetSchema) and topic info (GetTopic) so it can decode payloads.
  2. It calls Subscribe, passing a FetchRequest with numRequested (how many events it can handle, max 100) and a replayPreset (new-only, earliest retained, or a specific Replay ID).
  3. Salesforce returns up to that many events in one or more FetchResponse messages.
  4. Each event carries a Replay ID. The client persists the last one it processed.
  5. To keep receiving, the client sends another FetchRequest. It is never sent more than it asked for. That is flow control.
  6. After downtime, it resubscribes from its stored Replay ID to catch up within 72 hours.

4. The actual API and config

gRPC and Avro

The service is defined in a .proto file using Protocol Buffers as the IDL. The event payload inside each message is Apache Avro binary, so before you publish you Avro-encode, and when you receive you Avro-decode. Clients exist in 11 gRPC languages (Python, Java, Node, C++, Go, etc.).

The RPC methods

RPC method What it does
Publish Publish a batch of events in one request.
PublishStream Publish via a bidirectional stream for sustained high-volume publishing.
Subscribe Subscribe with client-driven flow control (you request N events at a time).
ManagedSubscribe (Beta) Like Subscribe, but Salesforce tracks your Replay ID server-side so you do not manage it yourself. Backed by a ManagedEventSubscription (Tooling/Metadata API).
GetSchema Fetch the Avro schema for a topic by schema ID.
GetTopic Fetch topic info (the channel and its schema ID).

Replay and the replay window

Set the starting point in the subscribe call via replayPreset:

replayPreset Starts from
LATEST Only new events from now (like Replay ID -1).
EARLIEST The earliest retained event in the window (like -2).
CUSTOM A specific stored Replay ID you provide.

Stored events are available for the 72-hour retention window, so a client can resume up to three days back.

Flow control in practice

numRequested caps how many events Salesforce delivers per request (max 100). The FetchResponse reports pendingNumRequested, how many of your requested events are still pending. The client requests the next batch only when ready, so a publish spike cannot overwhelm it.

Managed Subscribe (Beta) vs manual replay

With plain Subscribe, you store the last Replay ID and pass it back on reconnect. With ManagedSubscribe, you commit the Replay ID to the server and Salesforce remembers it for you, simplifying recovery. It is in Beta.


5. Design considerations and gotchas

Consideration Why it matters What to do
Avro is mandatory Payloads are binary Avro, not JSON. Always decode with the schema from GetSchema; cache schemas by ID.
numRequested max 100 You cannot pull unlimited events in one request. Loop: request, process, request again. Tune batch size to your throughput.
Replay within 72h only A stored Replay ID outside the window is invalid. If you fall behind 72h, do a full reload, then resume with LATEST.
At-least-once delivery The same event can arrive twice. Make processing idempotent (dedupe on a key).
Schema can evolve A new field changes the schema ID. Look up schema by the ID on each event, do not hardcode.
ManagedSubscribe is Beta Beta features can change. For production replay control, manual Replay ID handling is the stable path.
HTTP/2 + long-lived streams gRPC streams are persistent connections. Handle reconnects and resume from the last Replay ID.

6. Interview Q&A

Q: What is the Pub/Sub API and why use it over the Streaming API? A: It is a single gRPC-over-HTTP/2 API to publish, subscribe, and fetch schemas across Platform Events, CDC, and event monitoring, using compact Avro binary. It is the modern replacement for the CometD Streaming API for new builds, adding flow control and efficient streaming.

Q: What does flow control mean here? A: The API is pull-based. The subscriber requests a number of events (numRequested, max 100) and only receives that many. It asks for more when ready, so a publish spike cannot overwhelm a slow consumer.

Q: How does replay work? A: Every event carries a Replay ID. On subscribe you set replayPreset to LATEST (new only), EARLIEST (earliest retained), or CUSTOM with a stored Replay ID. You can resume within the 72-hour retention window.

Q: What is ManagedSubscribe and how does it differ from Subscribe? A: With Subscribe you track and resend the Replay ID yourself. With ManagedSubscribe (Beta) you commit the Replay ID to the server and Salesforce remembers your position, so recovery is simpler. It is currently in Beta.

Q: Which event types does the Pub/Sub API support? A: Platform Events, Change Data Capture events, real-time event monitoring events, and standard platform events, all through the same interface.

Talking point to explain it to anyone: "It's a universal remote for Salesforce events. One modern API to publish, subscribe, and read schemas, and the subscriber controls the pace so it never gets flooded."


7. Key terms

Pub/Sub API, gRPC, HTTP/2, Apache Avro, Subscribe, ManagedSubscribe, GetSchema, GetTopic, numRequested, flow control, replayPreset, Replay ID - defined here and in 01-event-driven-basics.md and the README.


Sources (Verified June 2026)


Next: 05-streaming-api-and-outbound-messages.md - the legacy push mechanisms (PushTopic, generic streaming, and declarative outbound messages).

05 - Streaming API and Outbound Messages

One-liner: Two legacy ways Salesforce pushed change notifications out before the modern event bus existed. Streaming API is a long-polling pub/sub channel. Outbound Messages is a declarative SOAP push to one endpoint. Status: Both are legacy. For new builds use Platform Events, Change Data Capture, and the Pub/Sub API. You still meet these in older orgs and managed packages. Goal of this file: Recognize them, explain how they work, and know why and when to migrate.

This is Module 06. New to the event bus and replay? Start with 01-event-driven-basics.md. For the modern replacement, see 04-pub-sub-api.md.


1. The idea in plain English

Imagine an old office with two ways to tell the outside world that something changed.

Streaming API is the building intercom. A clerk announces "Order 12 shipped" over a channel. Anyone holding a handset tuned to that channel hears it live. Nobody phones in to ask; they just keep the line open and listen. That open line is long polling over CometD.

Outbound Messages is the registered letter. When a record meets a rule, the org mails a single SOAP envelope to one fixed address. The post office keeps redelivering until the recipient signs a receipt. No signature, it tries again for up to a day.

Both predate the modern event bus. They work, but they carry old plumbing (SOAP, CometD, no gRPC) and weaker tooling. Treat them as brownfield knowledge: understand them, then steer new work to Platform Events and the Pub/Sub API.


2. When to use it (and when not)

You will encounter it when Prefer the modern path when
Maintaining an older org already wired to PushTopics or generic streaming. Building anything new that pushes events → Platform Events + Pub/Sub API.
A declarative team wants a no-code push to one endpoint and SOAP is acceptable. You need high throughput, replay, or many subscribers → event bus.
Integrating a vendor or managed package that still ships Outbound Messages. You want gRPC / Avro efficiency or external-app subscribe → Pub/Sub API.
Replicating record changes to an external store on legacy tooling. You want change events with schema → Change Data Capture.

Real-world examples: a 10-year-old org notifying an on-prem ERP via Outbound Message SOAP. A legacy Visualforce dashboard listening to a PushTopic for live Case updates. A partner package that fires generic streaming messages to a companion app.

Why migrate: the Pub/Sub API gives you one efficient gRPC channel for publish and subscribe, Avro binary payloads, server-side replay tracking, and first-class external-app support. Streaming API and Outbound Messages get no new investment.


3. How it works (two legacy mechanisms)

flowchart TB subgraph SA["Streaming API (pub/sub, long polling)"] direction LR PT["PushTopic<br/>SOQL-defined<br/>record changes"] --> CH1["CometD channel<br/>/topic/Name"] GEN["Generic Streaming<br/>arbitrary messages"] --> CH2["CometD channel<br/>/u/Name"] CH1 --> SUB["Subscriber holds<br/>long-poll connection<br/>replay -1 / -2 / id"] CH2 --> SUB end subgraph OM["Outbound Messages (declarative push)"] direction LR RULE["Flow or<br/>Workflow Rule"] --> SOAP["SOAP XML message<br/>up to 100 notifications"] SOAP --> EP["External endpoint"] EP -. "must ACK" .-> RETRY["Auto-retry up to 24h<br/>same message Id"] RETRY -. resend .-> EP end style PT fill:#607D8B,color:#fff style GEN fill:#607D8B,color:#fff style RULE fill:#F44336,color:#fff style SOAP fill:#F44336,color:#fff style SUB fill:#00A1E0,color:#fff

Streaming API walkthrough

  1. You define a PushTopic (a named record holding a SOQL query) or a Generic Streaming channel (for arbitrary user-defined payloads).
  2. Salesforce delivers messages over CometD, an implementation of the Bayeux protocol, using long polling (the client holds an open HTTP connection and the server pushes when something arrives).
  3. The subscriber chooses a replay option: -1 to receive only new events, -2 to receive all retained events from the start of the window, or a specific Replay ID to resume from where it left off.
  4. PushTopic events are retained for 24 hours (note: shorter than the 72-hour window for high-volume Platform Events and CDC).

Outbound Messages walkthrough

  1. A declarative action (a Flow Action element, or a legacy Workflow Rule) triggers when a record meets criteria.
  2. Salesforce assembles a SOAP XML message containing a configured set of fields, sends it to one configured endpoint URL, and can bundle up to 100 notifications in a single message.
  3. The endpoint must return a positive acknowledgment (an Ack of true). If it does not, Salesforce retries automatically with backoff for up to 24 hours (extendable to 7 days via Support); the message Id stays the same across retries so the receiver can deduplicate.

4. The actual code and config

PushTopic (created in Apex, then subscribed over CometD):

PushTopic pt = new PushTopic();
pt.Name = 'OrderUpdates';
pt.Query = 'SELECT Id, Name, Status__c FROM Order__c WHERE Status__c = \'Shipped\'';
pt.ApiVersion = 66.0;
pt.NotifyForOperationCreate = true;
pt.NotifyForOperationUpdate = true;
pt.NotifyForFields = 'Referenced'; // only when referenced fields change
insert pt;
// Subscribers then listen on channel:  /topic/OrderUpdates

Generic Streaming uses a StreamingChannel record (channel /u/MyChannel) and a REST call to push an arbitrary message. The subscribe side is the same CometD long-poll model with the same replay options.

Subscribing with a replay option (CometD extension, conceptual):

// -1 = new events only, -2 = all retained events, or a specific Replay Id
cometd.subscribe('/topic/OrderUpdates', callback, { replay: -1 });

Outbound Message is configured, not coded:

  • Setup: define an Outbound Message action (endpoint URL, fields to send, user to run as), then attach it to a Flow (Action element) or legacy Workflow Rule.
  • Salesforce provides a WSDL for the message. You generate a listener on the external side that returns the Ack.

Migration note: Workflow Rules and Process Builder are no longer supported as of December 31, 2025. Existing rules keep running, but build new automation in Flow, which supports the Outbound Message as an Action element. Better still, publish a Platform Event and let any subscriber react.


5. Design considerations and gotchas

Consideration Why it matters What to do
PushTopic retention is 24h Shorter than the 72h window for Platform Events and CDC. Long outages lose events. Track Replay IDs. For durability prefer the event bus.
CometD is fragile Long polling drops on network blips. No gRPC efficiency. Reconnect with the last Replay ID. For new work use Pub/Sub API.
Outbound Message needs an ACK No Ack=true means endless retries for 24h. Make the listener return the ack fast, even before heavy processing.
At-least-once, duplicates happen Retries reuse the same message Id. Deduplicate on that Id. Make the handler idempotent.
100 notifications per message Bulk changes batch into one SOAP envelope. Parse the full notification list, not just the first.
One fixed endpoint Outbound Messages target a single URL. No fan-out. If you need many listeners, use the event bus.
SOAP only XML envelope, WSDL listener, heavier than JSON/Avro. Accept it for legacy, avoid for greenfield.
No new investment Both are legacy; tooling and features are frozen. Plan a migration to Platform Events / Pub/Sub API.

6. Interview Q&A

Q: What is the Streaming API and how does it deliver events? A: A legacy pub/sub channel that pushes messages over CometD, an implementation of the Bayeux protocol, using long polling. It supports PushTopic events (SOQL-based record-change notifications) and Generic Streaming (arbitrary user-defined messages). Subscribers pick a replay option: -1 new only, -2 all retained, or a specific Replay ID.

Q: What are Outbound Messages? A: A declarative action (from a Flow Action element or legacy Workflow Rule) that sends a SOAP XML message with a configured field set to one external endpoint. It batches up to 100 notifications, retries automatically for up to 24 hours, and requires the endpoint to return a positive acknowledgment.

Q: How do Outbound Messages handle reliability and duplicates? A: Delivery is at-least-once. If no Ack=true arrives, Salesforce retries with backoff for up to 24 hours. The message Id stays the same across retries, so the receiver deduplicates on it. The listener should ack quickly and process idempotently.

Q: Why are these legacy, and what replaces them? A: They predate the modern event bus and carry old plumbing (SOAP, CometD). New builds should use Platform Events and Change Data Capture published and consumed through the Pub/Sub API (gRPC, HTTP/2, Avro), which is more efficient, supports replay, and serves external apps natively.

Q: A legacy org loses PushTopic events during a long outage. Why, and what do you do? A: PushTopic retention is only 24 hours, so events older than that age out. Track and resubscribe from the last Replay ID to recover what is still in the window. For durability beyond that, migrate the use case to the event bus, which retains high-volume events for 72 hours and offers managed replay.

Talking point to explain it to anyone: "Streaming API is an office intercom you keep listening to. Outbound Messages is a registered letter that keeps getting redelivered until someone signs for it. Both are the old way; the new way is a shared digital bus."


7. Key terms

PushTopic, Generic Streaming, CometD, Bayeux, long polling, Replay ID, Outbound Message, SOAP, acknowledgment, retry, idempotency - defined here and in 01-event-driven-basics.md and the README.


Sources (Verified June 2026)


Next: 06-publishing-subscribing-and-replay.md - the how-to for publishing, subscribing, and replay across every event type.

06 - Publishing, Subscribing, and Replay

One-liner: The practical how-to that ties the module together. Who can publish, who can subscribe, and how replay keeps subscribers from missing or duplicating events. Scope: Applies across Platform Events, Change Data Capture, and the Pub/Sub API. The legacy mechanisms live in 05. Goal of this file: Be able to wire up a publisher, a subscriber, and a resilient replay strategy, and defend the design in an interview.

This is Module 06. If "event bus", "Replay ID", and "at-least-once" are not yet automatic, read 01-event-driven-basics.md first.


1. The idea in plain English

Think of the event bus as a conveyor belt running through a warehouse. Publishers drop labeled boxes onto the belt. Subscribers stand alongside and grab the boxes meant for them. Nobody hands a box directly to anyone; the belt is the only contact point. That is the whole decoupling story.

Every box gets a position number as it lands. That number is the Replay ID. If a worker steps away and comes back, they do not start over and they do not miss boxes. They say "I last handled position 4012, continue from there." As long as the box is still on the belt (within the 72-hour retention window), they catch up cleanly.

So three questions drive every design: how do I put a box on the belt (publish), how do I pick boxes off (subscribe), and how do I never lose my place (replay). The rest is detail.


2. Publishers and subscribers at a glance

Role Option Best for
Publish Apex EventBus.publish() Custom logic, bulk publishing, callbacks
Publish Flow (Create Records on the event object) Low-code, admin-built signals
Publish REST / SOAP API (insert the event sObject) External systems publishing in
Publish Pub/Sub API (gRPC Publish) Efficient external publishing, Avro payloads
Subscribe Apex trigger (after insert) Server-side reaction, DML, callouts (async)
Subscribe Flow (event-triggered) Low-code reaction inside Salesforce
Subscribe LWC via lightning/empApi Live UI updates in desktop browsers
Subscribe External app via Pub/Sub API Off-platform consumers, microservices

Rule of thumb: inside Salesforce, publish with Apex or Flow and subscribe with a trigger or Flow. For anything off-platform, reach for the Pub/Sub API on both sides.


3. How it works (publishers to bus to subscribers, with replay)

flowchart LR P1[Apex<br/>EventBus.publish] --> BUS P2[Flow] --> BUS P3[REST / SOAP API] --> BUS P4[External app<br/>Pub/Sub API publish] --> BUS BUS{{Event Bus<br/>time-ordered<br/>Replay IDs<br/>72-hour retention}} BUS --> S1[Apex trigger<br/>after insert] BUS --> S2[Flow<br/>event-triggered] BUS --> S3[LWC via empApi<br/>desktop browsers] BUS --> S4[External app<br/>Pub/Sub API subscribe] RP["Replay: -1 new / -2 all<br/>or specific Replay Id<br/>Managed Subscriptions Beta"] -. resume .-> BUS style BUS fill:#ff9900,color:#fff style P4 fill:#9C27B0,color:#fff style S4 fill:#9C27B0,color:#fff style RP fill:#4CAF50,color:#fff

Walkthrough

  1. A publisher writes an event to the bus. With "Publish Immediately" behavior the event is sent outside the database transaction, so subscribers must not assume the publishing transaction's DML is committed.
  2. The bus assigns a Replay ID and stores the event in time order for 72 hours.
  3. Each subscriber receives the event. Apex triggers fire after insert on the event object, in batches of up to 2,000 event messages.
  4. A subscriber that disconnects resumes by passing a replay option: -1 (new only), -2 (all retained), or a specific Replay ID it persisted.
  5. With Managed Event Subscriptions (Beta) the server tracks the committed Replay ID for you, so the client does not have to store it.

4. The actual code

Publish with Apex (EventBus.publish returns a Database.SaveResult per event):

List<Order_Shipped__e> evts = new List<Order_Shipped__e>{
    new Order_Shipped__e(Order_Id__c = '801xx', Carrier__c = 'DHL')
};
List<Database.SaveResult> results = EventBus.publish(evts);
for (Database.SaveResult sr : results) {
    if (!sr.isSuccess()) {
        for (Database.Error err : sr.getErrors()) {
            System.debug('Publish enqueue failed: ' + err.getMessage());
        }
    }
}
// Note: SaveResult is the ENQUEUE result, not the final publish result.
// For the final outcome, pass an EventBus.EventPublishSuccessCallback /
// EventPublishFailureCallback as the second argument.

Subscribe with an Apex trigger (idempotent, with retry):

trigger OrderShippedTrigger on Order_Shipped__e (after insert) {
    Set<String> uuids = new Set<String>();
    for (Order_Shipped__e e : Trigger.new) uuids.add(e.EventUuid);

    // Idempotency: skip events already processed (EventUuid, API 52.0+)
    Set<String> done = new Set<String>(...); // query your processed-log

    List<Task> toCreate = new List<Task>();
    for (Order_Shipped__e e : Trigger.new) {
        if (done.contains(e.EventUuid)) continue;
        try {
            toCreate.add(new Task(Subject = 'Notify ' + e.Order_Id__c));
        } catch (CalloutException ex) {
            // Transient failure: retry the whole batch later
            throw new EventBus.RetryableException('Transient error, retrying');
        }
    }
    insert toCreate; // also persist EventUuids to your processed-log
}

Subscribe in an LWC with lightning/empApi (desktop browsers, API 44.0+):

import { subscribe, onError } from 'lightning/empApi';
// channel '/event/Order_Shipped__e', replayId -1 = new events only
subscribe('/event/Order_Shipped__e', -1, (msg) => {
    // react in the UI
}).then((sub) => { this.subscription = sub; });
onError((err) => console.error('empApi error', err));

External apps subscribe through the Pub/Sub API (gRPC over HTTP/2, Avro payloads) using a ReplayPreset of LATEST, EARLIEST, or CUSTOM with a saved Replay ID. See 04-pub-sub-api.md.


5. Replay, resilience, and error handling

Topic What to know What to do
Replay options -1 new only, -2 all retained, or a specific Replay ID to resume. Persist the last processed Replay ID after each batch.
Retention window High-volume events and CDC live 72 hours. PushTopics only 24h. Recover within the window; design for catch-up after outages.
Managed Subscriptions (Beta) Server tracks the committed Replay ID via ManagedEventSubscription. Use it to drop client-side replay-store bookkeeping.
At-least-once delivery Subscribers can see an event more than once. Make handlers idempotent, keyed on EventUuid.
Apex retry Throw EventBus.RetryableException to reprocess the batch on transient errors. Keep retries to fewer than 9; beyond the limit the trigger hits an error state.
Decoupled commit "Publish Immediately" runs outside the publisher's transaction. Do not assume publisher DML is committed when the event arrives.
Publish result EventBus.publish returns an enqueue SaveResult, not the final result. Use publish callbacks for the real success/failure.
empApi limits Works in desktop browsers only, not the Salesforce mobile app. For mobile or off-platform, use the Pub/Sub API.

6. Interview Q&A

Q: What are the ways to publish an event in Salesforce? A: Apex EventBus.publish(), Flow (create a record on the event object), the REST or SOAP API (insert the event sObject), and the Pub/Sub API for efficient external publishing. Inside the platform Apex and Flow are typical; off-platform publishers use Pub/Sub API.

Q: What are the ways to subscribe? A: Apex triggers (after insert on the event or CDC object), Flow (event-triggered), LWC via lightning/empApi for live desktop UI, and external apps via the Pub/Sub API. Server-side logic uses triggers; UIs use empApi; off-platform consumers use Pub/Sub API.

Q: How does replay work and why does it matter? A: Every event gets a Replay ID marking its position. A subscriber resumes with -1 (new only), -2 (all retained), or a specific Replay ID it stored. Because retention is 72 hours, a subscriber that drops off can catch up by replaying from its last position within that window.

Q: How do you make an Apex subscriber resilient? A: Make it idempotent, keyed on the EventUuid field, so duplicates are harmless. Wrap transient failures and throw EventBus.RetryableException to reprocess the batch, keeping retries to fewer than nine so the trigger does not hit its error state. Persist the last Replay ID for recovery.

Q: What is the trap with EventBus.publish and "Publish Immediately"? A: The returned Database.SaveResult is only the enqueue result, not the final publish outcome; use publish callbacks for that. And with "Publish Immediately" the event fires outside the transaction, so a subscriber must not assume the publisher's DML committed.

Q: What are Managed Event Subscriptions? A: A Beta capability where Salesforce tracks the committed Replay ID server-side (configured via ManagedEventSubscription), so the client resumes seamlessly after a disconnect without maintaining its own replay store.

Talking point to explain it to anyone: "Publishers drop labeled boxes on a conveyor belt, subscribers pick off the ones they want, and every box has a position number so anyone who steps away can come back and continue from exactly where they left off."


7. Key terms

Publish, subscribe, EventBus.publish, publish callback, Apex trigger subscriber, lightning/empApi, Pub/Sub API, Replay ID, retention, Managed Event Subscriptions, EventBus.RetryableException, EventUuid, idempotency - defined here and in 01-event-driven-basics.md and the README.


Sources (Verified June 2026)


Next: back to the README for the module map, or revisit 04-pub-sub-api.md for the modern transport in depth.

Module 07 - Bulk & Async Integration

Goal: Move millions of records and run heavy work without hitting governor limits. API version: v66.0 (Spring '26). The principle: large or slow work goes asynchronous, in chunks, off the user's thread.

Two related ideas: Bulk (large data in/out via the Bulk API) and Async Apex (server-side work in the background: Batch, Queueable, @future, Scheduled). Start with 01-bulk-api-2.md. This module realizes the Batch Data Synchronization pattern.


How to use this module

  1. 01-bulk-api-2.md for external large-data loads/extracts.
  2. 02 for the legacy batch API and chunking huge queries.
  3. The async Apex tools: Batch, Queueable, @future, Scheduled.
  4. 07 for limits, monitoring, and error handling.

Map of this module

# File What it covers
01 bulk-api-2 REST async jobs for large data (the default)
02 bulk-api-1-and-pk-chunking Legacy batch API + PK Chunking for huge queries
03 batch-apex Process SF's own data in chunks (start/execute/finish)
04 queueable-apex Chainable async jobs, objects, callouts
05 future-methods Simple primitive-only async (@future)
06 scheduled-apex Cron-scheduled Apex
07 async-limits-monitoring-errors Limits, AsyncApexJob, retries

Which tool? (decision tree)

flowchart TD Start([Large volume or async work]) --> Q1{Who moves the data?} Q1 -->|External client loads or extracts| Q2{How much / what control?} Q2 -->|2,000+ records, simplest| B2[Bulk API 2.0] Q2 -->|Manual batch control, legacy| B1[Bulk API 1.0] Q1 -->|Apex processes the org's own data| Q3{What shape of work?} Q3 -->|Millions of records in chunks| BATCH[Batch Apex] Q3 -->|Objects, chaining, callouts| QU[Queueable Apex] Q3 -->|Simple primitive async or callout after DML| FUT[at-future method] Q3 -->|Run on a schedule| SCH[Scheduled Apex] style B2 fill:#00A1E0,color:#fff style B1 fill:#607D8B,color:#fff style BATCH fill:#9C27B0,color:#fff style QU fill:#4CAF50,color:#fff style FUT fill:#ff9900,color:#fff style SCH fill:#ff9900,color:#fff

Async Apex at a glance

Tool Initiator Inputs Callouts Chaining / monitor Best for
Bulk API 2.0 External client CSV n/a Job status API Large data in/out
Batch Apex Apex/Schedule QueryLocator/Iterable Yes (AllowsCallouts) AsyncApexJob Millions of records in chunks
Queueable Apex sObjects/objects Yes Chainable, AsyncApexJob Flexible async + chaining
@future Apex primitives only Yes (callout=true) No Simple fire-and-forget
Scheduled Schedule (cron) n/a No (delegate) CronTrigger Time-based runs

Limits cheat sheet (memorize)

Limit Value
Bulk API 2.0 data per job 150 MB
Bulk API 2.0 daily records (24h rolling) 100 million
Bulk API 1.0 records per batch 10,000
Batch Apex execute scope default 200 (max 2,000 for QueryLocator)
Concurrent batch jobs 5 running/queued (Flex Queue holds 100)
@future calls per transaction 50
Queueable jobs added per sync transaction 50
Scheduled Apex jobs 100 active
Daily async executions greater of 250,000 or 200 × user licenses
Async heap / CPU 12 MB heap, 60,000 ms CPU

Interview rapid-fire

Q: Bulk API vs Batch Apex? → Bulk API = an external client moving large data in/out. Batch Apex = Apex processing the org's own data in chunks. Different initiator.

Q: @future vs Queueable? → Queueable is preferred: it accepts sObjects/objects, is chainable, and is monitorable via AsyncApexJob. @future is primitives-only, no return, no chaining.

Q: How many @future calls per transaction? → 50. (The old "10" figure is outdated.)

Q: Run a heavy job every night at 2 AM? → Scheduled Apex (cron) that enqueues a Batch Apex job. Scheduled Apex can't call out directly.

Q: Avoid duplicates in a bulk load? → Upsert by External Id (idempotent), then re-load only the failedResults.


Sources (Verified June 2026)

Each file has its own Sources section with the specific official doc.

01 - Bulk API 2.0

One-liner: A REST-based, asynchronous API for loading or extracting large data sets (think 2,000 to millions of records) without hitting synchronous limits. Direction: usually External → Salesforce (load) or Salesforce → External (extract). Timing: asynchronous, job-based. Format: CSV. Use when: You need to insert, update, upsert, delete, or query lots of records efficiently.

This is Module 07, bulk and async. This is the Batch Data Synchronization pattern in practice. For auth, see Module 03.


1. The idea in plain English

Bulk API 2.0 is freight shipping, not mailing envelopes. If you have two letters, you use the post office (the REST API, one record per call). If you have two million, you load a shipping container, hand it to the freight company, and they process it in the background while you go do other things. You come back later and pick up two receipts: what shipped successfully and what failed.

That "hand it off and check back" rhythm is the whole API. You create a job, upload a CSV, tell Salesforce you're done uploading, and Salesforce processes the records asynchronously in optimized chunks. You then poll for completion and download success and failure results. You never hold a synchronous connection open while millions of rows process.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
2,000+ records to load or extract. A handful of records → Standard REST API.
You want Salesforce to optimize and parallelize processing. A few related records atomically → Composite API.
Nightly / scheduled large syncs (ETL). Salesforce processing its own data → Batch Apex.
Exporting a huge query result. Real-time, per-record reaction → events.

Real-world examples: an ETL tool loads 2M Accounts from SAP nightly, a migration upserts 500K Contacts by External Id, a warehouse exports all Opportunities via a bulk query.


3. How it works (the job lifecycle)

sequenceDiagram autonumber participant C as Client / ETL participant SF as Bulk API 2.0<br/>/jobs/ingest C->>SF: POST /jobs/ingest (object, operation, lineEnding) SF-->>C: jobId + contentUrl, state=Open C->>SF: PUT contentUrl (CSV data) C->>SF: PATCH /jobs/ingest/{id} state=UploadComplete Note over SF: Salesforce processes asynchronously<br/>in optimized chunks C->>SF: GET /jobs/ingest/{id} (poll) SF-->>C: state=JobComplete (or Failed) C->>SF: GET /jobs/ingest/{id}/successfulResults C->>SF: GET /jobs/ingest/{id}/failedResults

Walkthrough

1-2. Create the job (object + operation like insert/upsert/update/delete/hardDelete); Salesforce returns a jobId and a contentUrl. 3. Upload the record data as CSV to the content URL. 4. PATCH the job to UploadComplete to signal you're done. 5-6. Salesforce processes in the background; you poll the job status. 7-8. Once JobComplete, download successfulResults and failedResults (and optionally unprocessedrecords).


4. The actual requests

Base: https://MyDomainName.my.salesforce.com/services/data/v66.0/jobs/

Create an ingest job

POST /services/data/v66.0/jobs/ingest
Authorization: Bearer 00D...!AQ...
Content-Type: application/json

{ "object": "Account", "operation": "upsert", "externalIdFieldName": "External_Id__c", "lineEnding": "LF" }

Upload the CSV (to the returned contentUrl)

PUT /services/data/v66.0/jobs/ingest/{jobId}/batches
Content-Type: text/csv

Name,External_Id__c
Acme Corp,EXT-001
Globex,EXT-002

Close the job for processing

PATCH /services/data/v66.0/jobs/ingest/{jobId}
{ "state": "UploadComplete" }

Then GET /jobs/ingest/{jobId} to poll, and GET .../successfulResults and .../failedResults to retrieve outcomes. Bulk query works the same way via /jobs/query with a SOQL query, then download the result stream.

Upsert by External Id is the safe, idempotent way to load: a retried job updates rather than duplicating. This is the backbone of reliable data syncs.


5. Design considerations and limits

Consideration Detail What to do
Data per job Up to 150 MB of CSV per job. Split very large loads across jobs.
Daily allocation Up to 100 million records per rolling 24 hours (Bulk API 2.0). Plan large migrations within the window.
Jobs in any state Up to 100,000 at once. Clean up old jobs.
Query threshold Bulk query is designed for 2,000+ record results. Below that, a normal SOQL/REST query is simpler.
Idempotency Retries can duplicate inserts. Use upsert by External Id.
Record locking Parallel processing can cause lock contention (e.g. many children to one parent). Order/group data, or process serially if needed.
Errors Failures come back in a failedResults CSV. Inspect, fix, and re-load only the failures.
Huge queries Very large extracts use PK Chunking. See 02-bulk-api-1-and-pk-chunking.md.

6. Interview Q&A

Q: What is Bulk API 2.0 and when do you use it? A: A REST-based, asynchronous, CSV job API for large data operations (roughly 2,000+ records). You create a job, upload data, mark it complete, and Salesforce processes it in the background while you poll and then download success/failure results.

Q: Bulk API 2.0 vs the standard REST API? A: REST is synchronous and per-record (great for small operations). Bulk 2.0 is asynchronous and built for volume, with Salesforce optimizing and parallelizing the processing.

Q: Bulk API 2.0 vs Batch Apex? A: Bulk API is for an external client moving large data in or out. Batch Apex is Apex processing Salesforce's own data in chunks server-side. Different initiators, different tools.

Q: How do you avoid duplicates and handle failures in a bulk load? A: Upsert by External Id for idempotency, then read the failedResults CSV, fix those rows, and re-load only the failures.

Q: Key Bulk API 2.0 limits? A: About 150 MB per job, up to 100 million records per rolling 24 hours, and up to 100,000 jobs in any state. Bulk query targets result sets of 2,000+ records.

Talking point to explain it to anyone: "It's freight shipping for data. You load a container, hand it off, Salesforce processes it in the background, and you come back for the receipts."


7. Key terms

Bulk job, ingest, query job, CSV, upsert, External Id, asynchronous, PK chunking - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 02-bulk-api-1-and-pk-chunking.md - the legacy batch-based API and chunking huge queries.

02 - Bulk API 1.0 and PK Chunking

One-liner: The legacy batch-based bulk API where you create and manage the batches yourself, plus PK Chunking, the trick for extracting enormous tables in Id-range slices. Direction: External ↔ Salesforce, large volumes. Timing: asynchronous, job + batch based. Format: CSV, XML, or JSON. Use when: You need fine-grained batch control, a tool that only speaks Bulk 1.0, or you are extracting a multi-million-row object via PK Chunking.

This is Module 07, bulk and async. Bulk API 1.0 is the predecessor to Bulk API 2.0. For the Batch Data Synchronization pattern and auth, see Module 03.


1. The idea in plain English

If Bulk API 2.0 is handing a freight company one big shipping container and letting them decide how to pack the trucks, Bulk API 1.0 is renting the trucks yourself. You open a job, then you personally load each truck (a batch), tell the depot how many trucks you sent, and track each one. More work, but you control exactly how the cargo is split.

That is the core difference. In Bulk 1.0 you create batches explicitly. In Bulk 2.0 you just upload one CSV and Salesforce auto-creates the batches for you. Bulk 1.0 is older, SOAP-flavored, and more verbose, but it still wins in two cases: when you need manual batch control, and when you turn on PK Chunking to extract a giant table.

PK Chunking is a separate idea you bolt onto a bulk query. Imagine photographing every page of a 50-million-page book. You cannot do it in one shot. So you say "pages 1 to 100,000, then 100,001 to 200,000..." PK Chunking does exactly that automatically, slicing one huge query into chunks by the record's primary key (Id) ranges.


2. When to use it (and when not)

✅ Use Bulk 1.0 / PK Chunking when ❌ Use something else when
You need manual control over batch boundaries. A normal large load → Bulk API 2.0 (auto-batches, simpler).
A tool or middleware only supports Bulk 1.0. A handful of records → Standard REST API.
You want XML or JSON ingest, not just CSV. Salesforce processing its own data → Batch Apex.
Extracting millions of rows from one big object → PK Chunking. Small query results → plain SOQL / REST query.

Real-world examples: a legacy ETL job that was built on Bulk 1.0 and still runs nightly, a data-warehouse export of a 40M-row Task object using PK Chunking, a migration tool that needs to retry one specific failed batch without re-sending the rest.


3. How it works

flowchart TD A["Create Job<br/>POST /job<br/>object + operation"] --> B["Add Batch 1<br/>POST /job/jobId/batch<br/>up to 10,000 records"] A --> C["Add Batch 2<br/>up to 10,000 records"] A --> D["Add Batch N"] B --> E{Processing mode} C --> E D --> E E -->|Parallel| F["Batches run<br/>at the same time"] E -->|Serial| G["Batches run<br/>one after another"] F --> H["Close Job<br/>state Closed"] G --> H H --> I["Poll each batch<br/>+ get results CSV"] style A fill:#00A1E0,color:#fff style B fill:#ff9900,color:#fff style C fill:#ff9900,color:#fff style D fill:#ff9900,color:#fff style E fill:#9C27B0,color:#fff style F fill:#4CAF50,color:#fff style G fill:#607D8B,color:#fff style H fill:#00A1E0,color:#fff style I fill:#4CAF50,color:#fff

Walkthrough

  1. Create a job specifying the object, the operation (insert, update, upsert, delete, hardDelete, query), and the content type.
  2. Add one or more batches to that job. Each batch is a file of records you upload yourself, up to 10,000 records per batch.
  3. Salesforce processes batches in either parallel (default, fastest) or serial mode, set by concurrencyMode on the job.
  4. Close the job so no more batches can be added.
  5. Poll each batch for status and download per-batch result files (success, error).

The contrast with Bulk API 2.0: there you never touch batches. You upload one CSV, PATCH the job to UploadComplete, and Salesforce chunks and parallelizes it internally.


4. The actual requests

Base: https://MyDomainName.my.salesforce.com/services/async/66.0/

Create a job (note the older /async/ path and XML body)

POST /services/async/66.0/job
X-SFDC-Session: 00D...!AQ...
Content-Type: application/xml

<jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">
  <operation>upsert</operation>
  <object>Account</object>
  <externalIdFieldName>External_Id__c</externalIdFieldName>
  <concurrencyMode>Parallel</concurrencyMode>
  <contentType>CSV</contentType>
</jobInfo>

Add a batch (you do this per chunk of up to 10,000 records)

POST /services/async/66.0/job/{jobId}/batch
Content-Type: text/csv

Name,External_Id__c
Acme Corp,EXT-001
Globex,EXT-002

Close the job, then GET /job/{jobId}/batch/{batchId} to poll and GET /job/{jobId}/batch/{batchId}/result to retrieve outcomes.

PK Chunking for a huge extract is just a header on a query job:

POST /services/async/66.0/job
Sforce-Enable-PKChunking: chunkSize=100000
Content-Type: application/xml

<jobInfo xmlns="http://www.force.com/2009/06/asyncapi/dataload">
  <operation>query</operation>
  <object>Task</object>
  <contentType>CSV</contentType>
</jobInfo>

Salesforce then auto-creates batches, each covering one Id range, and you download each batch's result file. The default chunk size is 100,000 records and is configurable up to 250,000 via the chunkSize field.


5. Bulk 1.0 vs 2.0, and design limits

Bulk API 1.0 vs Bulk API 2.0

Aspect Bulk API 1.0 Bulk API 2.0
Protocol SOAP-style /async/ endpoints REST /jobs/ endpoints
Batch management You create batches manually Auto-created by Salesforce
Records per batch Up to 10,000 (you control) Salesforce decides internally
Formats CSV, XML, JSON CSV only
Processing mode Parallel or serial (concurrencyMode) Salesforce optimizes automatically
Complexity Higher, more moving parts Lower, simpler workflow
Recommendation Legacy / special cases Default, recommended

Key limits and considerations

Consideration Detail What to do
Records per batch Max 10,000 records per batch. Split large loads into 10K batches yourself.
Batch processing mode Parallel (default) is fastest; serial avoids lock contention. Use serial when many children point to the same parent.
PK Chunking default Chunk size defaults to 100,000, up to 250,000. Larger chunks = fewer batches but slower each.
PK Chunking target Splits by record Id ranges, ideal for big extracts. Use on multi-million-row objects only.
Record locking Parallel batches can deadlock on shared parents. Group/order data or switch to serial.
Errors Results come back per batch as CSV. Re-submit only the failed batch.

6. Interview Q&A

Q: What is the difference between Bulk API 1.0 and 2.0? A: Bulk 1.0 is the older SOAP-style API where you create and manage batches yourself (up to 10,000 records each) and choose parallel or serial processing. Bulk 2.0 is REST-based, auto-creates the batches, supports CSV only, and is simpler. 2.0 is the recommended default; 1.0 is for legacy tools or when you need manual batch control.

Q: How many records can a Bulk API 1.0 batch hold? A: Up to 10,000 records per batch, and Bulk 1.0 also accepts XML and JSON, not just CSV.

Q: What is PK Chunking and when do you use it? A: It is a request header, Sforce-Enable-PKChunking, on a bulk query that splits one enormous query into chunks by record Id range. Default chunk size is 100,000 (configurable up to 250,000). You use it to extract very large objects, think tens of millions of rows.

Q: Parallel vs serial mode in Bulk 1.0? A: Parallel runs batches simultaneously for maximum throughput. Serial runs them one at a time, which you choose when parallel processing causes record-lock contention, like many child records updating the same parent.

Q: Would you ever pick Bulk 1.0 over 2.0 today? A: Only for specific reasons: a middleware tool that only speaks Bulk 1.0, a need for XML/JSON ingest, or a requirement for fine-grained manual batch control. Otherwise Bulk 2.0 is simpler and recommended.

Talking point to explain it to anyone: "Bulk 2.0 hands the mover one big box and lets them pack the trucks. Bulk 1.0 means you pack each truck yourself. PK Chunking is how you photograph a giant book one hundred-thousand-page chunk at a time."


7. Key terms

Job, batch, parallel mode, serial mode, concurrencyMode, PK Chunking, chunkSize, primary key, Id range - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 03-batch-apex.md - processing the org's own large data sets server-side in chunks.

03 - Batch Apex

One-liner: An Apex framework for processing large data sets the org already owns, split into chunks that each run with a fresh set of governor limits. Direction: Internal to Salesforce (Apex on its own data). Timing: asynchronous, job-based. Scale: up to 50 million records. Use when: You must touch more records than a single synchronous transaction allows, like recalculating or cleaning millions of rows.

This is Module 07, bulk and async. Where Bulk API 2.0 is an external client moving data, Batch Apex is Salesforce processing its own data. Compare with Queueable Apex for chained jobs.


1. The idea in plain English

Imagine you must repaint every room in a 50-story hotel, but you can only carry enough paint for one floor at a time. So you do floor 1, refill your supplies completely, do floor 2, refill again, and so on. Each floor is a fresh start with a full bucket.

That is Batch Apex. Salesforce caps how much one transaction can do (the governor limits). If you need to update millions of records, you cannot do it in one go. Batch Apex slices the work into chunks (default 200 records each) and runs each chunk as its own transaction with its own limits reset. So a job that would blow past limits in one shot quietly grinds through millions of records, chunk by chunk.

You write a class with three methods: start decides what to process, execute does the work on one chunk, and finish wraps up. Salesforce calls them in that order and manages the chunking for you.


2. When to use it (and when not)

✅ Use Batch Apex when ❌ Use something else when
Processing the org's own large data set (thousands to millions). Loading data from outside → Bulk API 2.0.
The work exceeds a single transaction's governor limits. Small async task, maybe one callout → Queueable Apex.
Nightly cleanup, recalculation, or mass update. A quick fire-and-forget call → @future.
You need scheduled large jobs (pair with Scheduled Apex). Real-time per-record reaction → triggers / events.

Real-world examples: recalculating a rollup on 5M Opportunities nightly, anonymizing 2M old Leads for GDPR, reassigning ownership across the whole Account base, archiving stale records in chunks.


3. How it works (start, execute, finish)

flowchart TD A["Database.executeBatch<br/>new MyBatch, scopeSize"] --> B["start<br/>returns QueryLocator<br/>up to 50 million records"] B --> C["Salesforce splits into chunks<br/>default 200 records each"] C --> D["execute chunk 1<br/>fresh governor limits"] C --> E["execute chunk 2<br/>fresh governor limits"] C --> F["execute chunk N"] D --> G["finish<br/>runs once after all chunks<br/>send email, chain next job"] E --> G F --> G style A fill:#00A1E0,color:#fff style B fill:#ff9900,color:#fff style C fill:#9C27B0,color:#fff style D fill:#4CAF50,color:#fff style E fill:#4CAF50,color:#fff style F fill:#4CAF50,color:#fff style G fill:#607D8B,color:#fff

Walkthrough

  1. You kick off the job with Database.executeBatch(new MyBatch(), scopeSize).
  2. start() returns a Database.QueryLocator (a query that can stream up to 50 million records) or an Iterable.
  3. Salesforce splits those records into chunks, default 200 per chunk (configurable 1 to 2,000 for a QueryLocator).
  4. execute() runs once per chunk. Each run is a separate transaction with governor limits reset, so the job survives volume.
  5. finish() runs once, after all chunks complete. Good for a summary email or chaining the next job.

Two add-on interfaces matter: Database.Stateful preserves instance variables across chunks (otherwise state resets each execute), and Database.AllowsCallouts lets execute make HTTP callouts.


4. The actual code

public class AccountCleanupBatch
        implements Database.Batchable<sObject>, Database.Stateful, Database.AllowsCallouts {

    // Database.Stateful keeps this value alive across all chunks.
    public Integer recordsProcessed = 0;

    // 1) start: define the scope. QueryLocator handles up to 50M records.
    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(
            'SELECT Id, Name, Industry FROM Account WHERE Industry = null'
        );
    }

    // 2) execute: runs per chunk (default 200 records), fresh limits each time.
    public void execute(Database.BatchableContext bc, List<Account> scope) {
        for (Account a : scope) {
            a.Industry = 'Unspecified';
        }
        update scope;
        recordsProcessed += scope.size();
    }

    // 3) finish: runs once after all chunks finish.
    public void finish(Database.BatchableContext bc) {
        System.debug('Done. Total processed: ' + recordsProcessed);
        // Could send an email or chain another batch / Queueable here.
    }
}

Invoke it (second argument is the chunk size, here 200):

// Default scope of 200:
Database.executeBatch(new AccountCleanupBatch());

// Explicit smaller scope when execute does heavy work per record:
Database.executeBatch(new AccountCleanupBatch(), 200);

Tip: Shrink the scope (e.g. 50) when each record triggers a lot of downstream automation or a callout, so each chunk stays under limits.


5. Design considerations and limits

Consideration Detail What to do
start() capacity A QueryLocator can return up to 50 million records. Use QueryLocator for large queries; it bypasses the SOQL row limit.
Default scope execute() processes 200 records by default. Override with the scopeSize argument when needed.
Max scope (QueryLocator) Up to 2,000 records per chunk. Higher values are chunked down to 2,000.
Concurrent jobs Only 5 batch jobs can be running or queued at once. Extra jobs go to the Apex flex queue.
Flex queue Holds up to 100 queued (Holding) jobs. Beyond that, executeBatch throws a LimitException.
State across chunks Variables reset each execute unless Database.Stateful. Implement Database.Stateful to accumulate totals.
Callouts Not allowed unless Database.AllowsCallouts. Implement the interface and keep callouts within limits.
Limits reset per chunk Each execute is its own transaction. This is the whole point. Lean on it for volume.

6. Interview Q&A

Q: What is Batch Apex and why use it? A: An Apex framework for processing large data sets the org already owns. It splits the work into chunks (default 200), and each chunk runs as a separate transaction with fresh governor limits, so you can process millions of records without hitting per-transaction limits.

Q: Walk me through the three methods. A: start() returns a QueryLocator (or Iterable) defining the records, up to 50 million. execute() runs once per chunk and does the work. finish() runs once at the end for cleanup, email, or chaining.

Q: What are Database.Stateful and Database.AllowsCallouts? A: By default instance variables reset between chunks. Database.Stateful preserves them across execute calls so you can accumulate counts or results. Database.AllowsCallouts lets execute make HTTP callouts.

Q: How many batch jobs can run at once, and what is the scope limit? A: Up to 5 batch jobs running or queued concurrently; extras wait in the Apex flex queue (up to 100). Scope defaults to 200 and can be set 1 to 2,000 when start returns a QueryLocator.

Q: Batch Apex vs Bulk API 2.0? A: Bulk API is an external client loading or extracting data. Batch Apex is Apex processing the org's own data in chunks. Different initiators, different tools.

Talking point to explain it to anyone: "It is repainting a 50-story hotel one floor at a time, refilling your paint completely on every floor. Each chunk starts fresh, so you never run out mid-job."


7. Key terms

Batchable, start, execute, finish, QueryLocator, scope, Database.Stateful, Database.AllowsCallouts, flex queue, governor limits - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 04-queueable-apex.md - flexible, chainable async jobs that accept objects and allow callouts.

04 - Queueable Apex

One-liner: A flexible asynchronous Apex job that accepts real objects, can be chained, can make callouts, and hands you a job Id you can monitor. Direction: Internal to Salesforce, async work. Timing: runs in the background after enqueue. Returns: an AsyncApexJob Id. Use when: You need async work that carries complex data, chains into follow-up jobs, or makes callouts.

This is Module 07, bulk and async. Queueable is the modern replacement for @future methods, and a lighter sibling of Batch Apex for non-bulk async work.


1. The idea in plain English

Think of Queueable as dropping a task into your team's work queue with a tracking number. You hand off the job, you get a ticket number so you can check its status later, and the job runs in the background. If that job needs to kick off a follow-up task when it finishes, it can simply add the next ticket to the queue itself. That hand-off-and-chain rhythm is the whole feature.

It exists because the old way, @future methods, was too limited. @future can only take primitive arguments (no sObjects, no objects), gives you no job Id to track, and cannot chain. Queueable fixes all three: it accepts sObjects and complex objects, returns a monitorable Id, and is chainable. Salesforce now recommends Queueable over @future.

You implement one method, execute(QueueableContext), and submit the job with System.enqueueJob().


2. When to use it (and when not)

✅ Use Queueable when ❌ Use something else when
Async work that needs sObjects or complex objects. You must process millions of records → Batch Apex.
You want to chain a follow-up job. Simple async with only primitives → @future (but Queueable is still preferred).
The job makes a callout and you want to monitor it. Loading data from outside Salesforce → Bulk API 2.0.
You need the job Id to track status. Real-time synchronous response → Request and Reply.

Real-world examples: enrich an Account via a callout then chain a job to update related Contacts, process a complex in-memory object a trigger could not handle synchronously, run a moderate async update that you want to monitor in Setup → Apex Jobs.


3. How it works (enqueue, execute, chain)

flowchart TD A["Trigger / Apex<br/>System.enqueueJob<br/>new MyQueueable"] --> B["Returns AsyncApexJob Id<br/>you can monitor"] A --> C["Job waits in queue"] C --> D["execute QueueableContext<br/>runs in background<br/>accepts sObjects + objects"] D --> E{Need a follow-up?} E -->|Yes| F["System.enqueueJob<br/>next Queueable<br/>chaining"] E -->|No| G["Done"] F --> H["Next job runs<br/>fresh limits"] style A fill:#00A1E0,color:#fff style B fill:#4CAF50,color:#fff style C fill:#607D8B,color:#fff style D fill:#ff9900,color:#fff style E fill:#9C27B0,color:#fff style F fill:#00A1E0,color:#fff style G fill:#4CAF50,color:#fff style H fill:#ff9900,color:#fff

Walkthrough

  1. From a trigger or Apex, call System.enqueueJob(new MyQueueable(...)). You can pass it sObjects and other objects directly.
  2. It returns an AsyncApexJob Id, which you can query or watch in Setup to monitor status.
  3. The job sits in the queue, then execute(QueueableContext) runs in the background with a fresh set of governor limits.
  4. Inside execute, you can enqueue another Queueable to chain follow-up work. The chained job runs after this one completes, with its own fresh limits.
  5. For HTTP callouts, the class implements Database.AllowsCallouts.

4. The actual code

public class AccountEnrichJob
        implements Queueable, Database.AllowsCallouts {

    // Queueable accepts sObjects and complex objects (unlike @future).
    private List<Account> accounts;

    public AccountEnrichJob(List<Account> accounts) {
        this.accounts = accounts;
    }

    public void execute(QueueableContext ctx) {
        // Make a callout (allowed via Database.AllowsCallouts).
        for (Account a : accounts) {
            HttpRequest req = new HttpRequest();
            req.setEndpoint('callout:Enrichment_Service/lookup?name=' + a.Name);
            req.setMethod('GET');
            HttpResponse res = new Http().send(req);
            if (res.getStatusCode() == 200) {
                a.Description = res.getBody();
            }
        }
        update accounts;

        // Chaining: enqueue a follow-up job from inside execute.
        if (!Test.isRunningTest()) {
            System.enqueueJob(new ContactSyncJob(accounts));
        }
    }
}

Enqueue it and capture the monitorable Id:

List<Account> toEnrich = [SELECT Id, Name FROM Account WHERE Description = null LIMIT 100];
Id jobId = System.enqueueJob(new AccountEnrichJob(toEnrich));

// Monitor it:
AsyncApexJob job = [
    SELECT Id, Status, NumberOfErrors
    FROM AsyncApexJob
    WHERE Id = :jobId
];

Chaining caveat: you can chain Queueables, but a job that makes callouts can only chain into a limited callout depth. Keep callout chains short.


5. Design considerations and limits

Consideration Detail What to do
Jobs per transaction Up to 50 jobs added via enqueueJob per synchronous transaction. Batch your enqueues; do not loop one per record blindly.
Enqueue from async From within an async job you can enqueue only 1 child (to chain). Use chaining, one job hands off to the next.
Chain depth (Dev/Trial) Max stack depth of 5 in Developer Edition and Trial orgs. Production editions allow deeper chaining.
Accepts sObjects and complex objects (member variables). This is the big win over @future.
Monitorable Returns an AsyncApexJob Id. Query AsyncApexJob or watch Setup → Apex Jobs.
Callouts Need Database.AllowsCallouts. Implement it; keep callout chains short.
Callout chain depth Only a limited number of chained jobs may make callouts. Avoid long chains of callout jobs.

6. Interview Q&A

Q: What is Queueable Apex and how do you start one? A: A flexible async Apex job. You implement execute(QueueableContext) and submit it with System.enqueueJob(), which returns an AsyncApexJob Id you can monitor. The job runs in the background with fresh governor limits.

Q: Why is Queueable preferred over @future? A: Three reasons. It accepts sObjects and complex objects (@future only takes primitives), it is chainable (you can enqueue a follow-up from inside execute), and it is monitorable via the returned job Id. Salesforce recommends Queueable over @future.

Q: What is chaining and how deep can it go? A: From inside a running Queueable you can enqueue one more Queueable, which runs after the current one finishes with fresh limits. Production orgs allow deep chaining; Developer Edition and Trial orgs cap the stack depth at 5.

Q: How do you make a callout from a Queueable? A: Implement Database.AllowsCallouts on the class. Note that the number of chained jobs that make callouts is limited, so keep callout chains short.

Q: What is the enqueue limit? A: Up to 50 jobs can be added with System.enqueueJob in a single synchronous transaction. From within an async context you can only enqueue one child job, which is what enables chaining.

Talking point to explain it to anyone: "It is dropping a task in the work queue with a tracking number. You hand it off, you can check its status, and when it finishes it can drop the next task in the queue itself."


7. Key terms

Queueable, QueueableContext, System.enqueueJob, AsyncApexJob, chaining, stack depth, Database.AllowsCallouts, @future - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 05-future-methods.md - the original lightweight async annotation, and why Queueable usually wins.

05 - Future Methods (@future)

One-liner: The simplest form of asynchronous Apex. Annotate a static void method with @future and Salesforce runs it later, on its own, in a separate transaction with its own governor limits. Direction: internal Apex (Salesforce processing its own work off the main thread). Timing: asynchronous, fire-and-forget. Trigger: just call the method. Use when: You need a quick, primitive-only async job, or you must make a callout after DML from a trigger.

This is Module 07, bulk and async. Future methods are the entry-level async tool. For the richer, generally-preferred alternative, see Queueable Apex. For the full async reference, see 07-async-limits-monitoring-errors.md.


1. The idea in plain English

A future method is dropping a note in the office mailroom instead of walking the message over yourself. You write down what you need done, hand it off, and immediately get back to your own work. Sometime soon, someone in the mailroom picks up the note and does the task. You do not wait. You do not get a reply. You just trust it gets done.

That "hand it off and walk away" behaviour is called fire-and-forget. When your Apex calls a @future method, Salesforce does not run it right then. It queues the work and finishes your current transaction first. The future method runs later, in its own transaction, with a fresh set of governor limits (and the higher async limits at that). The catch: because you walked away, you get no return value, you cannot easily chain another job after it, and it is hard to monitor.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You need a callout after DML from a trigger. You need to pass sObjects or complex types → Queueable.
The work is quick and primitive-only (Ids, Strings, numbers). You need to chain jobs or monitor them → Queueable.
You want to move slow logic off the main transaction. You need to process large data volumes in chunks → Batch Apex.
Fire-and-forget is genuinely fine (no result needed). You need it to run on a schedule → Scheduled Apex.

The classic fix: a trigger does DML, then needs to call an external API. A synchronous callout after DML throws "You have uncommitted work pending." Move the callout into a @future(callout=true) method and the problem disappears. The future method runs in a new transaction after the DML has committed.

Rule of thumb: Queueable is generally preferred. It does everything @future does, plus accepts objects, supports chaining, and returns a job Id you can monitor. Reach for @future only when the job is simple, quick, primitive-only, or when you are patching the trigger-callout case quickly.


3. How it works (the hand-off)

flowchart TD A["Trigger or Apex<br/>main transaction"] --> B["DML commits records"] B --> C["Call @future method<br/>just a method call"] C --> D["Salesforce queues the job<br/>main transaction ends"] D --> E["Later: future runs in<br/>NEW transaction"] E --> F["Fresh + higher async limits<br/>callout allowed if callout=true"] F --> G["Work done. No return value.<br/>Fire-and-forget."] style A fill:#00A1E0,color:#fff style B fill:#607D8B,color:#fff style C fill:#ff9900,color:#fff style D fill:#9C27B0,color:#fff style E fill:#9C27B0,color:#fff style F fill:#4CAF50,color:#fff style G fill:#4CAF50,color:#fff

Walkthrough

  1. Your main transaction (often a trigger) runs and commits its DML.
  2. You call the @future method. To Apex this looks like an ordinary method call, but nothing executes yet.
  3. Salesforce queues the request and lets your original transaction finish.
  4. Sometime soon, the platform runs the future method in a separate transaction with a fresh, higher (async) set of limits.
  5. If declared @future(callout=true), it may make HTTP callouts. When it finishes, there is no callback and no return value.

4. The actual code

A future method must be static, return void, and accept only primitives (and arrays/collections of primitives). You cannot pass sObjects — pass record Ids instead and re-query inside.

public class AccountSyncService {

    @future(callout=true)               // callout=true allows HTTP callouts
    public static void syncAccounts(Set<Id> accountIds) {
        // Re-query: we received Ids, not sObjects (primitives only)
        List<Account> accounts = [
            SELECT Id, Name, Industry FROM Account WHERE Id IN :accountIds
        ];

        for (Account a : accounts) {
            HttpRequest req = new HttpRequest();
            req.setEndpoint('callout:ERP_System/accounts'); // Named Credential
            req.setMethod('POST');
            req.setHeader('Content-Type', 'application/json');
            req.setBody(JSON.serialize(a));
            try {
                HttpResponse res = new Http().send(req);
                // log/handle res.getStatusCode()
            } catch (Exception e) {
                // no caller to bubble up to: log it (see Module 07 error handling)
                System.debug(LoggingLevel.ERROR, e.getMessage());
            }
        }
    }
}

Calling it from a trigger (the after-DML callout fix):

trigger AccountTrigger on Account (after insert, after update) {
    // DML has happened; a synchronous callout here would throw
    // "You have uncommitted work pending." So hand off to @future:
    AccountSyncService.syncAccounts(Trigger.newMap.keySet());
}

Why a Set<Id>, not one Id? Triggers fire in bulk. Pass the whole set so the future method runs once for all records, not once per record. Calling @future inside a for loop over records will blow the per-transaction cap fast.


5. Design considerations and limits

Consideration Detail What to do
Invocations per transaction Up to 50 @future calls per Apex transaction. Pass a collection and call once, never in a loop.
No sObjects Parameters must be primitives, arrays of primitives, or collections of primitives. Pass Ids and re-query inside the method.
No return value Methods are void. You cannot read a result. If you need a result or callback, use Queueable.
Hard to chain A future method cannot reliably call another future method. Use Queueable chaining for sequences.
Hard to monitor You get no job Id back from the call itself. Query AsyncApexJob by method name, or use Queueable.
Callouts Only allowed with @future(callout=true). Annotate explicitly when calling external services.
Counts toward daily async Shares the org's daily async execution limit. See 07-async-limits-monitoring-errors.md.
No guaranteed order/timing Runs "soon," not instantly, and not in any set order. Never depend on timing or ordering between future calls.

6. Interview Q&A

Q: What is a future method and how do you declare one? A: It is the simplest async Apex. Annotate a method with @future; it must be static, return void, and take only primitives (or collections of primitives). Calling it queues the work to run later in its own transaction with fresh, higher async limits. Add @future(callout=true) to allow HTTP callouts.

Q: Why can't a future method take sObjects? A: The record could change between when you queue the job and when it runs, so Salesforce only allows primitives. The pattern is to pass record Ids and re-query the latest data inside the method.

Q: How do future methods fix the "uncommitted work pending" error? A: A synchronous callout after DML in the same transaction throws that error. Moving the callout into @future(callout=true) runs it in a new transaction after the DML commits, so there is no pending work.

Q: When would you choose @future over Queueable? A: Rarely. Queueable is generally preferred because it accepts objects, supports chaining, and returns a monitorable job Id. Use @future only for simple, quick, primitive-only work, or as a fast patch for the trigger-callout case.

Q: What is the limit on future invocations? A: Up to 50 @future calls per Apex transaction. Because of that, you bulkify: pass a collection of Ids and invoke the method once rather than once per record.

Talking point to explain it to anyone: "It is dropping a note in the mailroom. You hand off the task and walk away — no waiting, no reply. The platform does it later on its own."


7. Key terms

Future method, asynchronous, fire-and-forget, callout=true, primitive, governor limits, AsyncApexJob - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 06-scheduled-apex.md - running Apex automatically on a schedule with the Schedulable interface and CRON.

06 - Scheduled Apex

One-liner: Apex that runs automatically on a schedule (e.g. nightly at 2 AM). You implement the Schedulable interface and register the class with a CRON expression. Direction: internal Apex, time-triggered. Timing: asynchronous, on a recurring clock. Trigger: the platform scheduler, not user code. Use when: A job must run at set times — nightly syncs, weekly cleanups, periodic kick-offs of Batch Apex.

This is Module 07, bulk and async. Scheduled Apex is the "alarm clock" of the async family. It often kicks off a Batch Apex job. For the full async reference and monitoring, see 07-async-limits-monitoring-errors.md.


1. The idea in plain English

Scheduled Apex is an alarm clock for your code. You set the time once, and from then on Salesforce wakes your class up and runs it — at 2 AM every day, every Monday morning, the first of every month, whatever you specify. You are not standing there pressing a button; the platform's scheduler does it for you, forever, until you cancel it.

The clock itself is a CRON expression: a compact string that says "at these seconds, minutes, hours, on these days." You write a class that implements the Schedulable interface — which just means it has one method, execute — and then you register it with a name and a CRON string. After that it is hands-off.

One important boundary: a scheduled job is a quiet alarm, not a phone. It cannot make callouts directly. If the scheduled work needs to call an external system, it hands that part off to a Queueable or @future method.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
Work must run at specific times (nightly, weekly). Work should run immediately after an event → Queueable / @future.
You want to kick off a Batch Apex job on a clock. You just need a one-off async task now → Queueable.
Recurring maintenance: cleanup, recalculation, reminders. The scheduled code itself must call out → delegate to async.
Periodic data sync windows (ETL-style). High-frequency, sub-minute triggers → not what the scheduler is for.

Real-world examples: every night at 2 AM, start a batch that recalculates account health scores; every Monday at 6 AM, expire stale leads; on the 1st of each month, roll up invoices and enqueue a Queueable to push totals to an ERP.


3. How it works (the scheduler clock)

flowchart TD A["You register the job once<br/>System.schedule or Setup UI"] --> B["CRON expression<br/>defines the times"] B --> C{"Is it the<br/>scheduled time?"} C -- "no" --> C C -- "yes" --> D["Platform calls<br/>execute(SchedulableContext)"] D --> E["Your logic runs<br/>NO direct callouts"] E --> F["Commonly:<br/>Database.executeBatch(...)"] F --> G["Batch job processes<br/>records in chunks"] E --> H["Or enqueue Queueable<br/>for any callouts"] style A fill:#00A1E0,color:#fff style B fill:#607D8B,color:#fff style C fill:#ff9900,color:#fff style D fill:#9C27B0,color:#fff style E fill:#9C27B0,color:#fff style F fill:#4CAF50,color:#fff style G fill:#4CAF50,color:#fff style H fill:#4CAF50,color:#fff

Walkthrough

  1. You register the class once — via System.schedule() in Apex or through the Setup UI.
  2. The CRON expression tells the scheduler exactly when to fire.
  3. The platform watches the clock and does nothing until the scheduled moment arrives.
  4. At that moment it calls your execute(SchedulableContext sc) method.
  5. Your logic runs. It cannot make a callout directly. Most often it calls Database.executeBatch() to start a Batch Apex job, or enqueues a Queueable for any external calls.

4. The actual code

Implement Schedulable and its single execute method. The usual job is to start a batch.

public class NightlyAccountScoreScheduler implements Schedulable {

    public void execute(SchedulableContext sc) {
        // Scheduled Apex cannot call out directly.
        // Typical pattern: kick off a Batch Apex job.
        AccountScoreBatch batch = new AccountScoreBatch();
        Database.executeBatch(batch, 200);   // 200 = scope (records per chunk)

        // If you needed a callout instead, you would enqueue a Queueable:
        // System.enqueueJob(new SyncQueueable());
    }
}

Schedule it from Anonymous Apex with a CRON string:

// CRON: Seconds Minutes Hours Day-of-month Month Day-of-week [Year]
// Every day at 2:00:00 AM:
String cron = '0 0 2 * * ?';
String jobId = System.schedule(
    'Nightly Account Score',   // unique job name
    cron,                      // CRON expression
    new NightlyAccountScoreScheduler()  // an instance of the class
);

Reading the CRON expression 0 0 2 * * ?:

Field Value Meaning
Seconds 0 at second 0
Minutes 0 at minute 0
Hours 2 at 2 AM (24-hour)
Day-of-month * every day of the month
Month * every month
Day-of-week ? no specific day (use ? when day-of-month is set)

More examples: 0 0 6 ? * MON = every Monday at 6 AM. 0 0 0 1 * ? = midnight on the 1st of each month. 0 30 13 * * ? = every day at 1:30 PM.

? vs * gotcha: day-of-month and day-of-week conflict, so you specify one and put ? ("no value") in the other. Salesforce requires this; using * in both is invalid.

You can also schedule without code: Setup → Apex Classes → Schedule Apex, pick the class, set frequency and time. That UI is convenient but offers only preset frequencies; System.schedule() with CRON gives full control.


5. Design considerations and limits

Consideration Detail What to do
Scheduled jobs at once Up to 100 scheduled Apex jobs can be active at one time. Consolidate jobs; cancel ones you no longer need.
No direct callouts Scheduled Apex cannot make HTTP callouts directly. Delegate to a Queueable or @future(callout=true).
Unique job name System.schedule name must be unique among active jobs. Use clear, descriptive names; you cannot reschedule a duplicate name.
Kicks off batch A common, recommended pattern is scheduler → Database.executeBatch. Keep execute() thin; do the heavy lifting in the batch.
Editing a scheduled class You cannot save changes to a class that is currently scheduled. Cancel the job in Scheduled Jobs, deploy, then reschedule.
CRON format Seconds Minutes Hours Day-of-month Month Day-of-week [Year]. Put ? in whichever of day-of-month / day-of-week you are not using.
Counts toward daily async Each run consumes the org's daily async execution allowance. See 07-async-limits-monitoring-errors.md.
Timing is approximate Jobs start at or shortly after the scheduled time, resources permitting. Do not assume to-the-second precision.

6. Interview Q&A

Q: How do you make Apex run on a schedule? A: Implement the Schedulable interface (one method, execute(SchedulableContext sc)), then register the class with System.schedule(name, cronExpression, instance) or through Setup → Schedule Apex. The platform's scheduler calls execute at the times the CRON expression defines.

Q: What does a CRON expression look like in Salesforce? A: Seven fields: Seconds Minutes Hours Day-of-month Month Day-of-week and an optional Year. For example 0 0 2 * * ? runs every day at 2 AM. You put ? in either day-of-month or day-of-week (you cannot constrain both).

Q: Can scheduled Apex make a callout? A: Not directly. If the scheduled work needs an external call, it delegates to a Queueable or an @future(callout=true) method, which run in a context where callouts are allowed.

Q: What is the most common pattern with scheduled Apex? A: Scheduler kicks off Batch Apex. The execute method stays thin and just calls Database.executeBatch(...), letting the batch do the heavy, chunked processing.

Q: How many scheduled jobs can you have, and what trips people up when editing? A: Up to 100 active scheduled Apex jobs. A frequent gotcha: you cannot save changes to a class while it is scheduled — you must cancel the scheduled job, deploy the change, then reschedule.

Talking point to explain it to anyone: "It is an alarm clock for code. You set the time once, and Salesforce wakes the job up and runs it automatically, again and again."


7. Key terms

Schedulable, execute, SchedulableContext, System.schedule, CRON expression, Scheduled Jobs, Batch Apex - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 07-async-limits-monitoring-errors.md - the async reference table, limits, monitoring, and error handling.

07 - Async Limits, Monitoring & Error Handling

One-liner: The reference page for the whole async family. Which tool to pick, the limits they share, how to monitor running jobs, and how to make them reliable with retries and idempotency. Direction: internal Apex (off-thread processing). Timing: asynchronous. Scope: @future, Queueable, Batch, Scheduled, plus Bulk API job monitoring. Use when: You are choosing an async tool, watching a job in production, or designing for failure.

This is Module 07, bulk and async, and the final file in the module. It ties together @future, Queueable, Batch Apex, Scheduled Apex, and Bulk API 2.0.


1. The idea in plain English

Async Apex is a back-office team working behind the storefront. The customer-facing transaction (the user clicking Save) stays fast because the slow work is handed to the back office to finish later. But a back office needs three things to be trustworthy: the right worker for the job, rules so no one team hogs the building, and a way to check whether the work actually got done.

This page covers all three. First a comparison table so you pick the right async worker. Then the limits that keep one tenant from starving the others (you are on shared infrastructure, so async gets higher governor limits but a daily cap on how much you can run). Then monitoring — the AsyncApexJob object and the Setup pages that show you what is queued, running, or failed. Finally error handling, because a fire-and-forget job that silently fails is worse than no job at all.


2. Choosing the tool: the async comparison

@future Queueable Batch Apex Scheduled Apex
Interface / syntax @future annotation Queueable interface Database.Batchable Schedulable interface
Input types Primitives only Any (objects, sObjects) Query/iterable scope None (clock-driven)
Chaining No (cannot chain) Yes (enqueue from execute) Yes (chain in finish) Indirect (starts batch)
Callouts Yes, with callout=true Yes (implement Database.AllowsCallouts) Yes (Database.AllowsCallouts) No (delegate to async)
Returns a job Id No Yes (System.enqueueJob) Yes (Database.executeBatch) Yes (System.schedule)
Monitoring Hard (no Id from call) Easy (job Id) Easy (job Id + Apex Jobs) Easy (Scheduled Jobs)
Processes in chunks No No Yes (200 default) No (but starts a batch)
Typical use Quick primitive task; callout after DML Most async work; chained callouts Large data volumes Run on a clock; kick off batch

The short version: default to Queueable for general async work. Use Batch when the data set is too big for one transaction. Use Scheduled to run on a clock (usually to start a batch). Use @future only for simple primitive-only cases or the trigger-callout patch.


3. The shared limits

All four tools draw from the same async governor pool and a shared daily allowance. The good news: async transactions get higher limits than synchronous ones.

Limit Synchronous Asynchronous Note
Heap size 6 MB 12 MB Async doubles your in-memory budget.
CPU time 10,000 ms 60,000 ms Async gets 6× the CPU per transaction.
SOQL queries 100 100 Same per-transaction query limit.
Daily async executions — greater of 250,000 or 200 × user licenses Shared across @future, Queueable, Batch, Scheduled per rolling 24h.
Concurrent batch jobs — 5 Active/processing batch jobs at once.
Batch holding queue — 100 Flex Queue holds up to 100 jobs waiting to run.
Scheduled jobs — 100 Active scheduled Apex jobs at once.
@future per transaction — 50 Invocations allowed in one Apex transaction.
Queueable depth — chainable One job enqueued per execute (chaining); platform guards runaway loops.

The daily async formula in plain terms: take 200 × (number of user licenses in the org), compare it to 250,000, and your daily ceiling is whichever is larger. A small org gets the 250,000 floor; a large org gets more. This pool is consumed by all async Apex combined.

Why async gets more room: it runs off the user's thread, so a longer or heavier job does not freeze anyone's screen. That is exactly why you move slow callouts and big data jobs to async in the first place.


4. Monitoring async jobs

flowchart TD A["Async job enqueued"] --> B["Row created in<br/>AsyncApexJob"] B --> C{"Where to watch?"} C --> D["SOQL on AsyncApexJob<br/>Status, NumberOfErrors"] C --> E["Setup: Apex Jobs<br/>batch + queueable history"] C --> F["Setup: Apex Flex Queue<br/>holding batch jobs, reorder"] C --> G["Setup: Scheduled Jobs<br/>cancel / inspect schedules"] C --> H["Setup: Bulk Data Load Jobs<br/>Bulk API ingest + query"] style A fill:#00A1E0,color:#fff style B fill:#9C27B0,color:#fff style C fill:#ff9900,color:#fff style D fill:#4CAF50,color:#fff style E fill:#607D8B,color:#fff style F fill:#607D8B,color:#fff style G fill:#607D8B,color:#fff style H fill:#607D8B,color:#fff

AsyncApexJob is the object that backs nearly every async job — a @future call, a Queueable, a Batch, or a Scheduled job. Query it to check status programmatically:

List<AsyncApexJob> jobs = [
    SELECT Id, JobType, Status, NumberOfErrors,
           JobItemsProcessed, TotalJobItems, MethodName, CreatedDate
    FROM AsyncApexJob
    WHERE Status IN ('Queued', 'Processing', 'Failed')
    ORDER BY CreatedDate DESC
];
Where What it shows Covers
AsyncApexJob (SOQL) Status, error count, items processed vs total. All async Apex.
Setup → Apex Jobs History and status of batch and queueable jobs. Batch, Queueable, @future.
Setup → Apex Flex Queue Batch jobs holding (beyond the 5 running); reorder them. Batch Apex.
Setup → Scheduled Jobs Active schedules; cancel or inspect. Scheduled Apex.
Setup → Bulk Data Load Jobs Bulk API ingest and query job progress. Bulk API 2.0.

Key distinction: Apex async jobs show under Apex Jobs / Flex Queue / Scheduled Jobs. The external Bulk API has its own page, Bulk Data Load Jobs. Different engines, different monitors.


5. Error handling and reliability

Async work has no user watching the screen, so failures are silent unless you design for them. Four patterns:

Pattern What it means How to apply
Idempotency Running the job twice produces the same result, not duplicates. Upsert by External Id; check state before acting; design for safe re-runs.
Retry Transient failures (a 503, a lock) get tried again. Re-enqueue with a bounded attempt counter; stop after N so you do not loop forever.
Catch and log Errors are recorded, not swallowed. try/catch, write a custom error log object or Platform Event with the record Id and message.
Dead-letter Records that keep failing are parked, not lost. After max retries, write the failed payload to a dead-letter record/object for manual review.

A Queueable with bounded retry and logging:

public class SyncQueueable implements Queueable, Database.AllowsCallouts {
    private Set<Id> recordIds;
    private Integer attempt;

    public SyncQueueable(Set<Id> recordIds, Integer attempt) {
        this.recordIds = recordIds;
        this.attempt = attempt;
    }

    public void execute(QueueableContext qc) {
        try {
            // ... do the callout / DML work (idempotent: upsert by External Id) ...
        } catch (Exception e) {
            if (attempt < 3) {
                // Retry: re-enqueue with a higher attempt count
                System.enqueueJob(new SyncQueueable(recordIds, attempt + 1));
            } else {
                // Dead-letter: park the failure for review (don't lose it)
                insert new Integration_Error__c(
                    Payload__c = String.join(new List<Id>(recordIds), ','),
                    Message__c = e.getMessage(),
                    Source__c  = 'SyncQueueable'
                );
            }
        }
    }
}

For Batch Apex, errors are per-chunk: a failing scope rolls back only that chunk, and you can implement Database.RaisesPlatformEvents (or capture errors in execute) and summarise in finish. For Bulk API, failures come back in a failedResults CSV you re-load after fixing — see Bulk API 2.0.

Golden rule: assume any async job can run twice (retries, replays). If a double-run would corrupt data, you have an idempotency bug, not a retry strategy.


6. Interview Q&A

Q: How do you choose between @future, Queueable, Batch, and Scheduled Apex? A: Default to Queueable for general async (objects, chaining, callouts, a job Id). Use Batch for large data volumes that need chunking. Use Scheduled to run on a clock, usually to kick off a batch. Use @future only for simple primitive-only work or the callout-after-DML trigger fix.

Q: What is the daily limit on asynchronous Apex executions? A: The greater of 250,000 or 200 × the number of user licenses in the org, per rolling 24 hours. It is shared across all async Apex — @future, Queueable, Batch, and Scheduled combined.

Q: How do async governor limits differ from synchronous? A: Async transactions get more room: 12 MB heap (vs 6 MB sync) and 60,000 ms CPU (vs 10,000 ms sync). That is the whole point of going async — heavier work without freezing the user.

Q: How do you monitor async jobs? A: Query the AsyncApexJob object for status and error counts, and use Setup pages: Apex Jobs (batch/queueable history), Apex Flex Queue (holding batch jobs), Scheduled Jobs (schedules), and Bulk Data Load Jobs for the Bulk API.

Q: How do you make async jobs reliable? A: Design for idempotency (upsert by External Id, safe re-runs), add bounded retries with an attempt counter, catch and log errors to a custom object or Platform Event, and dead-letter records that exhaust retries so nothing is silently lost. Assume every job can run twice.

Talking point to explain it to anyone: "Async is the back office behind the counter. It gets more room to work, but there is a daily limit on how much it can do, dashboards to watch it, and a rule that any task might get done twice — so it must be safe to repeat."


7. Key terms

AsyncApexJob, async governor limits, daily async limit, Apex Flex Queue, Scheduled Jobs, Bulk Data Load Jobs, idempotency, retry, dead-letter - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: back to the Module 07 README for the full bulk-and-async map. Then continue to Module 08 - Modern APIs (GraphQL, Connect, UI APIs) and Module 09 - Security & Limits for governance across every integration.

Module 08 - Modern APIs

Goal: Use the newer APIs that solve old pain points, and know how the whole API surface fits together. API version: v66.0 (Spring '26). Theme: stop making 5 calls when 1 will do.

Several "modern API" topics already have homes elsewhere in the vault: Composite and UI API in Module 04, Pub/Sub API in Module 06, Salesforce Connect and External Services in Module 02/05. This module covers what's left and ties it all together: GraphQL, sObject Collections, Data Cloud APIs, and the definitive API landscape.


Map of this module

# File What it covers
01 graphql-api Field-selective, nested reads in one round-trip
02 sobject-collections CRUD up to 200 records in one call
03 data-cloud-apis Ingestion, Query, Profile APIs for Data Cloud
04 modern-api-landscape The capstone: which API for which job, across the whole platform

Which modern API? (decision tree)

flowchart TD Start([Reading or writing data efficiently?]) --> Q1{What do you need?} Q1 -->|Specific fields across related objects| GQL[GraphQL API] Q1 -->|CRUD up to 200 records in one call| COLL[sObject Collections] Q1 -->|Different operations chained in one call| COMP[Composite API - Module 04] Q1 -->|Ingest or query unified big data| DC[Data Cloud APIs] Q1 -->|Real-time events| PUB[Pub/Sub API - Module 06] Q1 -->|Comparing every option| LAND[See the API landscape - file 04] style GQL fill:#00A1E0,color:#fff style COLL fill:#4CAF50,color:#fff style COMP fill:#9C27B0,color:#fff style DC fill:#ff9900,color:#fff style PUB fill:#607D8B,color:#fff

The pain points these solve

Old pain Modern fix
Multiple REST calls to get related data GraphQL (one query, nested)
Looping single-record REST calls for 100 records sObject Collections (200 in one call)
Chatty multi-step writes Composite API (Module 04)
Polling for changes Pub/Sub API (Module 06)
Copying huge external datasets in Data Cloud / Salesforce Connect
Writing Apex just to call a REST API from Flow External Services (Module 05)

Interview rapid-fire

Q: GraphQL vs REST? → GraphQL returns exactly the fields you ask for across related objects in one round-trip (no over/under-fetching). REST is simpler for single-object CRUD and caches better. See 01.

Q: sObject Collections vs Composite vs Bulk? → Collections = same CRUD on up to 200 records, one sync call. Composite = different operations chained in one call. Bulk = async, thousands+.

Q: What's the modern way to subscribe to events? → The Pub/Sub API (gRPC + Avro), not the legacy Streaming API. See Module 06.

Q: How do you decide which API to use? → Match sync/async, volume, and shape to the job. The full matrix is in 04-modern-api-landscape.md.


Sources (Verified June 2026)

Each file has its own Sources section with the specific official doc.

01 - GraphQL API

One-liner: Ask for exactly the fields you want, across related objects, in one round-trip, and get back exactly that shape, nothing more. Direction: External → Salesforce (inbound). Format: JSON over HTTP POST. Auth: OAuth 2.0 Bearer. Use when: A client needs specific fields from nested objects and you want to avoid over-fetching and multiple REST calls.

This is Module 08, the modern APIs. New to REST? See Module 04. For the full "which API" picture, see 04-modern-api-landscape.md.


1. The idea in plain English

REST is a fixed-menu restaurant: each endpoint serves a set plate. Want the Account, its Contacts, and its Opportunities? That's three trips to three counters, and each plate comes with side dishes (fields) you didn't ask for. GraphQL is à la carte: you hand over one order that says exactly which dishes and which related items you want, and the kitchen returns one tray with precisely that.

Two problems disappear. Over-fetching (getting fields you don't need) is gone because you list the fields. Under-fetching (having to make follow-up calls for related data) is gone because you traverse relationships in the same query. One request, one response, shaped to your screen.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You need specific fields across nested/related objects. Simple single-object CRUD → REST API.
Bandwidth matters (mobile, low-power clients). Bulk loads of 2,000+ records → Bulk API 2.0.
Dashboards that aggregate and combine data. Multiple unrelated writes in one call → Composite.
You want field-level versioning and a typed schema. Heavy HTTP caching needs (REST caches better).

Real-world examples: a mobile app rendering an Account page with its Contacts and recent Opportunities in one call; a dashboard pulling counts and sums by Stage; a custom UI that wants only Name and Phone.


3. How it works (sequence diagram)

sequenceDiagram autonumber participant C as Client participant SF as GraphQL API<br/>/services/data/v66.0/graphql C->>SF: POST one query (fields + relationships) + Bearer token SF->>SF: Validate against schema, enforce FLS and sharing SF-->>C: One JSON response shaped exactly like the query

The client sends a single POST containing a query document. Salesforce validates it against the typed schema, runs it (respecting the user's field-level security and sharing), and returns one JSON response whose shape mirrors the query.


4. The actual request

Salesforce's GraphQL schema is rooted at uiapi (it builds on the UI API), uses a connection model (edges then node), and each field is an object exposing .value (and often .displayValue). This shape surprises people who expect "vanilla" GraphQL.

Endpoint: POST https://MyDomainName.my.salesforce.com/services/data/v66.0/graphql

Query (Accounts in Technology, with their Contacts):

query AccountsWithContacts {
  uiapi {
    query {
      Account(first: 10, where: { Industry: { eq: "Technology" } }) {
        edges {
          node {
            Id
            Name { value }
            Contacts {
              edges { node { LastName { value } Email { value } } }
            }
          }
        }
      }
    }
  }
}

Response (same shape as the query):

{
  "data": {
    "uiapi": {
      "query": {
        "Account": {
          "edges": [
            { "node": { "Id": "001...", "Name": { "value": "Acme Corp" },
              "Contacts": { "edges": [ { "node": { "LastName": { "value": "Khan" }, "Email": { "value": "k@acme.com" } } } ] } } }
          ]
        }
      }
    }
  }
}

Aggregates: GraphQL also supports aggregate queries (group by, count, sum, avg on numeric/currency fields) via an aggregate root, returning summarized results in one call, which is great for dashboards.


5. Design considerations and gotchas

Consideration Why it matters What to do
Salesforce shape It's uiapi + edges/node + field.value, not plain GraphQL. Read fields as node.Field.value.
Security still applies GraphQL respects FLS and sharing. A field the user can't see won't return.
Query complexity Deep nesting and large first: are costly. Page with first/after; keep depth sane.
Not for bulk It's for shaping reads, not mass loads. Use Bulk API 2.0 for volume.
Caching GraphQL POSTs cache worse than REST GETs. Use REST where HTTP caching matters.
Versioning Field-level versioning is a strength. Pin the API version in the URL.

6. Interview Q&A

Q: What problem does GraphQL solve over REST? A: Over-fetching and under-fetching. You request exactly the fields you need across related objects in one round-trip, instead of multiple REST calls each returning fixed, often excessive, payloads.

Q: How is Salesforce's GraphQL different from standard GraphQL? A: It's rooted at uiapi, uses a connection model (edges → node), and each field is an object with .value / .displayValue. It's built on the UI API, so it returns UI-aware, FLS-respecting data.

Q: When would you still pick REST over GraphQL? A: Simple single-object CRUD, when HTTP caching matters, or for public/simple integrations. GraphQL shines for nested, field-selective reads and dashboards.

Q: Does GraphQL bypass field-level security or sharing? A: No. It enforces the running user's FLS and sharing, so restricted fields and records won't appear.

Q: Can GraphQL do aggregations? A: Yes, group-by and functions like count/sum/avg on numeric and currency fields, returned in a single response, ideal for dashboards.

Talking point to explain it to anyone: "REST is a fixed-menu meal; GraphQL is à la carte. You ask for exactly the fields you want, including related records, and get one tidy response, no extra trips."


7. Key terms

GraphQL, over-fetching, under-fetching, field selection, uiapi, edges/node, aggregate query - defined here and in the Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 02-sobject-collections.md - work on up to 200 records in a single REST call.

02 - sObject Collections

One-liner: Create, update, upsert, delete, or retrieve up to 200 records of one object type in a single synchronous REST call, with an optional all-or-none rollback. Direction: External → Salesforce (inbound). Format: JSON over HTTP. Auth: OAuth 2.0 Bearer token. Use when: You have dozens to ~200 records to write or read in one round-trip and you want the answer now, not on an async job.

This is Module 08, the modern APIs. New to REST? See Module 04. For the full "which API" picture, see 04-modern-api-landscape.md. The previous page covered 01-graphql-api.md.


1. The idea in plain English

Imagine posting letters. The Standard REST API is one envelope per letter: 200 records means 200 trips to the post box, 200 stamps (API calls), 200 round-trips. sObject Collections is a single mail sack: you drop in up to 200 letters at once, hand it over in one trip, and the post office processes them together and hands you back one receipt sheet listing the outcome of each letter.

The catch the analogy hides: every letter in the sack must be addressed to the same object type. One sack for Accounts, another for Contacts. And the whole thing happens synchronously, you wait on the line and get all 200 results back in the same response. That immediacy is the whole point, and also the reason for the 200 cap.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
You have 50-200 records of one object to write in one trip. A single record → Standard REST API sObject Rows.
You need the result synchronously, right now. Thousands+ records, async is fine → Bulk API 2.0.
You want fewer API calls and fewer round-trips. Different operations chained together → Composite API.
You want optional all-or-none rollback across the batch. Parent and its children in one payload → sObject Tree.

Real-world examples: a nightly micro-sync pushing 180 changed Contacts; an order page submitting 200 line items at once; a dedupe job retrieving 150 Accounts by Id with two fields each; deleting a batch of 200 stale Leads after a campaign.


3. How it differs from its cousins

These three get confused constantly in interviews. The distinction is the headline.

Feature sObject Collections Composite API Bulk API 2.0
What it batches Same operation on many records, one object Different operations chained in order One operation on huge volumes
Sync or async Synchronous Synchronous Asynchronous (job + polling)
Volume per call Up to 200 records Up to 25 subrequests Thousands to millions
Chaining results ❌ No, records are independent ✅ Yes, @{refId.field} ❌ No
Home file this page 05-composite-api.md 01-bulk-api-2.md

The one-liner that nails it: sObject Collections is for many records, same job, now. Composite is for different jobs that depend on each other. Bulk is for volume you can wait on.

flowchart TD Q["What are you doing?"] --> A{Same operation<br/>on many records<br/>of one object?} A -->|Yes| V{How many?} A -->|"No, different<br/>chained operations"| C[Composite API] V -->|"Up to 200, need it now"| SC[sObject Collections] V -->|"Thousands+, async OK"| B[Bulk API 2.0] style SC fill:#00A1E0,color:#fff style C fill:#9C27B0,color:#fff style B fill:#ff9900,color:#fff style Q fill:#607D8B,color:#fff

4. How it works (sequence diagram)

sequenceDiagram autonumber participant EXT as External App participant SF as sObject Collections<br/>/composite/sobjects participant DB as Salesforce Data EXT->>SF: POST one body with up to 200 records + allOrNone + Bearer token SF->>DB: Process all records together in one transaction context Note over SF,DB: If allOrNone=true and any record fails, roll back ALL DB-->>SF: One result per record (SaveResult array) SF-->>EXT: 200 OK + array of per-record results, same order as input

Walkthrough

  1. One POST (or PATCH/DELETE/GET) carries the batch. For writes, the body has an allOrNone flag and a records array.
  2. Salesforce processes every record. Each carries its attributes.type so the platform knows the object.
  3. With allOrNone:true, a single failure rolls back the entire batch. With false (the default), good records commit and only the bad ones report errors, partial success.
  4. You get back one array, one entry per input record, in the same order. Each entry has success, the new id (on create), and any errors.

5. The actual request

Base: https://MyDomainName.my.salesforce.com/services/data/v66.0/composite/sobjects

Operation Method + path Returns
Create POST /composite/sobjects SaveResult[]
Update PATCH /composite/sobjects SaveResult[]
Upsert PATCH /composite/sobjects/{SObject}/{externalIdField} UpsertResult[]
Delete DELETE /composite/sobjects?ids=id1,id2&allOrNone=true DeleteResult[]
Retrieve GET /composite/sobjects/{SObject}?ids=id1,id2&fields=Name,Phone sObject[]
Retrieve (body) POST /composite/sobjects/{SObject} (ids + fields in body) sObject[]

Each record needs its type. In the body, every record carries "attributes": { "type": "Account" }. That is how one collection can technically mix object types, though most teams keep one type per call.

Create up to 200 records (POST)

POST /services/data/v66.0/composite/sobjects
Authorization: Bearer 00D...!AQ...
Content-Type: application/json

{
  "allOrNone": false,
  "records": [
    { "attributes": { "type": "Account" }, "Name": "Acme Corp", "Industry": "Technology" },
    { "attributes": { "type": "Account" }, "Name": "Globex Inc", "Industry": "Manufacturing" }
  ]
}

Response (one entry per record, same order):

[
  { "id": "001RO0000035frYYAQ", "success": true, "errors": [] },
  { "success": false, "errors": [
      { "statusCode": "REQUIRED_FIELD_MISSING", "message": "Required fields are missing: [Name]", "fields": ["Name"] }
  ] }
]

With allOrNone:false above, record 1 commits even though record 2 fails. Flip to allOrNone:true and neither would commit.

Update (PATCH) — same shape, but each record must include its Id:

PATCH /services/data/v66.0/composite/sobjects
{
  "allOrNone": true,
  "records": [
    { "attributes": { "type": "Contact" }, "id": "003...AAA", "Title": "VP Sales" },
    { "attributes": { "type": "Contact" }, "id": "003...BBB", "Title": "Director" }
  ]
}

Delete (DELETE) — Ids go in the query string:

DELETE /services/data/v66.0/composite/sobjects?ids=001...AAA,001...BBB&allOrNone=true

6. Design considerations and gotchas

Consideration Why it matters What to do
200-record hard cap All operations top out at 200 records per call. For more, page into multiple calls or switch to Bulk API 2.0.
allOrNone default is false Without it, you get partial success, some commit, some fail silently to your code. Always read every result entry. Set allOrNone:true when the batch must be atomic.
One API call, many records The whole batch counts as one request against the daily API limit. Use it to collapse chatty per-record loops and save limits.
Synchronous timeout It runs inline and is subject to normal sync limits. Keep batches sane. Do not stuff 200 heavy records with deep automation.
Triggers fire per batch Your Apex triggers run on these records like any DML. Bulkify triggers. 200 records can hit governor limits if logic is sloppy.
Read the result array Results map positionally to input. Index 3 in equals index 3 out. Match errors back to source records by order, not by guessing.
Nested allOrNone Inside a Composite request, the outer call and each Collections subrequest each have their own allOrNone. Set both deliberately. They interact.

7. Interview Q&A

Q: What does sObject Collections do that the Standard REST API does not? A: It performs CRUD on up to 200 records in a single synchronous call, returning one result per record. The Standard REST API (sObject Rows) is one record per call, so 200 records would be 200 round-trips and 200 API calls. Collections is one round-trip and one API call.

Q: sObject Collections vs Composite API, what is the real difference? A: Collections runs the same operation across many records of one object and they are independent. Composite chains different operations in order and lets later steps reference earlier results with @{refId.field}. Use Collections for "200 Contacts, update them all"; use Composite for "create an Account, then a Contact that links to it."

Q: sObject Collections vs Bulk API 2.0? A: Synchronous vs asynchronous, and 200 vs thousands+. Collections is the right pick for 50-200 records when you need the answer immediately. Bulk 2.0 is for large volumes you can submit as a job and poll for later.

Q: What is the allOrNone flag and what is its default? A: It controls rollback. Default is false, meaning partial success, valid records commit and only the failures report errors. Set it to true and any single failure rolls back the entire batch. You must inspect the result array either way.

Q: How do you create a parent and its children with Collections? A: You do not, directly, because records in a collection are independent and Collections does not chain. Use sObject Tree or Composite with chaining instead. Collections is for flat batches of one object type.

Talking point to explain it to anyone: "Instead of mailing 200 letters one at a time, you drop all 200 into one mail sack, hand it over once, and get back one sheet telling you which letters made it and which bounced."


8. Key terms

sObject Collections, /composite/sobjects, SaveResult, UpsertResult, DeleteResult, allOrNone, partial success, synchronous, 200-record limit, external Id (upsert) - defined here and in the Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 03-data-cloud-apis.md - the APIs for ingesting and querying Salesforce Data Cloud.

03 - Data Cloud APIs

One-liner: A separate family of APIs to stream or bulk-load external data into Data Cloud, then query, profile, and act on the unified result at scale. Direction: Mostly External → Data Cloud (ingest) and External → Data Cloud (query). Format: JSON and CSV over HTTPS. Auth: OAuth 2.0, then a token exchange for a Data Cloud token. Use when: Your org licenses Data Cloud and you need to unify, query, or activate large external data sets that do not belong in core CRM objects.

This is Module 08, the modern APIs. For the full "which API" picture, see 04-modern-api-landscape.md. The previous page covered 02-sobject-collections.md.

Naming note (read this first). Salesforce is rebranding Data Cloud to Data 360 across its docs in 2025-2026. You will see both names, and they mean the same product. This page uses "Data Cloud" for the concept and flags "Data 360" where the current docs do. These APIs evolve fast, so treat exact endpoints as a snapshot and confirm against the live docs linked at the bottom.


1. The idea in plain English

Core Salesforce (Sales Cloud, Service Cloud) is your office filing cabinet: tidy, governed, but sized for CRM-scale data. Data Cloud is a giant warehouse next door built to absorb billions of rows from everywhere, web clicks, mobile events, order systems, data lakes, and stitch them into one unified profile per person.

The Data Cloud APIs are the loading docks and the search desk of that warehouse:

  • The Ingestion API is the loading dock: trucks back up and drop off data, either a steady trickle (streaming) or pallets of CSV (bulk).
  • The Query API is the search desk: ask SQL-style questions across everything in the warehouse.
  • The Profile API is the "pull this one customer's full file" window.
  • The Connect API is the back office: manage the warehouse itself, its segments, insights, and metadata.

Critically, this warehouse is a separately licensed product. These APIs sit alongside the core platform APIs (REST, Bulk, GraphQL), not inside them, and they use a slightly different auth handshake.


2. When to use it (and when not)

✅ Use it when ❌ Avoid / use something else
Your org licenses Data Cloud / Data 360. No Data Cloud license → use core APIs. There is nothing to call.
You ingest large external data sets (events, web, IoT). A handful of CRM records → sObject Collections or REST.
You query a unified profile across many sources. Querying standard CRM objects → SOQL / GraphQL.
You build segments, insights, activation. Streaming CRM change events → Pub/Sub API.

Real-world examples: streaming clickstream events from a website into Data Cloud every few seconds; nightly bulk upload of a 120 MB orders CSV; running a SQL query across unified profiles to build an audience segment; pulling one customer's full unified profile for a service agent screen.


3. The four APIs at a glance

API Job Pattern Typical resource root
Ingestion API Push external data in Streaming (JSON) + Bulk (CSV) Tenant-specific object endpoints
Query API Run SQL over Data Cloud objects Sync first batch + async pagination /api/v2/query or Connect /ssot/query-sql
Profile API Read a unified individual profile Synchronous read Connect /ssot/... profile resources
Connect API Manage metadata, segments, insights, actions Connect REST resources /services/data/vXX/ssot/... and /connect/...

Two SQL query flavors exist. The older Query API V1/V2 and the newer Query Connect API (/ssot/query-sql, introduced around 2025) coexist. The Connect variant is the direction of travel and has no row-count limit with row-based pagination. Confirm which your org and tooling target before building.


4. How it works (ingest then query)

flowchart LR SRC[External sources<br/>web, mobile, orders] -->|Ingestion API<br/>stream JSON or bulk CSV| DC[(Data Cloud<br/>Data Lake Objects)] DC -->|Mapping + Identity Resolution| UP[(Unified Profiles<br/>and Insights)] UP -->|Query API SQL| Q[Analytics / app] UP -->|Profile API| AGENT[Agent / UI single profile] UP -->|Connect API| SEG[Segments / activation] style DC fill:#00A1E0,color:#fff style UP fill:#9C27B0,color:#fff style SEG fill:#4CAF50,color:#fff style SRC fill:#607D8B,color:#fff

Walkthrough

  1. Ingest. External systems call the Ingestion API. Streaming sends small JSON payloads as changes happen. Bulk uploads large CSV files via a job. Data lands in Data Lake Objects.
  2. Harmonize. Inside Data Cloud, mapping and identity resolution stitch records into unified profiles and calculate insights. This is platform work, not an API call.
  3. Consume. Query the result with the Query API (SQL), pull one person with the Profile API, or manage segments and metadata with the Connect API.

Eventual consistency. Ingested data is processed asynchronously. The docs say to allow roughly 30 seconds minimum before streamed data is queryable, and streaming is processed about every 3 minutes. Do not expect read-your-write immediacy.


5. The actual requests

Authentication: a two-step token exchange

Data Cloud does not use your normal Salesforce session token directly. You authenticate to Salesforce, then exchange that token for a Data Cloud access token scoped to a tenant-specific endpoint.

sequenceDiagram autonumber participant APP as External App participant SF as Salesforce Core OAuth participant DC as Data Cloud Token Exchange APP->>SF: OAuth flow (e.g. JWT Bearer) for a core access token SF-->>APP: access_token + instance_url APP->>DC: Exchange core token for Data Cloud token DC-->>APP: Data Cloud access_token + tenant-specific instance_url APP->>DC: Call Ingestion / Query / Profile against the tenant endpoint

Relevant OAuth scopes: cdp_ingest_api (Ingestion), cdp_query_api (Query), cdp_profile_api (Profile), plus the base api and refresh_token scopes.

Ingestion API — streaming (small JSON, near real time)

POST https://<tenant>.c360a.salesforce.com/api/v1/ingest/sources/{sourceName}/{objectName}
Authorization: Bearer <DataCloudToken>
Content-Type: application/json

{ "data": [
    { "id": "c-001", "email": "jo@example.com", "event": "page_view", "ts": "2026-06-18T10:00:00Z" }
] }

Streaming limits worth knowing: max ~200 KB per request, up to 200 records deletable per call, processed asynchronously roughly every few minutes.

Ingestion API — bulk (large CSV, periodic)

A bulk load is a job lifecycle: create a job, upload one or more CSV files (max 100 files per job, each up to 150 MB), then close the job to start processing.

POST .../api/v1/ingest/jobs            # create job (object + operation: upsert/delete)
PUT  .../api/v1/ingest/jobs/{id}/batches  # upload CSV
PATCH .../api/v1/ingest/jobs/{id}      # { "state": "UploadComplete" } to start

Query API — SQL over Data Cloud

POST https://<tenant>.c360a.salesforce.com/api/v2/query
Authorization: Bearer <DataCloudToken>
Content-Type: application/json

{ "sql": "SELECT FirstName__c, City__c FROM UnifiedIndividual__dlm WHERE City__c = 'Berlin' LIMIT 100" }

The newer Query Connect API uses POST /services/data/v66.0/ssot/query-sql to submit, GET .../ssot/query-sql/{queryId} to check status, GET .../ssot/query-sql/{queryId}/rows to page through results, and DELETE to cancel.

Object suffixes. Data Cloud objects use suffixes like __dlm (Data Model Object) and __dll (Data Lake Object), not the CRM __c. Field names and suffixes depend on your data model.


6. Design considerations and gotchas

Consideration Why it matters What to do
Separate license Data Cloud is a paid add-on. No license, no endpoints. Confirm provisioning before designing anything against it.
Two-step auth The core token alone will not work. You need the exchanged Data Cloud token and tenant endpoint. Implement the token exchange. Cache the tenant instance_url.
Eventual consistency Ingested data is not instantly queryable (allow ~30s+). Do not build read-after-write flows. Design for async.
Streaming vs bulk Streaming is small and frequent; bulk is large and periodic. Match the pattern to the data shape. Same stream can take both.
Volume limits Streaming ~200 KB/request, bulk CSV 150 MB/file, 100 files/job, rate caps apply. Chunk uploads. Honor HTTP 429 with back-off.
Rebrand churn Docs are mid-rename to Data 360; some endpoints and version paths shift. Pin the API version. Re-verify endpoints against live docs.
Not for core CRM These do not replace REST/Bulk for Accounts and Contacts. Use core APIs for CRM objects; Data Cloud for the unified lake.

7. Interview Q&A

Q: What is Data Cloud and why does it have its own APIs? A: Data Cloud (now branded Data 360) is a separately licensed platform for ingesting, unifying, and activating massive external data sets into one profile per individual. It has dedicated APIs because it is a different data engine from core CRM, with its own objects (__dlm, __dll), its own scale, and its own auth.

Q: What are the main Data Cloud APIs? A: The Ingestion API (stream or bulk-load data in), the Query API (SQL over Data Cloud objects), the Profile API (read a unified individual), and the Connect API (manage metadata, segments, insights, and actions).

Q: Streaming vs bulk ingestion, when do you use each? A: Streaming for small, frequent JSON payloads in near real time, like clickstream events. Bulk for large CSV files loaded periodically, like a nightly orders export. The same data stream can accept both.

Q: How is authentication different from core APIs? A: It is a two-step exchange. You authenticate to Salesforce normally (often JWT Bearer), then exchange that token for a Data Cloud access token tied to a tenant-specific endpoint, using scopes like cdp_ingest_api, cdp_query_api, and cdp_profile_api.

Q: Can you read data back the instant you ingest it? A: No. Ingestion is eventually consistent, processed asynchronously. The docs advise allowing at least ~30 seconds before ingested data is queryable, so design for async rather than read-after-write.

Talking point to explain it to anyone: "Core Salesforce is the filing cabinet; Data Cloud is the huge warehouse next door. The Ingestion API is its loading dock, the Query and Profile APIs are its search desk, and you need a special pass (a separate token) to get in."


8. Key terms

Data Cloud / Data 360, Ingestion API, Query API, Profile API, Connect API, Data Lake Object (__dll), Data Model Object (__dlm), unified profile, identity resolution, streaming vs bulk ingestion, token exchange, tenant-specific endpoint, eventual consistency - defined here and in the Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 04-modern-api-landscape.md - the capstone "which API do I use?" decision guide.

04 - The Modern API Landscape

One-liner: One page to pick the right Salesforce API for any integration, by purpose, sync vs async, format, and volume. Direction: Both. Format: Mixed. Auth: OAuth 2.0 (mostly). Use when: You are designing an integration and need to choose, or an interviewer asks "which API would you use and why?"

This is the capstone of Module 08 and a synthesis of the whole vault. Each API below links to its home page across Modules 04, 06, 07, and 08.


1. The idea in plain English

Salesforce gives you a toolbox, not a single tool. Reaching for REST when you should use Bulk is like digging a foundation with a teaspoon. Reaching for Bulk to fetch one record is like swinging a backhoe to plant a seed. The skill is matching the tool to the job.

Three questions settle almost every choice:

  1. How much data? One record, a couple hundred, or millions?
  2. Sync or async? Do you need the answer now, or can you submit a job and collect results later?
  3. What shape? Simple CRUD, a precise field selection, chained operations, or a live event stream?

Answer those three and the API picks itself. The rest of this page is the lookup table and a decision tree.


2. The full comparison table

API Purpose Sync / Async Format Volume When to use Home
REST API General CRUD + SOQL, the workhorse Sync JSON / XML 1 record per call Default for most app integrations 04 · 01
SOAP API Legacy, strongly typed, WSDL contract Sync XML 1-200 per call Old/enterprise clients needing a WSDL 04 · 02
GraphQL Pick exact fields across related objects Sync JSON Read-shaped, paged Avoid over/under-fetching; dashboards 08 · 01
Composite Chain several different ops in one trip Sync JSON Up to 25 subrequests Dependent calls, all-or-none 04 · 05
sObject Collections Same op on many records, one object Sync JSON Up to 200 records 50-200 records, need it now 08 · 02
Bulk API 2.0 Mass load/extract, job-based Async CSV / JSON Thousands to millions Large data loads you can poll 07 · 01
Pub/Sub API Publish + subscribe to events over gRPC Async stream Avro / gRPC Event stream Modern event-driven, CDC, replay 06 · 04
Streaming API Legacy CometD push (PushTopic, generic) Async stream JSON Event stream Older event subscribers; being superseded 06 · 05
Connect REST API Chatter, Experience Cloud, Files, feeds Sync JSON Feature-scoped Social, communities, files, programmatically 04 · 06
UI API Build Salesforce-like UIs (FLS + layout aware) Sync JSON UI-scoped Custom UIs needing layout/FLS metadata 04 · 07
Metadata API Deploy/retrieve config and metadata Async XML Org metadata CI/CD, deployments, org setup README
Tooling API Developer tooling, fine-grained metadata Sync JSON / XML Dev artifacts IDEs, code analysis, debug logs README
Data Cloud APIs Ingest, query, profile unified data Both JSON / CSV Massive Data Cloud-licensed orgs only 08 · 03

The two axes that matter most: volume (1 → 200 → millions) and timing (sync now vs async job). REST → sObject Collections → Bulk 2.0 is the same axis at three scales. Everything else is about shape: GraphQL for field selection, Composite for chaining, Pub/Sub for events, Metadata/Tooling for config.


3. Decision tree — what are you trying to do?

flowchart TD START["What are you trying to do?"] --> KIND{Move records,<br/>stream events,<br/>or deploy metadata?} KIND -->|Deploy / retrieve config| META["Metadata API (async)<br/>Tooling API (dev tooling)"] KIND -->|React to live events| EV{Modern or legacy?} KIND -->|Move record data| DATA{How many records?} EV -->|Modern, gRPC, replay| PS[Pub/Sub API] EV -->|Legacy CometD| STR[Streaming API] DATA -->|"Millions, async OK"| BULK[Bulk API 2.0] DATA -->|"Up to 200, one object, now"| COLL[sObject Collections] DATA -->|"A few, different chained ops"| COMP[Composite API] DATA -->|"One record or simple CRUD"| ONE{Special need?} ONE -->|Exact fields, nested objects| GQL[GraphQL] ONE -->|Chatter / Files / Community| CONN[Connect REST] ONE -->|Layout + FLS aware UI| UIAPI[UI API] ONE -->|Unified Data Cloud data| DC[Data Cloud APIs] ONE -->|Plain CRUD / SOQL| REST[REST API] style PS fill:#9C27B0,color:#fff style BULK fill:#ff9900,color:#fff style COLL fill:#00A1E0,color:#fff style COMP fill:#9C27B0,color:#fff style GQL fill:#00A1E0,color:#fff style REST fill:#4CAF50,color:#fff style DC fill:#00A1E0,color:#fff style META fill:#607D8B,color:#fff style START fill:#607D8B,color:#fff

4. The three-scale spine (memorize this)

For plain record movement, picture one axis of growing volume. This single line answers most integration questions.

Scale Records API Why
One 1 at a time REST API Simple, synchronous, one call per record
Some up to 200, one object, sync sObject Collections One round-trip, one API call, immediate
Many thousands to millions, async Bulk API 2.0 Job-based, parallelized, poll for results

Then layer the shape exceptions on top:

  • Need specific fields across related objects? → GraphQL.
  • Need different operations chained with all-or-none? → Composite.
  • Need parent + children in one payload? → sObject Tree (in Composite).
  • Need to react to changes? → Pub/Sub API (modern) over Streaming (legacy).

5. Design considerations and gotchas

Consideration Why it matters What to do
Sync vs async first It splits the whole toolbox in two. Decide if you can wait. Async (Bulk, Metadata, Pub/Sub) unlocks scale.
API limits Every sync call counts against the daily allocation. Batch with Collections/Composite; offload volume to Bulk. See Module 09.
Legacy vs modern events Streaming API is superseded by Pub/Sub. Choose Pub/Sub for new event work; know Streaming for old systems.
GraphQL is read-shaped It is not a bulk loader. Use it to shape reads, not to mass-write.
Data Cloud is separate Different license, different token. Only relevant if the org is provisioned. See 03.
Metadata vs data Metadata API moves config, not rows. Never use it to move records, and never use data APIs for config.
Pin the version Behavior and shape change by version. Use v66.0 consistently across the integration.

6. Interview Q&A

Q: Walk me through choosing an API for a 500,000-record nightly load. A: That is volume + async, so Bulk API 2.0. It is job-based, parallelized, and built for millions. sObject Collections caps at 200 and is synchronous; REST would be 500,000 calls. Bulk is the only sane fit.

Q: A mobile app needs an Account with its Contacts and Opportunities in one call, only a few fields each. Which API? A: GraphQL. It selects exact fields across related objects in one round-trip, killing over-fetching and under-fetching. Composite could bundle the calls, but GraphQL is purpose-built for field-selective nested reads.

Q: You must create an Account, then a Contact linked to it, atomically. Which API? A: Composite with chaining (@{refId.field}) and allOrNone:true, or sObject Tree for the nested parent-child payload. sObject Collections cannot chain because its records are independent.

Q: REST vs sObject Collections vs Bulk, in one sentence each. A: REST is one record, sync. sObject Collections is up to 200 records of one object, sync, one call. Bulk 2.0 is thousands to millions, async, job-based. Same axis, three scales.

Q: New project needs to react to record changes in near real time. Which event API? A: Pub/Sub API, gRPC-based, bi-directional, with replay and efficient Avro payloads. The older Streaming API still works but is being superseded, so pick Pub/Sub for anything new.

Talking point to explain it to anyone: "Salesforce hands you a toolbox. One record, use REST. A couple hundred at once, use sObject Collections. Millions, use Bulk. Want exact fields, use GraphQL. Want live events, use Pub/Sub. Match the tool to the job."


7. Key terms

API surface, synchronous, asynchronous, volume, field selection, chaining, event stream, metadata vs data, three-scale spine (REST → Collections → Bulk) - defined here and in the Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: back to the README.md - or jump to Module 09 · Security and Limits to see how these APIs are governed.

Module 09 - Security & Limits

Goal: Don't get hacked, and don't hit a limit in production. API version: v66.0 (Spring '26). Two halves: secure every integration end-to-end, and know every limit before go-live.

Security in Salesforce is layered: connection allowlists, authentication, access controls, data-level security, and encryption. Limits are the guardrails of the multitenant platform. Start with 01-connection-security.md (the CORS/CSP/Remote-Site confusion). For the OAuth underneath, see Module 03.


Map of this module

# File What it covers
01 connection-security CORS vs CSP Trusted Sites vs Remote Site Settings
02 authentication-and-access-controls API Enabled, profiles/perm sets, IP ranges, scopes
03 data-level-security CRUD, FLS, sharing, enforcing in Apex
04 mtls-and-shield-encryption Mutual TLS + Shield Platform Encryption
05 governor-and-api-limits Callout, async, org, and bulk limits

The security layers an API call passes through

flowchart TD EXT[External caller] --> CONN{Connection allowlist<br/>CORS / mTLS} CONN -->|allowed| AUTH{Authentication<br/>OAuth token / cert} AUTH -->|valid| PERM{API Enabled<br/>+ app policy + scope} PERM -->|permitted| OBJ{Object / CRUD<br/>permissions} OBJ -->|allowed| FLS{Field-Level<br/>Security} FLS -->|allowed| SHR{Record-level<br/>sharing} SHR -->|visible| DATA[(Record)] style CONN fill:#00A1E0,color:#fff style AUTH fill:#ff9900,color:#fff style PERM fill:#9C27B0,color:#fff style OBJ fill:#4CAF50,color:#fff style FLS fill:#4CAF50,color:#fff style SHR fill:#4CAF50,color:#fff

Every layer must pass. Miss one in design and you either leak data or get blocked in production.


Limits cheat sheet (corrected and verified)

Callout governor limits (per transaction)

Limit Value
Callouts per transaction 100
Request + response size 6 MB sync, 12 MB async (not 10 MB)
Timeout per callout default 10s, max 120s
Total callout time per transaction 120s
Callout after uncommitted DML Not allowed
Concurrent long-running sync requests (>5s) 10 (scales by license up to 50)

Org and async limits

Limit Value
Daily API requests By edition + licenses (e.g. Enterprise base 100,000 + per-license)
Bulk API 1.0 15,000 batches / 24h (10,000 records each)
Bulk API 2.0 100 million records / 24h, 150 MB per job
Async Apex executions / 24h greater of 250,000 or 200 × user licenses
Check live usage GET /services/data/v66.0/limits

Interview rapid-fire

Q: CORS vs CSP Trusted Sites? → CORS lets external browsers call Salesforce (inbound). CSP Trusted Sites lets Salesforce's own LWC/Aura reach external resources (outbound from the browser). See 01.

Q: How do you secure a high-assurance B2B integration? → mTLS (mutual certificate auth), least-privilege integration user, and Shield encryption for sensitive data at rest. On Hyperforce, prefer mTLS over IP allowlists. See 04.

Q: Apex REST runs as system context. What must you add? → Enforce security yourself: with sharing, WITH USER_MODE, and Security.stripInaccessible(). See 03.

Q: Max callout response size? → 6 MB synchronous, 12 MB asynchronous. (The "10 MB" figure is wrong.)

Q: How do you check remaining API calls? → The /limits REST resource or System Overview in Setup.


Sources (Verified June 2026)

Each file has its own Sources section with the specific official doc.

01 - Connection Security (CORS vs CSP Trusted Sites vs Remote Site Settings)

One-liner: Three different "allowlists" decide who may reach Salesforce and where Salesforce may reach out. People constantly confuse them, so nail the distinction. The three: CORS (inbound browser → SF API), CSP Trusted Sites (SF's own LWC/Aura browser → external), Remote Site Settings (Apex callout → external, legacy).

This is Module 09, security and limits. For the OAuth that authenticates callers, see Module 03. For outbound callouts, see Module 05.


1. The idea in plain English

Think of Salesforce as a secured building. Three different rules govern doors and errands, and mixing them up is the classic mistake:

  • CORS is the guest list at the front door: which outside browsers (origins) are allowed to walk in and call the Salesforce API directly from JavaScript.
  • CSP Trusted Sites is where your in-house staff may go: which external sites your own Lightning (LWC/Aura) pages are permitted to load scripts/images from or call from the browser.
  • Remote Site Settings is the approved address book for the butler: which external URLs Apex server-side callouts may hit (largely superseded by Named Credentials).

The key axes are direction (into Salesforce vs out of Salesforce) and who is making the call (an external browser, a Salesforce browser page, or Apex on the server).


2. The three controls compared

Control Direction Who it governs When you need it
CORS allowlist Inbound An external website's browser calling the Salesforce REST/UI/etc. API cross-origin A SPA on app.example.com calls Salesforce APIs from JavaScript
CSP Trusted Sites Outbound (browser) Salesforce's own LWC/Aura loading or calling external resources An LWC pulls an image, script, or fetch()s an external API from the browser
Remote Site Settings Outbound (server) Apex HTTP callouts to a raw URL Apex calls an external endpoint without a Named Credential

The 10-second answer: CORS = let outsiders' browsers in. CSP Trusted Sites = let our browser pages reach out. Remote Site Settings = let Apex reach out (use Named Credentials instead).


3. How they fit together

flowchart LR EXTB[External browser app] -->|cross-origin API call| CORS{CORS allowlist} CORS -->|origin allowed| SFAPI[Salesforce API] LWC[Salesforce LWC / Aura<br/>in the browser] -->|load script / fetch| CSP{CSP Trusted Sites} CSP -->|site allowed| EXTR[External resource] APX[Apex callout] -->|named credential| NC[Named Credential<br/>RECOMMENDED] APX -->|raw URL| RSS{Remote Site Setting} NC --> EXT[External API] RSS --> EXT style CORS fill:#00A1E0,color:#fff style CSP fill:#ff9900,color:#fff style RSS fill:#9C27B0,color:#fff style NC fill:#4CAF50,color:#fff
  • CORS gates the inbound browser call. Note: CORS handles origin, not authentication. The caller still needs a valid OAuth token.
  • CSP Trusted Sites gates what Salesforce's own UI may load or call from the browser.
  • For Apex callouts, a Named Credential both authenticates and authorizes the endpoint, so you do not need a Remote Site Setting. Raw-URL callouts still require one.

4. Setup / configuration

  • CORS: Setup → CORS → add the external origin (e.g. https://app.example.com) to the allowlist. Managed via the CorsWhitelistOrigin metadata.
  • CSP Trusted Sites: Setup → CSP Trusted Sites → add the external site and choose the contexts (connect-src, img-src, script-src, etc.). Managed via CspTrustedSite metadata.
  • Remote Site Settings: Setup → Remote Site Settings → add the URL. Prefer creating a Named Credential instead, which removes this need. (Module 05.)

5. Gotchas and interview traps

Gotcha Clarification
"CORS authenticates the caller." No. CORS only allows the origin. The request still needs a valid OAuth token.
"Use Remote Site Settings for callouts." Prefer Named Credentials, which authenticate and remove the Remote Site requirement.
"CSP Trusted Sites lets external apps call us." No, that's CORS. CSP governs what our Lightning pages may reach outbound in the browser.
"CORS and CSP are the same." Opposite directions. CORS = inbound to SF API; CSP Trusted Sites = outbound from SF's browser UI.

6. Interview Q&A

Q: CORS vs CSP Trusted Sites vs Remote Site Settings? A: CORS allowlists external browser origins calling Salesforce APIs (inbound). CSP Trusted Sites allowlists external resources that Salesforce's own LWC/Aura pages load or call (outbound from the browser). Remote Site Settings allowlists URLs for Apex server-side callouts (outbound, legacy, superseded by Named Credentials).

Q: Does CORS replace authentication? A: No. CORS only permits the origin to make cross-origin calls. The caller still authenticates with OAuth.

Q: When do you NOT need a Remote Site Setting? A: When you use a Named Credential for the callout. It registers and authenticates the endpoint, so no Remote Site Setting is required.

Q: An LWC needs to call a third-party API directly from the browser. What do you configure? A: Add the API's domain to CSP Trusted Sites (with the right directive like connect-src). For server-side, you'd use Apex + a Named Credential instead.

Talking point to explain it to anyone: "Three guest lists: who may come in (CORS), where our staff may go (CSP Trusted Sites), and which addresses the butler may visit (Remote Site Settings, now usually replaced by Named Credentials)."


7. Key terms

CORS, origin, CSP, Trusted Site, Remote Site Setting, Named Credential, cross-origin - defined here and in the Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 02-authentication-and-access-controls.md - who is allowed to call the API at all.

02 - Authentication and Access Controls (WHO Can Call the API)

One-liner: Before a single byte of data is touched, Salesforce asks a chain of "are you even allowed in?" questions. This file is that chain: IP, OAuth scope, the API Enabled permission, and the app's own user policy. API version: v66.0 (Spring '26). For the OAuth flows and tokens that authenticate the caller, see Module 03.

This is Module 09, security and limits. This file is WHO may call the API. The next file, 03-data-level-security.md, is WHAT that caller can then see and do.


1. The idea in plain English

Authentication answered "who are you?" (Module 03). Access control answers "are you allowed to use this door at all?" Two different questions.

Think of an office tower with an integration vendor arriving to do work:

  • Login IP Ranges are the parking gate: if you did not come from an approved address, the gate never lifts. You never even reach the lobby.
  • OAuth scope is what your building pass is encoded to open: a token granted only the api scope can call data APIs but cannot, say, manage users.
  • API Enabled is the "this badge works for the service entrance, not just the front door" flag. Without it, the human can log in to the UI but the API turns them away.
  • Connected App / External Client App policy is the vendor contract: even with a valid badge, the building owner decides which vendors (apps) are pre-approved to operate.

Miss any one link and the call fails before a record is ever read. Each layer is independent, so the request must satisfy all of them.


2. The access-control layers and when each applies

Layer Question it answers Set where Applies to
Login IP Ranges (profile) Did the request come from an allowed network? Profile → Login IP Ranges The user behind the token
Connected App IP Relaxation Should this app bypass or enforce org IP rules? The app's OAuth policy The app
OAuth scopes What categories of API is this token allowed to hit? App definition + requested at token time The token
API Enabled permission May this user touch any API at all? Profile or permission set The user
Apex REST Services permission May this user invoke custom Apex REST endpoints? Profile or permission set The user (notably guest/site users)
Permitted Users policy Is this app pre-authorized for this user? App's OAuth policy The app + user pairing

The 10-second answer: IP gates the network, scopes gate the token, API Enabled gates the user, and the Connected App policy gates the app. All four must pass.


3. How a request flows through the gauntlet

flowchart LR REQ[API request<br/>with access token] --> IP{Login IP Range<br/>+ App IP Relaxation} IP -->|blocked| DENY[401 / login denied] IP -->|allowed| SCOPE{OAuth scope<br/>covers this API?} SCOPE -->|no| DENY SCOPE -->|yes| APIEN{User has<br/>API Enabled?} APIEN -->|no| DENY APIEN -->|yes| APP{App Permitted Users<br/>policy satisfied?} APP -->|not pre-authorized| DENY APP -->|allowed| DATA[Reaches data-security layer<br/>see file 03] style IP fill:#F44336,color:#fff style SCOPE fill:#ff9900,color:#fff style APIEN fill:#00A1E0,color:#fff style APP fill:#9C27B0,color:#fff style DATA fill:#4CAF50,color:#fff

Walkthrough:

  1. IP check first. If the user's profile has Login IP Ranges, a request from any other address is denied. With Enforce login IP ranges on every request turned on (Session Settings), this is checked on every API call, not just at login.
  2. Scope check. The token only works against APIs covered by the scopes it was issued. A token without api cannot call the REST data API no matter who the user is.
  3. API Enabled check. The running user must have the API Enabled user permission, or the platform rejects all API access with an error.
  4. App policy check. If the Connected App / External Client App is set to Admin approved users are pre-authorized, the user must be authorized via a profile or permission set, or access is refused.

Only after all four pass does the request reach the data-security funnel in file 03.


4. Setup, configuration, and least-privilege

4.1 The user permissions that unlock the API

Permission What it unlocks Where granted
API Enabled Any API access (REST, SOAP, Bulk, etc.). Without it, no API call works. Profile or permission set
API Only User User can use the API but cannot log in to the UI. Ideal for integration identities. Profile or permission set
Apex REST Services Invoke custom @RestResource Apex endpoints. Needed for guest/site users calling Apex REST. Profile or permission set
Author Apex Write and deploy Apex (and run executeAnonymous). A build-time permission, not a runtime API gate. Profile or permission set

Trap: "Apex REST Services" and "Author Apex" are different. Author Apex lets a developer create the class. Apex REST Services lets a user call a custom Apex REST endpoint (most relevant for unauthenticated guest users on Experience Cloud sites).

4.2 Least-privilege for integration users (the modern pattern)

Do not reuse an admin login for integrations. The recommended build:

  1. Create a dedicated user per integration on the Minimum Access - API Only Integrations profile (built on Minimum Access - Salesforce, grants API Enabled + API Only, and these cannot be edited on the profile).
  2. Layer on exactly the object, field, and feature permissions the integration needs via permission sets (assigned with the Salesforce API Integration permission set license).
  3. Authenticate via the Client Credentials flow, choosing that user as the Run As execution user. The token inherits that user's permissions, so a tightly scoped user yields a tightly scoped token.

This is least privilege in practice: a Minimum Access base plus a narrow permission set, never a Modify All Data admin.

4.3 IP controls

  • Profile Login IP Ranges: Setup → Profiles → (profile) → Login IP Ranges → add start/end addresses. Any login outside the range is denied. Winter '26 enforces a cap on how many ranges a profile may hold per IP type.
  • Org Trusted IP Ranges: Setup → Network Access. Addresses here skip identity verification (no challenge), but are not a login restriction by themselves.
  • Connected App IP Relaxation: in the app's OAuth policy, choose Enforce IP restrictions (default, honor org/profile IP rules), Relax IP restrictions for activated devices, or Relax IP restrictions (ignore org IP rules for this app).

4.4 Connected App / External Client App user policy

In Manage → OAuth Policies, set Permitted Users:

  • All users may self-authorize (default): any user can approve the app on first use.
  • Admin approved users are pre-authorized: only users granted the app via a profile or permission set may use it. This is the locked-down choice for production integrations.

Critical bypass: a user with the Use Any API Client permission can use any connected app even when it is set to Admin-approved. Guard that permission carefully in a security review.


5. Gotchas and interview traps

Gotcha Clarification
"Valid OAuth token means the call works." No. The user still needs API Enabled, the token needs the right scope, the IP must pass, and the app policy must allow it.
"Login IP Ranges only block interactive logins." Only until you enable Enforce login IP ranges on every request, which then checks every API call too.
"Trusted IP Ranges restrict who can log in." No. Org Trusted IP Ranges only skip the identity-verification challenge. Profile Login IP Ranges are the actual restriction.
"Admin-approved blocks everyone unauthorized." Mostly, but Use Any API Client overrides it.
"Author Apex lets you call an Apex REST endpoint." No. Calling needs Apex REST Services (and access to the class). Author Apex is for building Apex.
"Scope full includes a refresh token." No, you must request refresh_token separately. (See Module 03.)

6. Interview Q&A

Q: A partner has a valid access token but every API call returns an error. Where do you look? A: Walk the chain. Confirm the user has API Enabled, the token carries the api scope, the request's source IP is within the profile's Login IP Ranges (or relaxed on the app), and the Connected App's Permitted Users policy authorizes that user. Any one of these failing blocks the call before data access.

Q: What is the modern least-privilege setup for a server-to-server integration? A: A dedicated user on the Minimum Access - API Only Integrations profile (API Enabled + API Only), narrow permission sets for exactly the needed objects/fields, authenticated via the Client Credentials flow with that user as the Run As user. The token then inherits only those minimal permissions.

Q: Difference between profile Login IP Ranges and org Trusted IP Ranges? A: Profile Login IP Ranges restrict where a user may authenticate from (outside the range is denied). Org Trusted IP Ranges only let users skip the identity-verification challenge. One is a wall, the other is a fast lane.

Q: What do OAuth scopes control, and how does that relate to access control? A: Scopes gate which categories of API a token may call (for example api for data APIs, web for browser sessions). They are AuthZ at the token level: even a powerful user gets a narrow token if the app requested narrow scopes. (Full scope table in Module 03.)

Q: How do you lock a Connected App so only specific integrations can use it? A: Set Permitted Users to Admin approved users are pre-authorized and grant the app only via specific profiles/permission sets. Then audit who holds Use Any API Client, since that permission bypasses the restriction.

Talking point to explain it to anyone: "Four gates before any data: the network you came from (IP), what your pass is coded to open (scope), whether your badge even works for the service door (API Enabled), and whether the building approved your vendor (the app policy)."


7. Key terms

API Enabled, API Only User, Apex REST Services, Author Apex, Login IP Ranges, Trusted IP Ranges, IP Relaxation, OAuth scope, Permitted Users, Admin approved users are pre-authorized, Use Any API Client, Minimum Access - API Only Integrations, Run As user - defined here and in the Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 03-data-level-security.md - once a caller is in, what records and fields can they actually see and change.

03 - Data-Level Security (WHAT a Caller Can See and Do)

One-liner: Getting in (file 02) is not the same as seeing everything. Salesforce filters every call through object permissions, then field-level security, then record sharing. This file is that funnel, plus how to enforce it yourself in Apex. API version: v66.0 (Spring '26). Builds on 02-authentication-and-access-controls.md (who may call) and ties to Apex REST.

This is Module 09, security and limits. File 02 answered WHO may call the API. This file answers WHAT they can read and write once inside.


1. The idea in plain English

Imagine a library with three nested rules:

  • Object permissions (CRUD) decide which rooms you may enter at all: the Accounts room, the Cases room. No Read on Contacts means the Contacts room is locked.
  • Field-Level Security (FLS) decides which shelves in a room you may touch: you are in the Accounts room, but the Revenue shelf is blacked out.
  • Record-level sharing decides which individual books you may open: you can read Accounts, but only the ones shared with you by org-wide defaults, the role hierarchy, or sharing rules.

A call must clear all three. You can have full Read on Account (object), see every field (FLS), and still be handed zero records because none are shared with you.

The twist for integrations: the Standard REST/SOAP API enforces all three automatically as the running user. But Apex REST runs in system context, which bypasses them, so you must re-add the checks by hand.


2. The data-security layers and when each applies

Layer What it controls Set where Enforced automatically?
Object permissions (CRUD) Can you Create / Read / Update / Delete this object type? Profiles, permission sets Yes, by Standard API and Apex in user mode
Field-Level Security (FLS) Can you see/edit each individual field? Permission sets (or profiles) Yes, by Standard API and Apex in user mode
Org-Wide Defaults (OWD) Baseline record visibility (Private / Public Read / Public Read-Write) Sharing Settings Yes
Role hierarchy Managers inherit access to subordinates' records Roles Yes
Sharing rules Exceptions that widen OWD for groups/roles Sharing Settings Yes
Manual / Apex sharing One-off or programmatic record grants Record share or Apex Yes

The 10-second answer: Object says which tables, FLS says which columns, sharing says which rows. The Standard API applies all of them as the running user. Apex REST does not, so you enforce it.


3. How it works: the security funnel

flowchart TB CALL[API call as a user] --> OBJ{Object permission<br/>CRUD on the SObject?} OBJ -->|no access| BLOCK[Denied / stripped] OBJ -->|allowed| FLS{Field-Level Security<br/>per field?} FLS -->|field hidden| STRIP[Field removed from result] FLS -->|field visible| REC{Record sharing<br/>OWD + role + rules?} REC -->|not shared| NONE[Row not returned] REC -->|shared| RESULT[Visible, editable data] style OBJ fill:#00A1E0,color:#fff style FLS fill:#ff9900,color:#fff style REC fill:#9C27B0,color:#fff style RESULT fill:#4CAF50,color:#fff style BLOCK fill:#F44336,color:#fff

Walkthrough:

  1. Object gate. No Read on the object and the call returns an error or nothing for that type. No Create and an insert is rejected.
  2. Field gate. Even with object access, fields hidden by FLS are stripped from the response (and cannot be written). The user simply never sees them.
  3. Record gate. Of the records that survive, only those the user can see via OWD + role hierarchy + sharing rules + manual/Apex shares are returned. Private OWD with no sharing means an empty list.

The order matters for interviews: object then field then record. All three are AND-ed together.


4. Enforcing security in Apex

Apex runs in system mode by default, meaning it ignores object permissions and FLS, and its sharing behavior depends on the class keyword. For any code that touches data on behalf of a user, you opt back into the checks.

4.1 Sharing keywords (record-level)

Keyword Record (sharing) behavior Default when...
with sharing Enforces the running user's record sharing What you should use for user-facing logic
without sharing Ignores sharing, sees all records Deliberate elevated operations only
inherited sharing Runs in the caller's sharing mode A reusable class that should adapt to its caller

Sharp edge: sharing keywords control record sharing only. They do not enforce object permissions or FLS. Also, if a class is the entry point of the transaction and omits a keyword, it defaults to without sharing, whereas inherited sharing at the entry point defaults to the safer with sharing. Never omit the clause.

4.2 Enforcing object + field security in SOQL

Two clauses, very different vintages:

Mechanism Enforces Behavior on violation Notes
WITH USER_MODE Object perms, FLS, and sharing Throws on access failure Modern, recommended, faster, covers all three layers
WITH SECURITY_ENFORCED Object perms and FLS (not sharing) Throws System.QueryException, returns nothing Older; less complete than USER_MODE
// Modern: one clause enforces CRUD, FLS, and sharing as the running user.
List<Account> accts = [
    SELECT Id, Name, AnnualRevenue
    FROM Account
    WHERE Industry = 'Tech'
    WITH USER_MODE
];

You can also set the mode on DML with Database methods that accept AccessLevel.USER_MODE so inserts/updates honor the user's permissions too.

4.3 Graceful stripping with Security.stripInaccessible()

When you would rather drop inaccessible fields than throw, use Security.stripInaccessible(). It returns an SObjectAccessDecision; call getRecords() for the sanitized list.

SObjectAccessDecision decision = Security.stripInaccessible(
    AccessType.READABLE, accts
);
List<Account> safe = decision.getRecords(); // hidden fields removed

The AccessType enum is CREATABLE, READABLE, UPDATABLE, or UPSERTABLE. It also sanitizes objects deserialized from untrusted input before DML, avoiding exceptions.

4.4 Apex REST runs in system context, so enforce it yourself

This is the headline integration trap. Invoking a custom Apex REST method always uses system context. The caller's permissions, FLS, and sharing are not applied automatically. Any user with access to the endpoint wields its full power.

Therefore, in an @RestResource class you must:

  • Declare with sharing (or inherited sharing) for record-level enforcement.
  • Query with WITH USER_MODE and/or sanitize results with stripInaccessible().
  • Validate object/field access before DML.

By contrast, the Standard REST and SOAP APIs run as the calling user automatically, so object permissions, FLS, and sharing are all enforced with no extra code. That difference is exactly why hand-rolled Apex REST endpoints are a common source of data leaks. See ../04-Inbound-APIs/03-apex-rest.md.


5. Gotchas and interview traps

Gotcha Clarification
"with sharing secures everything." No. It enforces record sharing only, not object permissions or FLS. Add WITH USER_MODE for those.
"Apex REST respects the caller's permissions." No. Apex REST runs in system context. You must enforce CRUD, FLS, and sharing yourself.
"The Standard REST API needs manual FLS checks." No. It runs as the calling user and enforces object, field, and record security automatically.
"Omitting a sharing clause is harmless." At a transaction entry point, omission defaults to without sharing. Always declare it.
"WITH SECURITY_ENFORCED and WITH USER_MODE are the same." USER_MODE also enforces sharing and performs better; SECURITY_ENFORCED only covers object + field.
"Object Read access means you see the records." Only the shared ones. Private OWD with no sharing rule returns an empty list despite Read access.

6. Interview Q&A

Q: Name the three data-security layers Salesforce enforces on a data API call, in order. A: Object permissions (CRUD), then Field-Level Security (FLS), then record-level sharing (OWD, role hierarchy, sharing rules, manual/Apex shares). Object = which tables, field = which columns, sharing = which rows. All three are AND-ed.

Q: Standard REST API vs Apex REST, from a security standpoint? A: The Standard REST API runs as the calling user and enforces object, field, and record security automatically. Apex REST runs in system context, bypassing all of it, so the developer must re-add with sharing, WITH USER_MODE, and access checks. Forgetting this is a classic data-exposure bug.

Q: How do you enforce object, field, and record security inside a SOQL query? A: Add WITH USER_MODE, which enforces CRUD, FLS, and sharing as the running user and throws on a violation. It supersedes the older WITH SECURITY_ENFORCED, which covers only object and field security and not sharing.

Q: When would you use Security.stripInaccessible() instead of WITH USER_MODE? A: When you want to gracefully drop fields the user cannot access rather than throw an exception, or to sanitize sObjects from untrusted input before DML. It returns an SObjectAccessDecision; getRecords() gives the cleaned list. AccessType picks READABLE, CREATABLE, UPDATABLE, or UPSERTABLE.

Q: What does with sharing actually do, and what does it not do? A: It enforces the running user's record-level sharing. It does not enforce object permissions or FLS, and it is not a substitute for WITH USER_MODE. Use both for full coverage.

Talking point to explain it to anyone: "Three nested locks on every call: which rooms (object), which shelves (fields), which books (records). The normal API checks all three for you. Hand-written Apex endpoints run as a master key, so you must put the locks back yourself."


7. Key terms

Object permissions (CRUD), Field-Level Security (FLS), org-wide defaults (OWD), role hierarchy, sharing rules, manual sharing, Apex managed sharing, system mode, user mode, with sharing, without sharing, inherited sharing, WITH USER_MODE, WITH SECURITY_ENFORCED, Security.stripInaccessible, SObjectAccessDecision, AccessType - defined here and in the Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 04-mtls-and-shield-encryption.md - encrypting the connection and the data at rest.

04 - mTLS and Shield Platform Encryption

One-liner: Two high-security options that go beyond passwords and tokens. mTLS proves who is on each end of the wire. Shield Platform Encryption scrambles the data sitting in the database. The split: mTLS protects data in transit with certificates on both sides. Shield protects data at rest with org-managed keys. Use when: A bank, healthcare, or government integration demands certificate-based trust and field-level encryption that even Salesforce staff cannot read.

This is Module 09, security and limits. For the OAuth and Named Credentials that authenticate callers, see Module 03. For the outbound callouts that carry the certificate, see Module 05. Sibling: 01-connection-security.md.


1. The idea in plain English

Imagine a secure courier service moving a locked safe between two buildings.

  • mTLS (mutual TLS) is the two-way ID check at the door. Normal TLS only checks the server's badge, like a website showing its padlock. Mutual TLS means both parties show a badge: the server presents its certificate and demands the client present one too. No valid client certificate, no connection. It secures the road, not the cargo.
  • Shield Platform Encryption is the locked safe itself. Even after the data arrives and sits in the Salesforce database, the sensitive fields stay scrambled. The keys are controlled by your org, so the contents are unreadable at rest, even to someone with raw database access.

Two independent layers. mTLS answers "can I trust who I am talking to?" Shield answers "if someone steals the disk, can they read it?" Serious regulated integrations use both.


2. The mechanisms and when each applies

Mechanism Protects Direction / scope Reach for it when
Inbound mTLS Data in transit External client → Salesforce. SF is the server and demands a client cert An external system calls the Salesforce API and you must prove the caller's identity by certificate
Outbound mTLS Data in transit Salesforce → external. SF is the client and presents its own cert An Apex callout hits a partner endpoint that requires a client certificate to let you in
Shield Platform Encryption Data at rest Inside the Salesforce database, field and file level You must encrypt sensitive fields with org-controlled keys and meet compliance like HIPAA, PCI, or GDPR
Classic encrypted text field Data at rest (weakly) A single masked text field You only need a masked input with no real key management. Not a compliance control

The 10-second answer: mTLS = mutual certificate check on the connection. Shield = strong, key-managed encryption of the stored data. Inbound mTLS = SF checks the caller's cert. Outbound mTLS = SF sends its own cert.


3. How mTLS works: inbound vs outbound

The crucial mental model is who acts as the server and who must present a client certificate.

flowchart TB subgraph IN["INBOUND mTLS - Salesforce is the SERVER"] EXT[External client app] -->|"presents CLIENT cert<br/>on port 8443"| SFS[Salesforce endpoint] SFS -->|"validates cert chains to a<br/>trusted root CA"| VER{Cert valid<br/>and trusted?} VER -->|yes| OK1[Allow API request] VER -->|no| DENY1[Reject handshake] end subgraph OUT["OUTBOUND mTLS - Salesforce is the CLIENT"] APX[Apex callout] -->|"via Named Credential"| NC[Named Credential<br/>references stored cert] NC -->|"presents SF CLIENT cert<br/>plus private key"| PART[Partner endpoint] PART -->|validates SF cert| OK2[Allow callout] end style SFS fill:#00A1E0,color:#fff style VER fill:#ff9900,color:#fff style NC fill:#4CAF50,color:#fff style PART fill:#9C27B0,color:#fff style DENY1 fill:#F44336,color:#fff

Inbound mTLS walkthrough. An external system connects to your org's API endpoint on port 8443. That endpoint sends a client certificate request during the TLS handshake. The client presents its certificate chain. Salesforce runs standard chain validation: the cert must chain up to a root CA that Salesforce trusts. Identity is actually enforced only when the API user has the Enforce SSL/TLS Mutual Authentication user permission. Here Salesforce is the server, checking the caller's badge.

Outbound mTLS walkthrough. Your Apex makes a callout. The partner endpoint demands a client certificate. Salesforce, now acting as the client, presents a certificate and private key stored in Certificate and Key Management, referenced through a Named Credential. The Named Credential is the clean, modern way to attach the cert so the secret never appears in code. See Module 05 Named Credentials.

Hyperforce note. On Hyperforce, outbound IP addresses are dynamic because the infrastructure runs on public cloud. Salesforce therefore recommends mTLS over IP allowlists. Pinning a partner firewall to Salesforce IP ranges is brittle when those ranges shift. A client certificate proves identity regardless of the source IP, so it is the durable control.


4. Shield Platform Encryption

Shield encrypts sensitive data at rest using keys your org manages. Unlike classic encrypted text fields, it is a real key-management and compliance control that preserves most platform functionality.

Probabilistic vs deterministic

Scheme What it does Filtering / SOQL Trade-off
Probabilistic Same input yields a different ciphertext each time Cannot be used in filters, WHERE clauses, SOQL/SOSL functions Strongest privacy, least usable
Deterministic Same input yields the same ciphertext, so values are comparable Can filter in reports, list views, and exact-match SOQL Slightly weaker, far more usable

Deterministic comes in case-sensitive and exact-match case-insensitive variants, chosen field by field. Use deterministic only on fields you must filter or match on, like an external ID. Otherwise prefer probabilistic.

Key management and BYOK

Shield uses a key derivation model. A tenant secret plus a master secret derive the data encryption key that actually encrypts your fields. With BYOK (Bring Your Own Key) you generate and control your own tenant secret on your premises, then upload it via the Platform Encryption REST API, and rotate it on your schedule. This means Salesforce never holds the full key material on its own, which is often a hard compliance requirement.

Integration impacts (the part that bites)

Encrypting a field is not free. On encrypted fields, several operations are restricted:

  • Probabilistic fields cannot appear in WHERE, ORDER BY, GROUP BY, or SOQL/SOSL functions.
  • Sorting and filtering in reports and list views work only with deterministic encryption.
  • Some features, certain formula references, external lookups, and matching during lead import behave differently or are unsupported on encrypted fields.
  • An integration that previously filtered on a now-encrypted field can silently return wrong results or error until you switch that field to deterministic or redesign the query.
flowchart LR DATA[Sensitive field value] --> ENC[Shield encryption<br/>at rest] ENC --> SCHEME{Which scheme?} SCHEME -->|Probabilistic| P[No filtering<br/>strongest privacy] SCHEME -->|Deterministic| D[Filter and exact match<br/>in reports and SOQL] KEY[Your tenant secret<br/>BYOK] -.controls.-> ENC style ENC fill:#00A1E0,color:#fff style SCHEME fill:#ff9900,color:#fff style P fill:#9C27B0,color:#fff style D fill:#4CAF50,color:#fff style KEY fill:#607D8B,color:#fff

Shield vs classic encrypted text fields

Classic Encrypted Text Shield Platform Encryption
Scope One custom text field Many standard/custom fields, files, attachments
Key management None you control Org-managed, rotatable, BYOK
Filtering Not filterable Deterministic option supports filtering
Visibility Masked in UI by permission Encrypted at rest, unreadable in storage
Compliance grade No Yes, designed for regulated data

5. Gotchas and interview traps

Gotcha Clarification
"TLS and mTLS are the same." Standard TLS checks only the server cert. mTLS also requires a valid client cert. Both ends prove identity.
"Inbound and outbound mTLS are configured the same way." No. Inbound: SF is the server validating the caller's cert. Outbound: SF is the client presenting its own cert via a Named Credential.
"Use IP allowlists on Hyperforce." Hyperforce IPs are dynamic. Salesforce recommends mTLS over IP allowlists for durable identity.
"Shield encrypts data in transit." No. Shield encrypts data at rest. TLS/mTLS handles transit. Different layers.
"Just turn on Shield, nothing changes." Encrypting a field can break filtering, sorting, and SOQL. Probabilistic blocks WHERE clauses. Plan deterministic for fields you query.
"Classic encrypted text fields are compliance-grade." They are a masked input, not key-managed encryption. Use Shield for real compliance.

6. Interview Q&A

Q: What is mTLS and how does it differ from normal TLS? A: Mutual TLS requires both sides to present certificates. Normal TLS validates only the server. With mTLS the server also demands a valid client certificate during the handshake, so each party cryptographically proves its identity before any data flows.

Q: Explain inbound vs outbound mTLS in Salesforce. A: Inbound, Salesforce is the server: an external client connects on port 8443, presents a client certificate, and Salesforce validates it chains to a trusted root CA, with the Enforce SSL/TLS Mutual Authentication permission enforcing it. Outbound, Salesforce is the client: during an Apex callout it presents its own client certificate and private key, stored in Certificate and Key Management and referenced by a Named Credential.

Q: On Hyperforce, why prefer mTLS over IP allowlisting? A: Hyperforce runs on public cloud with dynamic outbound IPs, so allowlists need constant maintenance and can break. A client certificate proves identity independent of source IP, so Salesforce recommends mTLS over IP allowlists.

Q: Probabilistic vs deterministic encryption in Shield? A: Probabilistic produces a different ciphertext each time, giving the strongest privacy but it cannot be filtered or used in SOQL WHERE clauses. Deterministic produces the same ciphertext for the same input, so values support filtering, exact-match SOQL, reports, and list views. Use deterministic only on fields you must query.

Q: What changes for integrations once a field is Shield-encrypted? A: Operations on the field can be restricted. Probabilistic fields cannot appear in WHERE, ORDER BY, GROUP BY, or SOQL functions, and sorting/filtering needs deterministic encryption. Existing queries that filter on a now-encrypted field can break, so you switch the field to deterministic or redesign the query, and you manage key rotation via BYOK.

Talking point to explain it to anyone: "mTLS is a two-way ID check at the door, both sides show a badge before anyone talks. Shield is a locked safe for the data once it is inside, and only your org holds the key, even if someone steals the disk they cannot read it."


7. Key terms

mTLS, mutual TLS, client certificate, certificate chain, root CA, port 8443, Enforce SSL/TLS Mutual Authentication, Named Credential, Certificate and Key Management, Hyperforce, Shield Platform Encryption, probabilistic encryption, deterministic encryption, tenant secret, data encryption key, BYOK, encryption at rest, classic encrypted text field - defined here and in the Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 05-governor-and-api-limits.md - the definitive limits reference for the whole module.

05 - Governor and API Limits

One-liner: The definitive limits reference for Salesforce integrations. Per-transaction governor limits, org-level daily API allocations, and the Bulk, Platform Event, and Async Apex ceilings you design around. Scope: Reference + monitoring + design. The numbers an interviewer expects you to recall cold. Use when: Sizing an integration, debugging a LimitException, or proving you can architect within Salesforce's multitenant guardrails.

This is the final file in Module 09. It consolidates the callout limits from Module 05 and adds the org-wide allocations. For event volume, see Module 06. Sibling: 04-mtls-and-shield-encryption.md.


1. The idea in plain English

Salesforce is a multitenant apartment building. Thousands of orgs share the same plumbing, so no single tenant may flood the pipes. The limits come in two flavors:

  • Governor limits are the per-visit rules. They reset on every transaction. How many callouts, how big the payload, how long it may run. Cross one and you get an instant runtime exception, not a slow response.
  • Allocations are the monthly utility caps. They are measured over a rolling 24 hours across the whole org. Daily API calls, Bulk records, platform event deliveries, async job runs. Burn through the day's budget and further requests are throttled or rejected until the window rolls.

A senior engineer designs so that neither ceiling is hit at peak. That means bulkifying, batching, going asynchronous, and monitoring usage before the org runs out of headroom on a busy Monday.


2. Callout governor limits (per transaction)

These match the outbound module exactly. Memorize them.

Limit Value Notes
Callouts per transaction 100 All HTTP and web service callouts combined in one Apex transaction. Bulkify, never loop.
Max request or response size (sync) 6 MB Synchronous transaction heap context.
Max request or response size (async) 12 MB Higher ceiling in async: Queueable, Batch, @future.
Timeout per callout 120 s Default is 10 s. Set with req.setTimeout(ms), max 120000.
Total callout time per transaction 120 s Cumulative across all callouts in the transaction.
Concurrent long-running requests 10 minimum, scales by license Synchronous requests running > 5 s count, org-wide. See note below.
No callout after uncommitted DML rule Pending DML in the same transaction blocks the callout.

Myth correction, read this twice. The callout request/response maximum is 6 MB synchronous and 12 MB asynchronous. It is NOT 10 MB. The "10 MB" figure is a persistent myth. The real ceiling depends on the transaction's heap context: 6 MB sync, 12 MB async.

flowchart LR A[Outbound callout] --> B{Sync or async?} B -->|Sync| C["6 MB payload<br/>counts toward concurrent<br/>pool if over 5s"] B -->|Async| D["12 MB payload<br/>does not count toward<br/>concurrent pool"] A --> E[100 callouts per txn] A --> F["120s per callout<br/>120s total per txn"] style C fill:#00A1E0,color:#fff style D fill:#4CAF50,color:#fff style E fill:#ff9900,color:#fff style F fill:#9C27B0,color:#fff

No callout after DML. Once a transaction issues DML and has not committed, a callout throws You have uncommitted work pending. Call out before any DML, or move the callout to async so it runs in its own transaction.

Concurrent long-running request limit. Any synchronous Apex request running longer than 5 seconds counts against an org-wide pool. The historical floor is 10, and Salesforce now scales it by license count (roughly one slot per 100 licenses), up to a maximum of 50. Asynchronous work, Batch, @future, scheduled, and Bulk API, does not count. This is exactly why slow UI callouts use a Continuation.


3. Org-level API request allocations (daily, rolling 24 h)

A single shared daily budget covers REST, SOAP, and Bulk API requests. It is calculated from your edition plus a per-user-license amount.

Edition Daily API request allocation
Enterprise 100,000 base + 1,000 per Salesforce license
Unlimited / Performance 100,000 base + 5,000 per Salesforce license
Developer Edition 15,000 per day (fixed)
Add-on Purchasable in increments (e.g. 1,000 to 10,000 blocks)

The total is shared across all integration users and tools. One runaway middleware job can starve every other integration. Each REST call typically costs 1 request. Bulk API is far more efficient per record because a whole batch or job counts as a small number of API calls, which is the entire reason it exists for high volume.

How to check usage.

  • REST resource: GET /services/data/v66.0/limits returns the maximum and remaining allocation for many limits, including DailyApiRequests. Values are accurate within about 5 minutes.
  • System Overview: Setup → System Overview shows API usage against the daily cap at a glance.
  • API Usage notifications: Setup lets you email an alert when usage crosses a percentage threshold of the daily limit.
flowchart TB REST[REST calls] --> POOL[Shared daily<br/>API request pool] SOAP[SOAP calls] --> POOL BULK[Bulk API jobs<br/>few calls per batch] --> POOL POOL --> CHK{Check headroom} CHK -->|"GET /services/data/v66.0/limits"| MON[DailyApiRequests<br/>max and remaining] CHK -->|Setup| SO[System Overview] style POOL fill:#00A1E0,color:#fff style CHK fill:#ff9900,color:#fff style MON fill:#4CAF50,color:#fff style SO fill:#607D8B,color:#fff

4. Bulk API limits

Bulk is the high-volume path. The two versions count differently.

Limit Bulk API 1.0 Bulk API 2.0
Daily ceiling 15,000 batches per rolling 24 h 100 million records per rolling 24 h
Job/batch model You create and manage batches yourself Salesforce chunks records into batches for you
Records per batch 10,000 records per batch Managed internally
Max file size per request 10 MB per batch (CSV) 150 MB per job (uploaded data)
API cost A batch counts as a small number of API calls, not one per record Counts toward daily API requests at the batch level

Note: Salesforce official docs state Bulk API 2.0's daily ingest limit as 100 million records per 24 hours, not 150 million. Use 100 million. The per-job data cap is 150 MB.

Why Bulk for integrations. Loading 1 million records via the REST or SOAP API one row at a time would obliterate the daily request pool. Bulk processes them asynchronously in batches, costing only a handful of API calls. Always reach for Bulk above roughly a few thousand records, and prefer Bulk API 2.0 for its simpler record-based model.


5. Platform Event and Async Apex allocations

Platform Events (and Change Data Capture)

Allocation What it governs
Daily event delivery Max event notifications delivered to subscribers (CometD/Pub-Sub, empApi, triggers) in 24 h. Set by edition/license, and shared across all CometD clients
Hourly publishing Max events published per hour, also edition-based
Add-on High-Volume Platform Events greatly expand both publish and delivery allocations

Delivery, not publishing, is usually the binding constraint, because one published event is delivered to every subscriber and each delivery counts. Monitor with the PlatformEventUsageMetric data and design subscribers to be few and efficient. See Module 06.

Async Apex executions

Limit Value
Async Apex executions per 24 h Greater of 250,000 or 200 x number of user licenses

This pool covers Batch Apex, Queueable, @future, and scheduled Apex combined. A 1,000-license org gets max(250000, 200000) = 250,000. A 2,000-license org gets max(250000, 400000) = 400,000. Chained Queueables and recursive batches can burn this fast, so a runaway chain is a real outage risk.

Quick reference Value
Callouts per transaction 100
Callout payload 6 MB sync / 12 MB async
Callout timeout 120 s per call, 120 s total
Enterprise daily API 100,000 + 1,000/license
Bulk API 1.0 15,000 batches / 24 h
Bulk API 2.0 100M records / 24 h, 150 MB/job
Async Apex max(250,000, 200 x licenses) / 24 h

6. Gotchas and interview traps

Gotcha Clarification
"Callout payload max is 10 MB." Wrong. It is 6 MB sync, 12 MB async. The 10 MB figure is a myth.
"Each governor limit is org-wide." No. Governor limits reset per transaction. Allocations (API, Bulk, events, async) are the org-wide, daily ones.
"REST and Bulk have separate daily pools." They draw from the same shared daily API request allocation. Bulk is just far cheaper per record.
"Async raises the callout count." No. Async raises the payload ceiling to 12 MB. The 100 callouts per transaction still applies.
"Batch Apex counts toward concurrent long-running requests." No. Only synchronous requests over 5 s count. Async is exempt.
"Publishing is the platform event bottleneck." Usually delivery is, since one event is delivered to every subscriber and each delivery counts.
"Buy more licenses to lift any limit." Only some scale with licenses (daily API, async Apex, concurrent requests). The 6 MB / 12 MB payload cap does not.

7. Interview Q&A

Q: What are the core callout limits? A: 100 callouts per transaction. Request/response max 6 MB synchronous, 12 MB asynchronous (not 10 MB). 120 s per callout and 120 s total per transaction, default per-callout timeout 10 s. Plus the org-wide concurrent long-running request limit and the no-callout-after-uncommitted-DML rule.

Q: How is the daily API request limit calculated, and how do you check it? A: It is a shared daily pool from edition plus per-license. Enterprise is 100,000 base + 1,000 per Salesforce license, covering REST, SOAP, and Bulk together. Check it with GET /services/data/v66.0/limits (look at DailyApiRequests max and remaining) or via System Overview, and set API Usage notifications for thresholds.

Q: Bulk API 1.0 vs 2.0 limits? A: 1.0: you manage batches, 15,000 batches per rolling 24 h, 10,000 records per batch. 2.0: Salesforce batches for you, 100 million records per rolling 24 h with up to 150 MB of data per job. Both are asynchronous and cost only a few API calls per batch, which is why you use them above a few thousand records.

Q: What is the async Apex daily limit and what counts against it? A: The greater of 250,000 or 200 x user licenses per 24 h, shared across Batch, Queueable, @future, and scheduled Apex. Chained Queueables and recursive batches can exhaust it, so bound your chains.

Q: Governor limit vs allocation, what is the difference? A: A governor limit resets per transaction and throws immediately when crossed, like 100 callouts or 6 MB payload. An allocation is an org-wide budget over a rolling 24 hours, like daily API requests or Bulk records, and throttles further requests once spent. You design for both at peak load.

Talking point to explain it to anyone: "Think of two kinds of limits. Per-trip rules that reset every transaction, like how many calls and how big each one can be. And a daily utility budget for the whole org, like total API calls and records moved per day. Good design never hits either at peak, and you watch the meter before it runs out."


8. Key terms

Governor limit, allocation, callout limit, transaction, heap, concurrent long-running request, uncommitted DML, daily API request limit, DailyApiRequests, /services/data/v66.0/limits, System Overview, Bulk API 1.0, Bulk API 2.0, batch, Platform Event delivery allocation, High-Volume Platform Events, async Apex, Queueable, Batch Apex - defined here and in the Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: back to the README.md for the full Module 09 map. That completes the security and limits track. From here, Module 10 (Tools & Middleware) covers MuleSoft, ETL, and integration platforms that orchestrate these calls, and Module 11 (Projects) puts the whole curriculum to work in end-to-end build scenarios. Those two are the natural continuation.

Module 10 - Tools & Middleware

Goal: Know your toolbox. Pick the right tool to test, explore, move data, and orchestrate. API version: v66.0 (Spring '26).

Two families: testing/dev tools (Postman, Workbench, the CLI, Data Loader, and smaller helpers) for building and debugging integrations, and middleware/iPaaS (MuleSoft and friends) for connecting many systems at scale.


Map of this module

# File What it covers
01 postman The universal API client + Salesforce collection
02 workbench Browser-based REST/SOQL/data/metadata tool
03 salesforce-cli The sf CLI for auth, data, deploy, scripting
04 data-loader Desktop bulk import/export (GUI + headless)
05 testing-helpers cURL, SOAP UI, Swagger, Pipedream, VS Code
06 middleware-and-ipaas MuleSoft API-led + the iPaaS landscape

Which tool? (decision tree)

flowchart TD Start([What do you need to do?]) --> Q{Task?} Q -->|Test or explore any API by hand| PM[Postman] Q -->|Quick SF data or metadata in a browser| WB[Workbench] Q -->|Automate, deploy, or script| CLI[Salesforce CLI sf] Q -->|Load a big CSV via GUI| DL[Data Loader] Q -->|Catch a webhook or fake an API| TH[Testing helpers] Q -->|Connect 3+ systems, transform, orchestrate| MW[Middleware / iPaaS] style PM fill:#00A1E0,color:#fff style WB fill:#ff9900,color:#fff style CLI fill:#9C27B0,color:#fff style DL fill:#4CAF50,color:#fff style TH fill:#607D8B,color:#fff style MW fill:#F44336,color:#fff

Tool comparison

Tool Type Best for Auth
Postman API client Testing/exploring any endpoint, OAuth flows OAuth
Workbench Web tool Quick SF REST/SOQL/data/metadata ops OAuth login
Salesforce CLI (sf) CLI Dev, CI/CD, deploys, scripted data Web or JWT
Data Loader Desktop app Large CSV import/export (uses Bulk API) OAuth / Bulk
Testing helpers Misc Webhooks, SOAP, OpenAPI, fake APIs varies
MuleSoft / iPaaS Middleware Many systems, transformation, orchestration platform

When do you actually need middleware?

flowchart TD Q1{How many systems?} -->|Just 2| DIRECT[Point-to-point is fine] Q1 -->|3 or more| Q2{Transformation or<br/>orchestration needed?} Q2 -->|No, simple and same format| DIRECT2[Point-to-point still OK] Q2 -->|Yes| MW[Use middleware<br/>MuleSoft, Boomi, Workato] Q1 -->|Many, complex flows| MW style DIRECT fill:#4CAF50,color:#fff style DIRECT2 fill:#4CAF50,color:#fff style MW fill:#F44336,color:#fff

Full reasoning in 06-middleware-and-ipaas.md and Module 01.


Interview rapid-fire

Q: Postman vs Workbench? → Postman is the general API client (great for OAuth and collections). Workbench is a Salesforce-specific web tool for quick REST/SOQL/data/metadata. Salesforce now steers people to Postman.

Q: How do you load 1M records as an admin, no code? → Data Loader (it uses the Bulk API), or sf data import bulk from the CLI.

Q: What authenticates the CLI in a CI/CD pipeline? → The JWT Bearer flow (sf org login jwt), no interactive login. See Module 03.

Q: When do you reach for MuleSoft? → When you connect many systems, need data transformation/orchestration, or want reusable API-led layers, not for a single point-to-point call.


Sources (Verified June 2026)

Each file has its own Sources section with the specific official doc.

01 - Postman

One-liner: The universal API client for exploring, testing, and demonstrating Salesforce APIs without writing code. Use when: You want to hit any Salesforce REST/Bulk/Composite endpoint by hand, prototype a call, or debug an integration. Bonus: Salesforce publishes an official Salesforce Platform APIs Postman collection (one of Postman's top-rated).

This is Module 10, the toolbox. For the APIs themselves, see Module 04. For the auth Postman performs, see Module 03.


1. The idea in plain English

Postman is a workbench for API calls. Instead of writing Apex or a script just to see what an endpoint returns, you fill in a URL, method, headers, and body in a UI, click Send, and read the response. It remembers your requests in collections, stores secrets and URLs in environments, and can run the OAuth dance for you so each call carries a fresh token.

For Salesforce specifically, you don't even start from scratch: you fork the official Salesforce Platform APIs collection, set a few environment variables, authenticate once, and every common request (query, create, composite, bulk) is ready to fire.


2. When to use it (and when not)

✅ Use it when ❌ Use something else when
Exploring or testing any Salesforce API by hand. You need quick SF-specific data/metadata ops → Workbench.
Prototyping a request before coding it in Apex. Scripting/automation in CI → Salesforce CLI.
Demonstrating an API to a teammate. Loading large CSVs → Data Loader.
Debugging OAuth flows and tokens. Building real integration logic → Apex / middleware.

3. How it works

sequenceDiagram autonumber participant PM as Postman participant SF as Salesforce<br/>/services/oauth2 + /services/data PM->>SF: OAuth request (e.g. web server / client credentials) SF-->>PM: access_token + instance_url PM->>PM: Store token + instance_url in environment PM->>SF: GET /services/data/v66.0/query?q=... (Bearer token) SF-->>PM: JSON response shown in the UI

Postman runs the OAuth flow, captures the access token and instance URL into environment variables, then sends API requests with Authorization: Bearer {{access_token}} against {{instance_url}}.


4. Setup steps

  1. Install Postman (desktop or web) and sign in.
  2. Fork the official "Salesforce Platform APIs" collection from the Salesforce Developers workspace on Postman.
  3. Create/select an environment and set variables: your My Domain login URL, the Connected App / External Client App consumer key and secret, and (per flow) a username.
  4. Choose an OAuth flow under the collection's Authorization tab. For a logged-in user use OAuth 2.0 (Authorization Code); for server-to-server use Client Credentials. Click Get New Access Token.
  5. Postman stores the token (and often the instance_url) as variables.
  6. Run a request, e.g.:
GET {{instance_url}}/services/data/v66.0/query?q=SELECT+Id,Name+FROM+Account+LIMIT+5
Authorization: Bearer {{access_token}}

Tip: Create a Connected App / External Client App with the right scopes (api refresh_token) and add Postman's callback (https://oauth.pstmn.io/v1/callback) as the callback URL for the Authorization Code flow.


5. Gotchas

Gotcha Fix
Token expired (401 INVALID_SESSION_ID) Click Get New Access Token again, or use a refresh token.
Wrong host Always use your My Domain / instance_url, not login.salesforce.com for data calls.
Callback mismatch Set the Connected App callback to https://oauth.pstmn.io/v1/callback.
Username-Password flow fails It's blocked/retiring. Use Authorization Code or Client Credentials. See Module 03.
Forgot the API version Pin v66.0 in the path.

6. Interview Q&A

Q: How do you test a Salesforce REST call without writing code? A: Postman. Fork the official Salesforce Platform APIs collection, set environment variables, run an OAuth flow to get a token, and send requests against the instance URL.

Q: Postman vs Workbench? A: Postman is the general-purpose API client great for OAuth, collections, and any endpoint. Workbench is a Salesforce-specific web tool with quick REST Explorer, SOQL, data, and metadata operations. Salesforce nudges people toward Postman now.

Q: How does Postman authenticate to Salesforce? A: Via OAuth. It runs a configured flow (Authorization Code, Client Credentials, etc.), stores the access token, and attaches it as a Bearer header. The Username-Password flow is avoided since it's retiring.

Q: Why use the official collection? A: It ships ready-made requests for query, CRUD, composite, and bulk with variables wired up, so you test in minutes instead of building requests by hand.

Talking point to explain it to anyone: "Postman is a workbench where you type an API call into a form, click send, and read the answer, no code required."


7. Key terms

Collection, environment, Bearer token, OAuth flow, instance URL - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 02-workbench.md - the browser-based Salesforce power tool.

02 - Workbench

One-liner: A free, browser-based Swiss Army knife for Salesforce-specific data and metadata operations, no install required. Use when: You want a quick, point-and-click way to run SOQL, fire a REST call, load a small CSV, or retrieve/deploy metadata in the browser. Note: Workbench is community-maintained (open source, BSD license, by Ryan Brainard) and is NOT a supported Salesforce product. Salesforce now nudges people toward Postman for API exploration.

This is Module 10, the toolbox. Coming from Postman? Workbench is the more Salesforce-aware cousin. For the APIs it wraps, see Module 04.


1. The idea in plain English

Workbench is a web dashboard that speaks fluent Salesforce. Postman is a generic API client where you build every request by hand. Workbench already knows your org: it logs in with OAuth, reads your schema, and gives you purpose-built screens for the common jobs. Pick "Insert," choose an object, upload a CSV, and go. Type SOQL and see results in a grid. Click "Retrieve" and pull down metadata as a zip.

Think of it as the mechanic's pit-stop tool. Not the prettiest, not officially backed, but it gets a Salesforce-specific job done in seconds without writing code or installing anything. The current build is Workbench 66.0.0, matching API v66.0 (Spring '26).


2. When to use it (and when not)

Use it when Use something else when
Quick browser-based SF data or metadata op, nothing to install. You need OAuth collections, scripting, or non-SF APIs to Postman.
Ad hoc SOQL/SOSL and eyeballing results in a grid. Automation, CI/CD, or repeatable scripts to Salesforce CLI.
One-off CSV insert/update/upsert/delete of a few thousand rows. Large or scheduled CSV loads (millions of rows) to Data Loader.
Exploring describe/schema or testing the REST Explorer fast. Building real integration logic to Apex / middleware.
Retrieving or deploying a small metadata package by hand. Production-grade releases to CLI + source control.

Warning: Workbench's own About page says "DO NOT USE WITH PRODUCTION DATA" without care. Treat it as a power tool, not a production pipeline.


3. How it works

flowchart LR U[You in a browser] -->|OAuth login| WB[Workbench<br/>workbench.developerforce.com] WB -->|Partner / REST / Bulk APIs| SF[(Salesforce Org)] WB -->|Metadata API| SF WB -->|Apex / Streaming APIs| SF SF -->|Results, CSV, zip| WB WB -->|Grid, JSON, download| U style U fill:#607D8B,color:#fff style WB fill:#00A1E0,color:#fff style SF fill:#9C27B0,color:#fff

Workbench authenticates with OAuth against your My Domain, then proxies your clicks into the right Salesforce API: Partner for data CRUD, REST for the REST Explorer, Bulk for big loads, Metadata for retrieve/deploy, plus Apex and Streaming. You pick the environment (Production or Sandbox) and API version on the login screen.


4. Key capabilities and how to use them

Log in at https://workbench.developerforce.com/login.php. Choose environment, accept the agreement, and authorize via OAuth. Then use the top menu:

Menu What it does Typical use
queries > SOQL Query Build or type SOQL, run, view in a grid, export to CSV. Pull SELECT Id, Name FROM Account and inspect.
queries > SOSL Search Run a text search across objects. Find a record by free text.
data > Insert / Update / Upsert / Delete Map a CSV to fields and run DML; choose Bulk if large. Fix or load a few thousand rows.
data > Export Export query results to CSV. Quick data pull.
migration > Retrieve / Deploy Pull a metadata package as zip, or deploy a zip. Grab a profile or deploy a small change set.
info > Standard & Custom Objects describe an object: fields, types, relationships. Check API names and field metadata.
utilities > REST Explorer Send GET/POST/PATCH/DELETE to any /services/data resource. Hit GET /services/data/v66.0/limits.
utilities > Apex Execute Run anonymous Apex. Quick scripted fix.

Example data load (Insert): pick data > Insert, choose the object (e.g. Contact), select your CSV, click Next, map columns to fields, then Confirm Insert. Workbench returns a results table with successes and errors you can download.

Example REST Explorer call:

GET /services/data/v66.0/query?q=SELECT+Id,Name+FROM+Account+LIMIT+5

No headers to set. Workbench attaches the session for you and renders the JSON response inline.


5. Gotchas

Gotcha Fix
Not Salesforce-supported. Community/open-source tool. For backed workflows use Postman or CLI. Do not rely on it in production runbooks.
API version drift. Pick the version on login. The build is tied to v66.0. Set it explicitly in REST paths.
Large CSVs time out in the UI. Tick Process records asynchronously via Bulk API for big jobs, or use Data Loader.
Session inherits your user's permissions. You only see/do what your profile allows. No magic admin powers.
Login redirect to wrong env. Choose Production vs Sandbox correctly on the login screen. Sandbox uses test.salesforce.com.
Deploy of large metadata is clunky. Workbench is fine for tiny packages. Use CLI sf project deploy start for real releases.

6. Interview Q&A

Q: What is Workbench and who maintains it? A: A free, browser-based suite for interacting with a Salesforce org via the Partner, REST, Bulk, Metadata, Apex, and Streaming APIs. It is community-maintained open source under a BSD license, not an officially supported Salesforce product.

Q: Workbench vs Postman? A: Workbench is Salesforce-aware and click-driven. It knows your schema and gives ready-made screens for SOQL, data DML, describe, and metadata retrieve/deploy. Postman is a general API client where you build requests and manage OAuth yourself. Salesforce now nudges users toward Postman for API exploration.

Q: Workbench vs Data Loader for a CSV load? A: Workbench is great for a quick, one-off load of a few thousand rows in the browser. For large volumes (up to 150M records) or scheduled/automated loads, use Data Loader, which runs on Bulk API and supports a headless command-line mode.

Q: How does Workbench authenticate? A: OAuth against your My Domain. You pick Production or Sandbox and an API version on the login screen, then authorize. It acts with your user's permissions.

Q: Can you run anonymous Apex or test the REST API in Workbench? A: Yes. utilities > Apex Execute runs anonymous Apex, and utilities > REST Explorer sends raw REST calls to /services/data resources with the session attached for you.

Talking point to explain it to anyone: "Workbench is a website that already knows your Salesforce org, so you can run a query, fix some records, or grab metadata in a few clicks instead of writing code."


7. Key terms

Metadata API, Bulk API, REST Explorer, describe, SOQL/SOSL, upsert, My Domain - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 03-salesforce-cli.md - the sf command-line tool for automation and CI/CD.

03 - Salesforce CLI (sf)

One-liner: The official command-line tool for automating Salesforce: auth, queries, data loads, Apex, and deployments, all scriptable. Use when: Dev workflows, deployments, CI/CD pipelines, and any task you want to repeat or script instead of clicking. Note: The modern command is sf (the sfdx executable is legacy and being retired). Examples here pin API v66.0 (Spring '26) where versions appear.

This is Module 10, the toolbox. Postman and Workbench are click-driven. The CLI is where you automate. For the auth flows it uses, see Module 03.


1. The idea in plain English

The Salesforce CLI is a remote control for your org from the terminal. Anything you would do by clicking, like log in, run a query, load a CSV, execute Apex, or deploy code, you can type as a command. Because it is text, you can save it in a script and run it the same way every time, in your laptop or in a CI pipeline at 3am with no human watching.

Think of it as the keyboard shortcut for the whole platform. Slower to learn than clicking, but once you know it, you move faster and, crucially, repeatably. That repeatability is why every serious Salesforce DevOps setup runs on the CLI.


2. When to use it (and when not)

Use it when Use something else when
Deploying metadata, especially from source control. One-off manual REST call to Postman.
CI/CD pipelines (build, test, deploy) with JWT auth. Quick browser data fix or describe to Workbench.
Scripted or bulk data import/export. Admin loading a giant CSV with a GUI to Data Loader.
Running Apex tests or anonymous Apex in automation. Real-time app logic to Apex / middleware.
Spinning up scratch orgs for dev. Pure point-and-click admin work to Setup UI.

3. How it works

flowchart TD DEV[Developer or CI runner] -->|"sf commands"| CLI[Salesforce CLI<br/>sf executable] CLI -->|"sf org login web (browser)"| AUTH1[OAuth Web Server flow] CLI -->|"sf org login jwt (headless)"| AUTH2[OAuth JWT Bearer flow] AUTH1 --> TOK[Stored auth + alias] AUTH2 --> TOK CLI -->|"data / apex / project commands"| API[Salesforce APIs<br/>REST, Bulk, Metadata, Tooling] TOK --> API API --> ORG[(Salesforce Org)] style DEV fill:#607D8B,color:#fff style CLI fill:#00A1E0,color:#fff style AUTH1 fill:#4CAF50,color:#fff style AUTH2 fill:#ff9900,color:#fff style API fill:#9C27B0,color:#fff style ORG fill:#F44336,color:#fff

You authenticate once (interactively with web login, or headlessly with JWT), the CLI stores the auth under an alias, and every later command targets that org with --target-org (or -o). Under the hood the CLI calls the REST, Bulk, Metadata, and Tooling APIs for you.


4. Key commands

The command shape is sf <topic> <command> --flags. Topics include org, data, apex, and project.

Authentication

# Interactive: opens a browser, great for your laptop
sf org login web --alias DevHub --set-default-dev-hub

# Headless: JWT bearer flow for CI/CD (no browser)
sf org login jwt \
  --username ci-user@example.com \
  --jwt-key-file server.key \
  --client-id <CONNECTED_APP_CONSUMER_KEY> \
  --instance-url https://login.salesforce.com \
  --alias prod

Query and search data

# Run SOQL
sf data query --query "SELECT Id, Name FROM Account LIMIT 5" --target-org prod

# Run SOSL
sf data search --query "FIND {Acme} IN ALL FIELDS RETURNING Account(Id, Name)" -o prod

Bulk data import/export (Bulk API 2.0)

# Import a CSV into an object
sf data import bulk --sobject Contact --file contacts.csv --target-org prod --wait 10

# Export query results to a CSV
sf data export bulk --query "SELECT Id, Email FROM Lead" --output-file leads.csv -o prod --wait 10

# Update existing records from a CSV
sf data update bulk --sobject Account --file updates.csv -o prod --wait 10

# Resume / check a long-running bulk job
sf data resume --job-id <JOB_ID> -o prod

Use --wait <minutes> to block until the job finishes. Without it, the command returns a job ID you poll with sf data resume.

Run Apex

# Execute anonymous Apex from a file
sf apex run --file ./scripts/fix.apex --target-org prod

# Run Apex tests
sf apex run test --code-coverage --result-format human -o prod

Deploy and retrieve metadata

# Validate first (dry run, no changes applied)
sf project deploy start --manifest manifest/package.xml --dry-run -o prod

# Deploy for real, running specified tests
sf project deploy start --manifest manifest/package.xml \
  --test-level RunSpecifiedTests --tests MyTestClass -o prod

# Pull metadata down into the project
sf project retrieve start --manifest manifest/package.xml -o prod

CI/CD pattern: store the JWT private key and consumer key as secret variables, run sf org login jwt at the start of the pipeline, then sf project deploy start --dry-run on PRs and a real deploy on merge.


5. Gotchas

Gotcha Fix
Using sfdx muscle memory. sfdx is legacy. Use sf. Old force: commands map to new topics like project deploy start.
Browser login fails in CI. sf org login web needs a browser. Use sf org login jwt in pipelines.
Forgot which org a command hits. Set a default with --set-default, or always pass -o <alias>. Check with sf org list.
Bulk command returns before the job is done. Add --wait <minutes>, or poll with sf data resume --job-id.
Deploy blows up in production. Always --dry-run first and set an explicit --test-level.
API version mismatch. Set sourceApiVersion in sfdx-project.json or pass --api-version 66.0.
JWT login "user hasn't approved this app". Pre-authorize the connected app for the user, or set the app's policy to admin-approved.

6. Interview Q&A

Q: What is the Salesforce CLI and why sf not sfdx? A: It is the official command-line tool for automating org work: auth, data, Apex, and deployments. sf is the modern unified executable; sfdx is legacy and being retired. New commands use topics like org, data, apex, and project.

Q: How do you authenticate the CLI in a CI/CD pipeline? A: The JWT bearer flow with sf org login jwt. It needs a connected app with a digital certificate and uses a private key, so it is fully headless, no browser. Store the key and consumer key as secret CI variables. Interactive sf org login web is for local dev.

Q: How do you deploy metadata safely? A: sf project deploy start with --dry-run to validate first, then the real deploy with an explicit --test-level. In a pipeline you validate on the PR and deploy on merge.

Q: How do you load a large CSV with the CLI? A: sf data import bulk --sobject <Object> --file <csv>, which uses Bulk API 2.0. Add --wait to block, or poll with sf data resume. For export use sf data export bulk with a SOQL query.

Q: CLI vs Data Loader for data? A: The CLI is scriptable and ideal for CI and dev automation. Data Loader is a desktop GUI (with an optional headless batch mode) aimed at admins and very large or scheduled CSV loads. Both ultimately use Bulk API for big volumes.

Talking point to explain it to anyone: "The CLI lets you control Salesforce by typing commands, so you can save the steps in a script and have a robot run the exact same deployment every time."


7. Key terms

CLI, alias, JWT bearer flow, scratch org, Bulk API 2.0, manifest (package.xml), dry run/validation - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 04-data-loader.md - the desktop app for bulk CSV import and export.

04 - Data Loader

One-liner: The official desktop app for bulk import and export of Salesforce data via CSV, with a wizard GUI and an optional headless mode. Use when: Admins or engineers need to load, update, or export large CSV volumes (up to 150M records), or schedule recurring loads. Note: Runs on Bulk API under the hood for big jobs. Current guide is v67.0 (Summer '26); examples pin API paths to v66.0 to match this vault. Requires Java 17+.

This is Module 10, the toolbox. Workbench handles small browser loads and the Salesforce CLI handles scripted ones. Data Loader is the heavyweight for big CSVs and admins.


1. The idea in plain English

Data Loader is a forklift for spreadsheets. You have a big CSV of records and need to push them into Salesforce, or pull a big set out. The app gives you a step-by-step wizard: pick the operation, log in, choose the object, map your columns to fields, and run. When it finishes, it hands you two files: a success.csv and an error.csv, so you know exactly what loaded and what failed.

It is a desktop app (Windows and macOS), not a website, because it is built to chew through volume reliably. For one-off small fixes you would reach for Workbench; for millions of rows or a nightly scheduled load, Data Loader is the tool.


2. When to use it (and when not)

Use it when Use something else when
Loading or exporting large CSVs (tens of thousands to 150M rows). A quick browser query or tiny load to Workbench.
An admin wants a GUI wizard, no code. A developer scripting data in CI to Salesforce CLI.
Upsert against an external ID key. A single manual API call to Postman.
Scheduled/recurring loads via headless command-line mode. Real-time, record-by-record sync to Apex / middleware.
You need a clean success/error CSV audit trail. Live two-way integration to a platform event or API.

3. How it works

flowchart LR CSV[Your CSV file] --> DL[Data Loader<br/>desktop app + Java] MAP[Field mapping .sdl] --> DL DL -->|OAuth login| SF[(Salesforce Org)] DL -->|Bulk API for large jobs| SF SF --> OUT1[success.csv] SF --> OUT2[error.csv] OUT1 --> YOU[You review results] OUT2 --> YOU style CSV fill:#607D8B,color:#fff style DL fill:#00A1E0,color:#fff style MAP fill:#ff9900,color:#fff style SF fill:#9C27B0,color:#fff style OUT1 fill:#4CAF50,color:#fff style OUT2 fill:#F44336,color:#fff

You log in via OAuth, choose an operation (insert, update, upsert, delete, hard delete, or export), map CSV columns to fields, and run. For large volumes Data Loader switches to Bulk API, which processes records asynchronously in batches. Results come back as success and error CSVs.


4. Setup and usage

Install: Download from Setup > Data Loader in your org, or from the Salesforce Data Loader GitHub releases. It needs Java Runtime 17 or later. Supported on Windows 10/11 and recent macOS (Ventura, Sonoma, Sequoia).

GUI wizard steps (Insert example)

  1. Open Data Loader and click Insert.
  2. Log in via OAuth and confirm the environment.
  3. Choose the object (e.g. Account) and select your CSV.
  4. Map CSV columns to Salesforce fields (save the mapping as an .sdl file to reuse).
  5. Choose where to write the success and error CSVs.
  6. Click Finish. Watch the progress, then review the result files.

Operations available: Insert, Update, Upsert (match on an external ID), Delete, Hard Delete, and Export / Export All (includes archived/soft-deleted).

Settings worth knowing (Settings menu):

Setting What it controls
Use Bulk API Switches large jobs to async Bulk API. Default batch size becomes 2000 (vs 200 on the SOAP-based default).
Enable serial mode for Bulk API Processes batches one at a time to avoid lock contention.
Batch size Rows per batch. Smaller batches reduce row-lock errors.

Headless / command-line (batch) mode for scheduled loads:

  1. Create an encryption key, then encrypt your password with the bundled encrypt utility so no plaintext password sits in a file.
  2. Write a process-conf.xml describing the job: a <bean> per process with sfdc.endpoint, sfdc.username, the encrypted sfdc.password, the operation, the CSV path, and the field-mapping file.
  3. Run the process and schedule it (Windows Task Scheduler or cron):
# Windows (officially supported for batch mode)
process.bat "C:\dl\conf" myUpsertProcess

# encrypt a password for the config (run once)
encrypt.bat -e "MyPasswordAndToken" "C:\dl\key.txt"

Heads up: Salesforce officially supports command-line batch mode on Windows. The same Java entry point exists on macOS/Linux, but it is not officially supported there. For cross-platform automation, prefer the Salesforce CLI.


5. Gotchas

Gotcha Fix
Wrong Java version. Install JRE 17+. Older Java will not launch the current Data Loader.
Row-lock errors on big loads (UNABLE_TO_LOCK_ROW). Turn on serial mode, reduce batch size, or sort the CSV by parent ID.
Plaintext password in process-conf.xml. Always use the encrypt utility and reference the encrypted value plus key file.
Upsert duplicates or fails. Map the correct external ID field and ensure values are unique.
Date/number format mismatches. Match your locale settings, or set them in Data Loader's config. Use ISO dates.
Expecting real-time sync. Data Loader is batch, not live. For event-driven sync use platform events or middleware.
Batch-mode automation on Mac/Linux. Not officially supported. Use the Salesforce CLI for cross-platform scripted loads.

6. Interview Q&A

Q: What is Data Loader and what API does it use? A: A Salesforce desktop app for bulk CSV import and export. It supports insert, update, upsert, delete, hard delete, and export, and for large volumes it uses Bulk API to process records asynchronously in batches. It handles up to 150M records.

Q: GUI mode vs command-line mode? A: The GUI is a wizard for interactive loads. Command-line (batch) mode runs headless from a process-conf.xml config with an encrypted password, so you can schedule recurring loads. Batch mode is officially supported on Windows.

Q: How do you secure credentials in automated Data Loader runs? A: Use the bundled encrypt utility to create a key and encrypt the password, then reference the encrypted value and key file in process-conf.xml. Never store a plaintext password.

Q: Data Loader vs the Salesforce CLI for data loads? A: Data Loader is a GUI tool aimed at admins and very large or scheduled CSV jobs, with cross-platform batch support best on Windows. The CLI (sf data import bulk) is scriptable and cross-platform, ideal for developers and CI/CD. Both use Bulk API for volume.

Q: A 5-million-row insert keeps hitting row-lock errors. What do you do? A: Enable serial mode so batches run one at a time, reduce the batch size, and sort the file by parent record ID to avoid concurrent locks on the same parent. Confirm Bulk API is enabled.

Talking point to explain it to anyone: "Data Loader is a forklift for spreadsheets. Point it at a big CSV, tell it which fields go where, and it loads everything into Salesforce, then hands you a receipt of what worked and what didn't."


7. Key terms

Bulk API, batch size, serial mode, upsert, external ID, process-conf.xml, headless/batch mode - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 05-testing-helpers.md - utilities and fake services for practicing callouts.

05 - Testing Helpers

One-liner: The smaller command-line and web helpers that round out the toolbox for quick API tests, SOAP calls, OpenAPI specs, webhook capture, and day-to-day Salesforce development. Use when: You need a fast one-off check, a free public endpoint to call, a place to catch an Outbound Message, or a proper IDE to write Apex and LWC.

This is Module 10, the toolbox. The heavyweights are Postman, Workbench, the Salesforce CLI, and Data Loader. This file covers the supporting cast you reach for around them.


1. The idea in plain English

Postman is the main workbench, but a tradesperson carries more than one tool. Sometimes you want the fastest possible check, so you type one line in a terminal. Sometimes the thing you are testing speaks SOAP, so you need a SOAP-aware client. Sometimes you are publishing an API contract, so you need a spec editor. Sometimes you need a free, throwaway endpoint to receive a webhook or to practice a callout against. And every day you need a real code editor to build on the platform.

Think of these as the drawer of small tools next to the big bench. Each does one job well. Knowing which to grab is the skill.


2. What each one is for

Tool What it is Reach for it when
cURL Command-line HTTP client, on every Mac and Linux box. You want the fastest one-line REST test or a snippet to paste into a script or ticket.
SOAP UI Desktop client built for SOAP and WSDL (also does REST). You are testing the SOAP API, the Metadata API, or any WSDL-based service.
Swagger UI / SwaggerHub Editor and host for OpenAPI specs with a try-it-out UI. You author or browse an OpenAPI contract that will feed Salesforce External Services.
Pipedream / webhook.site / RequestBin Free, instant inbound endpoints that log whatever hits them. You need to catch an Outbound Message or webhook and inspect its payload.
Public test APIs Ready-made fake REST APIs on the internet. You want to practice an Apex callout without standing up your own service.
VS Code + Salesforce Extension Pack The IDE plus the official Salesforce dev tooling. You are writing Apex, LWC, or Aura and running deploys and debugs.

3. How they fit around a Salesforce integration

flowchart LR DEV[VS Code +<br/>Extension Pack] -->|write and deploy| SF[Salesforce] CURL[cURL] -->|quick REST test| SF SOAP[SOAP UI] -->|SOAP / WSDL test| SF SF -->|Outbound Message<br/>or webhook| CATCH[Pipedream /<br/>webhook.site] SWAG[Swagger UI /<br/>SwaggerHub] -->|OpenAPI spec| EXT[External Services] EXT --> SF APEX[Apex callout] -->|practice against| TESTAPI[Public test API] style DEV fill:#9C27B0,color:#fff style SF fill:#00A1E0,color:#fff style CATCH fill:#ff9900,color:#fff style SWAG fill:#4CAF50,color:#fff style EXT fill:#4CAF50,color:#fff style TESTAPI fill:#607D8B,color:#fff

4. Usage and setup notes

cURL ships with macOS and Linux, so there is nothing to install. A token call looks like this:

curl https://MyDomain.my.salesforce.com/services/data/v66.0/query \
  --get --data-urlencode "q=SELECT Id, Name FROM Account LIMIT 5" \
  -H "Authorization: Bearer ACCESS_TOKEN"

It is perfect for a quick check or for pasting a reproducible command into a bug report. For anything interactive or repeated, Postman is friendlier.

SOAP UI is the go-to when the contract is a WSDL. Point it at the Salesforce Enterprise or Partner WSDL, or the Metadata WSDL, and it generates request stubs for every operation. Salesforce REST work has largely moved to Postman, but SOAP and Metadata API testing still benefit from a SOAP-native client.

Swagger UI / SwaggerHub let you write an OpenAPI document (formerly called Swagger) and try the operations in a browser. This pairs directly with External Services: Salesforce imports an OpenAPI 2.0 or 3.0 spec, generates an invocable action per operation, and you can then call that service from Flow, Apex, and bots with no callout code. Authoring the spec in Swagger first gives you a clean contract to register.

Pipedream / webhook.site / RequestBin give you a public URL in seconds. Paste that URL into an Outbound Message endpoint or a webhook config, fire the event, and the tool shows you the exact headers and body Salesforce sent. This is the fastest way to see what an Outbound Message actually looks like on the wire before you build the real receiver.

Public test APIs are free fake REST services for practising callouts:

  • httpbin.org echoes your request back, so it is ideal for checking headers, methods, and auth.
  • jsonplaceholder.typicode.com serves fake posts, users, and comments for read and write practice.
  • reqres.in returns realistic user data and now uses a free x-api-key header on many endpoints, so register for a key if calls start returning 401.

Note: these are shared public services. Several now rate-limit or require a key, so do not lean on them for load tests or anything production-adjacent. For repeatable testing, stand up your own mock or use a captured-endpoint tool above.

VS Code + Salesforce Extension Pack is the standard dev environment. The pack bundles the tools that matter:

  • Apex language support: highlighting, IntelliSense, running tests.
  • Lightning Web Components and Aura tooling.
  • Org Browser: view and retrieve metadata from the org without editing package.xml by hand.
  • Apex Replay Debugger: replays execution from an Apex debug log, so you step through code after the fact instead of live. It works on unmanaged code in any org.

It runs on top of the Salesforce CLI, which does the actual authentication and deploys under the hood.


5. Gotchas

Gotcha Fix
cURL request needs URL-encoding for SOQL Use --data-urlencode so spaces and commas are escaped.
SOAP UI feels heavy for a simple REST call Use cURL or Postman. Keep SOAP UI for genuine SOAP and WSDL work.
Public test API returns 401 or 429 reqres.in now wants a free x-api-key. Others rate-limit. Get a key or self-host a mock.
Webhook receiver URL went stale webhook.site and RequestBin URLs expire. Generate a fresh one per test.
Outbound Message endpoint must be HTTPS The capture URL has to be HTTPS, and Salesforce expects a specific ACK response, so a plain receiver may keep retrying.
Apex Replay Debugger shows nothing You need a debug log at the right level first. It replays a log, it does not attach live.
Swagger spec rejected by External Services Salesforce supports OpenAPI 2.0 and 3.0 only, with size and operation limits. Trim the spec to the operations you need.

6. Interview Q&A

Q: How do you test a Salesforce REST call from the command line? A: cURL. Hit /services/data/v66.0/... with an Authorization: Bearer header and --data-urlencode for the SOQL. It is the fastest one-liner and pastes cleanly into scripts or tickets.

Q: You need to test a SOAP or Metadata API operation. What do you use? A: SOAP UI. Point it at the WSDL and it generates request stubs for every operation. REST work has mostly moved to Postman, but a SOAP-native client is still the cleanest way to drive WSDL services.

Q: How can you see exactly what an Outbound Message sends without building a receiver? A: Use a free capture endpoint like Pipedream, webhook.site, or RequestBin. Drop its HTTPS URL into the Outbound Message config, fire the event, and read the captured headers and body. Note Salesforce expects an ACK or it will retry.

Q: How do Swagger and External Services relate? A: You author an OpenAPI spec in Swagger or SwaggerHub, then register it in External Services. Salesforce turns each operation into an invocable action you can call from Flow, Apex, or bots, no callout code required. It supports OpenAPI 2.0 and 3.0.

Q: What does the Salesforce Extension Pack give you in VS Code? A: Apex language support and test running, LWC and Aura tooling, an Org Browser to retrieve metadata, and the Apex Replay Debugger that steps through a captured debug log. It sits on top of the Salesforce CLI.

Talking point to explain it to anyone: "These are the small tools in the drawer next to the big workbench. cURL for a quick poke, SOAP UI for the SOAP stuff, Swagger for the contract, a throwaway URL to catch what Salesforce sends, and VS Code to actually write the code."


7. Key terms

cURL, SOAP, WSDL, OpenAPI, Swagger, External Services, webhook, Outbound Message, Apex Replay Debugger, Org Browser - defined in Module 01 vocabulary and the README.


Sources (Verified June 2026)


Next: 06-middleware-and-ipaas.md - MuleSoft, API-led connectivity, and the iPaaS lineup.

06 - Middleware and iPaaS

One-liner: When systems multiply, you stop wiring them directly and put a hub in the middle. MuleSoft Anypoint Platform is Salesforce's own iPaaS, and API-led connectivity is the method it is famous for. Use when: You need to connect three or more systems, transform data between formats, orchestrate multi-step flows, or manage and reuse APIs centrally.

This is Module 10, the toolbox. For the concept of middleware, the switchboard analogy, and the ESB vs iPaaS history, read 01-Fundamentals/08-middleware-and-esb.md first. This file is the practitioner's view: the platforms, the layers, and when to pull the trigger.


1. The idea in plain English

A delivery company does not send every driver to negotiate with every supplier. It runs a central warehouse. Goods come in, get sorted and repackaged, and go out shaped for each customer. Nobody talks directly to everybody.

Middleware is that warehouse for data. An iPaaS (Integration Platform as a Service) is the modern, cloud-hosted version of it: you rent the warehouse instead of building one, and it comes stocked with ready-made connectors for common systems plus tools to manage the APIs you build.

MuleSoft is the leading iPaaS and is owned by Salesforce (acquired 2018), so it is the headline answer when an interviewer asks how Salesforce handles enterprise integration at scale.


2. The lineup

Platform Type Best fit
MuleSoft Anypoint Platform Full iPaaS plus API management Enterprise scale, API-led connectivity, the Salesforce-native choice.
Boomi Cloud iPaaS Broad connector library, popular mid-market integration.
Workato iPaaS with automation focus Business-led automation and app integration, strong recipe model.
Informatica Data integration and iPaaS Heavy-duty ETL, data quality, and large data movement.
Zapier / Make No-code automation Low-volume, simple app-to-app triggers. Not for enterprise throughput.

Plain version: MuleSoft, Boomi, Workato, and Informatica are the enterprise hubs. Zapier and Make are the light, no-code option for hooking up a couple of SaaS apps when volume is low.


3. MuleSoft Anypoint Platform - what to know

Anypoint Platform is a single place to design, build, deploy, secure, monitor, and reuse APIs and integrations. The pieces you should be able to name:

  • Anypoint Studio: the IDE where you build Mule applications and flows with hundreds of pre-built connectors.
  • Anypoint Exchange: the marketplace of reusable assets, connectors, templates, and your own published APIs.
  • CloudHub: the fully managed, cloud-hosted runtime where Mule apps run without you provisioning servers.
  • Runtime Manager and API Manager: deploy and operate apps, then apply security policies, throttling, and monitoring.

The lifecycle is a loop, not a line. You design a contract, build the implementation, deploy it to a runtime, secure it with policies, monitor it in production, and reuse the asset on the next project instead of rebuilding it.

flowchart LR D[Design<br/>API contract] --> B[Build<br/>in Studio] B --> DE[Deploy<br/>to CloudHub] DE --> SE[Secure<br/>policies in API Manager] SE --> M[Monitor<br/>Runtime Manager] M --> R[Reuse<br/>publish to Exchange] R --> D style D fill:#4CAF50,color:#fff style B fill:#9C27B0,color:#fff style DE fill:#ff9900,color:#fff style SE fill:#F44336,color:#fff style M fill:#607D8B,color:#fff style R fill:#00A1E0,color:#fff

4. API-led connectivity - the three-layer model

This is MuleSoft's signature methodology and a near-certain interview question. Instead of one-off point-to-point integrations, you build reusable, purposeful APIs in three layers and compose them. Data flows up from systems of record to the channel, and assets are reused rather than rebuilt.

Layer Job Example
System APIs Unlock a backend system of record. Shield it from change. Connect to Salesforce, SAP, or a database and expose its data cleanly.
Process APIs Combine and orchestrate data across System APIs into a business capability. A "Customer 360" API that stitches CRM, billing, and orders together.
Experience APIs Reshape that capability for a specific channel or device. A trimmed, mobile-friendly payload for the iOS app versus the web.
flowchart TD subgraph EXP[Experience layer] E1[Mobile API] E2[Web API] E3[Partner API] end subgraph PROC[Process layer] P1[Customer 360<br/>orchestrate + combine] end subgraph SYS[System layer] S1[Salesforce<br/>System API] S2[SAP / ERP<br/>System API] S3[Database<br/>System API] end E1 --> P1 E2 --> P1 E3 --> P1 P1 --> S1 P1 --> S2 P1 --> S3 S1 --> SF[Salesforce] S2 --> SAP[SAP / ERP] S3 --> DB[(Database)] style E1 fill:#4CAF50,color:#fff style E2 fill:#4CAF50,color:#fff style E3 fill:#4CAF50,color:#fff style P1 fill:#ff9900,color:#fff style S1 fill:#00A1E0,color:#fff style S2 fill:#00A1E0,color:#fff style S3 fill:#00A1E0,color:#fff style SF fill:#00A1E0,color:#fff style SAP fill:#9C27B0,color:#fff style DB fill:#607D8B,color:#fff

The win is reuse. Build the Salesforce System API once, and every Process and Experience API on top of it reuses that single connection. The next project does not re-integrate Salesforce from scratch. This modularity is exactly what point-to-point cannot give you.


5. When you need middleware

Middleware buys decoupling and reuse at the cost of another platform to run and pay for. Justify it with the shape of the problem, not because it sounds enterprise-grade. (Full point-to-point comparison lives in 01-Fundamentals/08-middleware-and-esb.md.)

Reach for middleware when... Point-to-point is fine when...
You connect three or more systems, not two. You have one or two simple links.
Data needs transformation between formats or shapes. Both sides already speak the same format.
You need orchestration across several steps or systems. It is a single request to a single endpoint.
You must throttle or buffer to protect a slow downstream. Volume is low and steady.
You are bridging protocols, e.g. SOAP to REST or HTTP to a queue. Both sides speak the same protocol.
You want central logging, security, and reuse of APIs. The link is one-off and unlikely to grow.

Rule of thumb: count the connections and ask whether you must transform or orchestrate. A single Apex callout to one REST API needs no middleware at all. Zapier or Make can handle a low-volume two-app hookup. MuleSoft earns its keep when the estate is many systems with real transformation, governance, and reuse needs.


6. Interview Q&A

Q: What is MuleSoft and how does it relate to Salesforce? A: MuleSoft makes the Anypoint Platform, the leading iPaaS, and is famous for API-led connectivity. Salesforce acquired it in 2018, so it is Salesforce's enterprise integration and API management offering. You can invoke MuleSoft APIs and flows directly from Salesforce.

Q: Explain API-led connectivity and its three layers. A: It is MuleSoft's method of building reusable, layered APIs instead of one-off integrations. System APIs unlock backends like Salesforce or SAP. Process APIs combine and orchestrate them into business capabilities. Experience APIs reshape that data for a specific channel. The payoff is reuse: build each System API once and compose on top of it.

Q: Name the main Anypoint Platform components. A: Anypoint Studio is the IDE for building Mule apps, Exchange is the marketplace of reusable assets and connectors, CloudHub is the managed cloud runtime, and Runtime Manager plus API Manager handle deployment, security policies, throttling, and monitoring.

Q: When would you NOT use middleware? A: When you have only one or two simple connections, both sides speak the same protocol and format, and there is no orchestration or throttling needed. A single Apex callout to one API does not need a hub. For a small low-volume app-to-app link, Zapier or Make is enough.

Q: iPaaS versus a no-code tool like Zapier? A: An iPaaS like MuleSoft, Boomi, or Workato is built for enterprise volume, transformation, orchestration, and API governance. Zapier and Make are no-code automation for low-volume, simple SaaS-to-SaaS triggers. They solve very different scales of problem.

Talking point to explain it to anyone: "Instead of every system talking directly to every other, you run one central warehouse. Goods come in, get repackaged, and go out shaped for each customer. MuleSoft is that warehouse, and API-led connectivity is how you organise the shelves into three tidy layers so you build each connection once."


7. Key terms

Middleware, iPaaS, ESB, MuleSoft, Anypoint Platform, API-led connectivity, System API, Process API, Experience API, CloudHub, Anypoint Studio, Anypoint Exchange, orchestration, throttling - defined in 02-core-vocabulary.md and the README.


Sources (Verified June 2026)


Next: README.md - back to the Module 10 toolbox index.

Module 11 - Hands-On Projects

Goal: Build 12 real integrations end-to-end. Stop reading, start shipping. API version: v66.0 (Spring '26). Each project is a standalone build guide you can finish in a Developer org.

This module turns the concepts from Modules 01-09 into working builds. Every project follows the same shape: business problem → architecture → auth → step-by-step build → test → gotchas → extension → interview angle. Use a free Developer Edition org and the tools from Module 10.


The 12 projects

# Project Pattern Tools
01 REST callout to a public API Request-Reply Apex + Named Credential
02 SOAP callout via WSDL2Apex Request-Reply WSDL2Apex
03 External system creates an Account (REST) Remote Call-In Postman + REST API
04 Account + Contact in one call (Composite) Remote Call-In Composite API
05 Custom inbound Apex REST endpoint Remote Call-In @RestResource
06 Test an Apex SOAP service in SOAP UI Remote Call-In Apex SOAP + SOAP UI
07 Bulk API 2.0 load of 100K records Batch Data Sync Bulk API + CLI/CSV
08 Outbound Message to a webhook Fire-and-Forget Flow + Outbound Message
09 External Services from an OpenAPI spec Request-Reply External Services + Flow
10 Platform Event published to an LWC Fire-and-Forget Platform Events + empApi
11 CDC listener via Pub/Sub API UI Update on Change CDC + Pub/Sub (gRPC)
12 OAuth 2.0 Client Credentials server-to-server Auth pattern Connected App / ECA

Suggested order

flowchart LR P1[1. REST callout] --> P3[3. External to SF REST] P3 --> P5[5. Apex REST] P5 --> P4[4. Composite] P4 --> P10[10. Platform Event + LWC] P10 --> P11[11. CDC + Pub/Sub] P11 --> P7[7. Bulk 100K] P7 --> P12[12. Client Credentials] P1 -.-> P2[2. SOAP callout] P5 -.-> P6[6. SOAP UI test] P3 -.-> P8[8. Outbound Message] P8 -.-> P9[9. External Services] style P1 fill:#4CAF50,color:#fff style P12 fill:#00A1E0,color:#fff style P11 fill:#ff9900,color:#fff

Solid line = core path. Dotted = related side quests.


Coverage map (project → pattern → concept module)

Projects Pattern Concept module
01, 02, 09 Request and Reply Module 02 + Module 05
03, 04, 05, 06 Remote Call-In Module 02 + Module 04
07 Batch Data Sync Module 02 + Module 07
08, 10 Fire-and-Forget Module 06
11 UI Update on Change Module 06
12 Auth Module 03

Outcome

Finish these and you have hands-on proof of every major Salesforce integration pattern: inbound, outbound, bulk, events, and auth. That is a resume-ready portfolio and an interview full of concrete stories.


Sources (Verified June 2026)

Each project has its own Sources section with the specific official docs.

Project 01 - Apex REST Callout to a Public API

Pattern: Request and Reply (Salesforce → External, synchronous). Tools: Apex HttpRequest + a Named Credential + a public test API. You will learn: the cleanest way to call out from Apex without hardcoding anything, and how to test it.

This is Module 11, hands-on builds. Each project follows the same shape: problem → architecture → auth → build → test → gotchas → extension. Concepts behind this one live in Module 05.


1. Business problem

Salesforce needs to fetch live data from an external web service (here, a public test API) and use the response in Apex.


2. Architecture

sequenceDiagram autonumber participant APX as Apex (Anonymous / class) participant NC as Named Credential participant API as Public API APX->>NC: send(req) endpoint callout:Test_API/users/2 NC->>API: HTTPS GET /users/2 API-->>NC: 200 OK + JSON NC-->>APX: HttpResponse (status + body) APX->>APX: Parse JSON, log result

3. Auth setup

The test API needs no credentials, but we still route through a Named Credential so the URL isn't hardcoded and the pattern matches real life.

  1. Setup → Named Credentials → New (External Credential + Named Credential, or a legacy Named Credential).
  2. Label/Name: Test_API.
  3. URL: https://reqres.in (or https://httpbin.org).
  4. Authentication: choose No Authentication (this public API is open). For an authenticated API you'd pick an External Credential with OAuth/API Key, see Module 03.
  5. Save. (No Remote Site Setting is needed because a Named Credential registers the host. If you ever call the raw URL instead, add a Remote Site Setting.)

4. Step-by-step build

Create an Apex class that calls the API and returns the parsed body.

public with sharing class PublicApiClient {
    public static String getUser(Integer userId) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint('callout:Test_API/api/users/' + userId);
        req.setMethod('GET');
        req.setHeader('Content-Type', 'application/json');
        req.setTimeout(120000);

        HttpResponse res = new Http().send(req);
        if (res.getStatusCode() == 200) {
            Map<String, Object> body =
                (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
            System.debug('User payload: ' + body.get('data'));
            return res.getBody();
        }
        throw new CalloutException('Unexpected status: ' + res.getStatusCode());
    }
}

5. Test

Run Anonymous Apex (Developer Console → Debug → Open Execute Anonymous, or sf apex run):

System.debug(PublicApiClient.getUser(2));

Open the debug log and confirm a 200 and the JSON body. Alternatively, test the public endpoint first in Postman to see the expected shape.


6. Common gotchas

Gotcha Fix
Unauthorized endpoint Host not registered. Use a Named Credential (callout:) or add a Remote Site Setting.
You have uncommitted work pending A DML ran before the callout. Call out first, or go async. See Module 05.
Public test API rate-limits or requires a key Swap to httpbin.org, or add the key via an External Credential.
Timeout Set setTimeout; if the API is slow, use async/Continuation.

7. Extension challenge

  • Move the call into a Queueable so it can run after DML from a trigger.
  • Add a HttpCalloutMock test class so the callout has unit-test coverage. See Module 05.
  • Switch the Named Credential to an API Key External Credential against an authenticated API.

Interview angle

This project proves you know the right way to call out: a Named Credential instead of a hardcoded URL/secret, status-code handling, and how callouts interact with DML and governor limits.


Sources (Verified June 2026)


Next: 02-soap-callout-wsdl2apex.md - call a SOAP service from generated Apex stubs.

Project 02 - SOAP Callout via WSDL2Apex

Pattern: Request and Reply (Salesforce → External, synchronous). Tools: Generate from WSDL (Setup → Apex Classes) + a generated stub class + a Named Credential + a public SOAP service. You will learn: how to turn a SOAP WSDL into typed Apex stubs, route the endpoint through a Named Credential, and invoke a real SOAP operation without hand-writing XML.

This is Module 11, hands-on builds. Same shape as Project 01: problem → architecture → auth → build → test → gotchas → extension. The concept behind this one lives in Module 05.


1. Business problem

A legacy partner only exposes a SOAP service (here, a public calculator WSDL) and Salesforce must call one of its operations and use the result in Apex.


2. Architecture

sequenceDiagram autonumber participant APX as Apex (Anonymous) participant STUB as Generated stub class participant NC as Named Credential participant SOAP as External SOAP service APX->>STUB: call Add(2, 3) STUB->>STUB: build SOAP envelope from WSDL types STUB->>NC: WebServiceCallout.invoke endpoint callout:Calc_API NC->>SOAP: HTTPS POST SOAP 1.1 request SOAP-->>NC: 200 OK + SOAP response XML NC-->>STUB: response parsed into Apex objects STUB-->>APX: return 5

3. Auth setup

The sample calculator is open, but we still route the SOAP endpoint through a Named Credential so the host is registered and the URL is not hardcoded.

  1. Setup → Named Credentials → New.
  2. Label/Name: Calc_API.
  3. URL: the SOAP service base, for example https://www.dneonprism.dev (or any public calculator/country-info SOAP host you choose).
  4. Authentication: No Authentication for an open service. For an authenticated SOAP partner, attach an External Credential, see Module 03.
  5. Save. A Named Credential registers the host, so no Remote Site Setting is needed.

4. Step-by-step build

Step 1 - Get the WSDL. Download the service's WSDL file to disk (for a classic calculator service the WSDL exposes operations like Add, Subtract, Multiply, Divide). Inspect it first to confirm it is SOAP 1.1, document/literal (see gotchas).

Step 2 - Generate the Apex stubs. Setup → Apex Classes → Generate from WSDL → choose the WSDL file → Parse WSDL. Salesforce derives an Apex class name from the WSDL namespace, which you can rename (for example CalculatorService). Click Generate Apex code. Salesforce creates stub and type classes that mirror the SOAP operations.

Step 3 - Point the stub at the Named Credential. The generated stub exposes an endpoint_x property. Set it to the callout: reference so the endpoint and any auth come from the Named Credential instead of the hardcoded URL baked into the WSDL.

public with sharing class CalculatorClient {
    public static Integer add(Integer a, Integer b) {
        // The exact class/port names come from your generated WSDL2Apex code.
        CalculatorService.CalculatorSoap stub = new CalculatorService.CalculatorSoap();
        stub.endpoint_x = 'callout:Calc_API';   // route via Named Credential, never hardcode
        stub.timeout_x = 120000;                 // optional, ms

        // The stub builds the SOAP envelope, calls WebServiceCallout.invoke, parses the reply.
        Integer result = stub.Add(a, b);
        System.debug('SOAP Add result: ' + result);
        return result;
    }
}

The generated method internally calls WebServiceCallout.invoke, which performs the actual callout. You never touch the XML.


5. Test

Run Anonymous Apex (Developer Console → Debug → Open Execute Anonymous, or sf apex run):

System.debug(CalculatorClient.add(2, 3));   // expect 5

Open the debug log and confirm the returned value and a successful callout. To see the raw SOAP envelope a service expects, you can also send a request in SoapUI (point it at the WSDL, it builds a sample request for each operation) or Postman (raw XML body with Content-Type: text/xml and the correct SOAPAction header).

For unit-test coverage of the stub, implement the built-in WebServiceMock interface and register it with Test.setMock(WebServiceMock.class, new MyCalcMock()). The mock's doInvoke method returns a fake response so the test does not make a live callout. See Module 05.


6. Common gotchas

Gotcha Fix
Parse fails: unsupported WSDL constructs Apex does not support SOAP 1.2, rpc/encoded, multiple portType/binding/service, or imported external schemas. Trim the WSDL to one SOAP 1.1 document/literal binding, or hand-build the envelope with HttpRequest.
Generated class exceeds size limit If the parsed classes exceed the 1,000,000-character Apex class limit, generation fails. Split the WSDL or remove unused operations.
Endpoint hardcoded from WSDL Always set stub.endpoint_x = 'callout:NamedCredential' so the host is registered and secrets stay out of code.
Service changes its contract The stubs are a snapshot. Re-download the WSDL and regenerate; the old stub will not pick up new operations. Re-apply your endpoint_x wiring after regenerating.
SOAP fault returned The stub throws a CalloutException (or a generated fault type). Wrap the call in try/catch and read the fault detail.
Test fails with "callout not allowed" Tests cannot make live callouts. Use WebServiceMock + Test.setMock.

7. Extension challenge

  • Add a WebServiceMock implementation and a test class that asserts add(2, 3) == 5 with a mocked SOAP response.
  • Wrap the stub call in a Queueable so it can run after DML from a trigger (SOAP callouts obey the same uncommitted-work rule as REST).
  • Swap the calculator for a country-info WSDL and surface a country capital on a record page.

Interview angle

This proves you can integrate a legacy SOAP partner the supported way: generate typed stubs with WSDL2Apex, recognize which WSDL constructs Apex rejects (SOAP 1.2, rpc/encoded, multiple bindings, schema imports), route the endpoint through a Named Credential via endpoint_x, and cover the callout with WebServiceMock. Bonus signal: knowing the stub is a snapshot that must be regenerated when the contract changes.


Sources (Verified June 2026)


Next: 03-external-creates-account-rest.md - an external system creates an Account via the standard REST API.

Project 03 - External System Creates an Account via REST API

Pattern: Remote Call-In (External → Salesforce, synchronous). Tools: Postman + a Connected App / External Client App (OAuth) + the standard REST API. You will learn: how an outside system authenticates to Salesforce and inserts a record with a single POST, plus how to make the insert idempotent.

This is Module 11, hands-on builds. Same shape as Project 01: problem → architecture → auth → build → test → gotchas → extension. The concept behind this one lives in Module 04.


1. Business problem

An external system needs to create an Account in Salesforce without any custom Apex, using only the platform's standard REST API.


2. Architecture

sequenceDiagram autonumber participant EXT as External system (Postman) participant OAUTH as Salesforce OAuth token endpoint participant API as Salesforce REST API participant DB as Salesforce database EXT->>OAUTH: POST /services/oauth2/token (client credentials) OAUTH-->>EXT: access_token + instance_url EXT->>API: POST /services/data/v66.0/sobjects/Account (Bearer token + JSON) API->>DB: insert Account (validation, FLS, sharing) DB-->>API: new record Id API-->>EXT: 201 Created + id, success, errors

3. Auth setup

Use the OAuth 2.0 client credentials flow (server-to-server, no user prompt). It needs an app definition and a designated integration ("run-as") user.

  1. Setup → App Manager → New External Client App (or New Connected App).
  2. Enable OAuth Settings. Set a callback URL (any valid value, for example https://login.salesforce.com/services/oauth2/callback) and add the api scope (Manage user data via APIs).
  3. Enable the Client Credentials Flow and select a low-privilege integration user to run as. This flow returns tokens on behalf of that user, so its profile/permission set governs what the API can do.
  4. Save, then copy the Consumer Key and Consumer Secret.
  5. In Postman, POST to https://login.salesforce.com/services/oauth2/token (use test.salesforce.com for a sandbox) with x-www-form-urlencoded body:
grant_type=client_credentials
client_id=<Consumer Key>
client_secret=<Consumer Secret>

The response gives an access_token and an instance_url. The client credentials flow does not issue a refresh token. Never hardcode the secret in code; in Salesforce-to-elsewhere callouts you would store it in a callout:NamedCredential.


4. Step-by-step build

Step 1 - Set Postman variables. Save access_token and instance_url from the token response (a Postman test script can capture them automatically).

Step 2 - Build the create request.

  • Method: POST
  • URL: {{instance_url}}/services/data/v66.0/sobjects/Account
  • Headers: Authorization: Bearer {{access_token}} and Content-Type: application/json
  • Body (raw JSON):
{
  "Name": "Acme Inc",
  "Phone": "1234567890",
  "Industry": "Technology"
}

Step 3 - Send. A successful insert returns HTTP 201 Created:

{
  "id": "001XXXXXXXXXXXXAAA",
  "success": true,
  "errors": []
}

Step 4 (recommended) - Make it idempotent with upsert. A plain POST creates a new Account every time it runs, so retries make duplicates. If the Account has an External Id field (for example External_Account_Id__c), switch to PATCH against the external-id resource:

  • Method: PATCH
  • URL: {{instance_url}}/services/data/v66.0/sobjects/Account/External_Account_Id__c/A-1001
  • Body: the same JSON (omit the external-id field from the body).

Salesforce creates the record if the external id is new (201) or updates the existing one if it matches (200/204). This is the standard way to avoid duplicates on retries.


5. Test

  1. In Postman, run the token request, then the POST. Confirm 201 and copy the returned id.
  2. Verify in Salesforce: open the Account, or run a quick query in Workbench / Developer Console: SELECT Id, Name, Industry FROM Account WHERE Name = 'Acme Inc'.
  3. Re-send the same POST and watch a duplicate appear, then repeat with the PATCH upsert and confirm only one record exists. That contrast is the lesson.

6. Common gotchas

Gotcha Fix
400 with REQUIRED_FIELD_MISSING Account's only system-required field is Name, but admins often add required fields or validation rules. Include every required field in the body.
403 INSUFFICIENT_ACCESS / FIELD not writeable The integration (run-as) user lacks object/field permission or FLS. Grant create on Account and edit on each field via a permission set.
401 INVALID_SESSION_ID Token missing, malformed, or expired. Re-run the token request (client credentials has no refresh token, just fetch a new one).
Duplicate Accounts on retry A POST is not idempotent. Use PATCH upsert by External Id so retries update instead of insert.
Wrong API version or host Use /services/data/**v66.0**/... and the instance_url from the token response, not login.salesforce.com.
Duplicate rules block insert Org duplicate rules can return a DUPLICATES_DETECTED error. Adjust the rule or include the Sforce-Duplicate-Rule-Header to allowSave.

7. Extension challenge

  • Add a child Contact in a second call, then collapse both into one request in Project 04 with the Composite API.
  • Add an External Id field and prove idempotency by hammering the upsert in a Postman collection runner.
  • Lock the integration user down to least privilege (only the objects/fields it needs) and confirm the call still succeeds.

Interview angle

This shows you can stand up a Remote Call-In with zero custom code: pick the right OAuth flow (client credentials for server-to-server, with a designated run-as user), hit the standard /sobjects REST resource, read a 201 + id, and know that the integration user's profile/FLS is the real access boundary. The senior signal is choosing upsert by External Id for idempotency so retries never create duplicates.


Sources (Verified June 2026)


Next: 04-composite-account-contact.md - create an Account and a child Contact in one Composite API call.

Project 04 - Account + Contact in One Composite API Call

Pattern: Remote Call-In (External → Salesforce, synchronous). Tools: Postman + OAuth access token + the Composite API. You will learn: how to chain dependent inserts in a single round trip, reference a record created earlier in the same request, and make the whole thing transactional.

This is Module 11, hands-on builds. Same shape as Project 01: problem → architecture → auth → build → test → gotchas → extension. The concept behind this one lives in Module 04.


1. Business problem

An external system must create an Account and its child Contact together, in one network call, so they either both succeed or both roll back.


2. Architecture

sequenceDiagram autonumber participant EXT as External system (Postman) participant API as Composite API endpoint participant DB as Salesforce database EXT->>API: POST /services/data/v66.0/composite (2 subrequests, allOrNone true) API->>DB: subrequest 1 - insert Account (refAccount) DB-->>API: new Account Id API->>DB: subrequest 2 - insert Contact with AccountId @{refAccount.id} DB-->>API: new Contact Id API-->>EXT: 200 OK + compositeResponse (per-subrequest results)

3. Auth setup

Reuse the OAuth setup from Project 03: a Connected App / External Client App with the client credentials flow and a designated integration user. In Postman, fetch the token (POST {{login}}/services/oauth2/token, grant_type=client_credentials) and store the access_token and instance_url. The secret stays out of code; for Salesforce-to-elsewhere callouts you would use a callout:NamedCredential.


4. Step-by-step build

Step 1 - Build the request.

  • Method: POST
  • URL: {{instance_url}}/services/data/v66.0/composite
  • Headers: Authorization: Bearer {{access_token}} and Content-Type: application/json

Step 2 - Body. Each entry in compositeRequest has a method, a relative url, a unique referenceId, and a body. The Contact subrequest references the Account created above with the @{referenceId.id} syntax. allOrNone: true makes it one transaction.

{
  "allOrNone": true,
  "compositeRequest": [
    {
      "method": "POST",
      "url": "/services/data/v66.0/sobjects/Account",
      "referenceId": "refAccount",
      "body": { "Name": "Acme Inc", "Industry": "Technology" }
    },
    {
      "method": "POST",
      "url": "/services/data/v66.0/sobjects/Contact",
      "referenceId": "refContact",
      "body": {
        "LastName": "Doe",
        "FirstName": "Jane",
        "AccountId": "@{refAccount.id}"
      }
    }
  ]
}

Step 3 - Send. Subrequests run sequentially in order, so refAccount exists before the Contact references it. The response wraps each subrequest's result and HTTP status:

{
  "compositeResponse": [
    {
      "body": { "id": "001XXXXXXXXXXXXAAA", "success": true, "errors": [] },
      "httpStatusCode": 201,
      "referenceId": "refAccount"
    },
    {
      "body": { "id": "003XXXXXXXXXXXXAAA", "success": true, "errors": [] },
      "httpStatusCode": 201,
      "referenceId": "refContact"
    }
  ]
}

Alternative - sObject Tree. For purely parent-with-children inserts you can instead POST to /services/data/v66.0/composite/tree/Account with nested records and local referenceIds. sObject Tree is insert-only and nests children inside the parent; the general Composite resource is more flexible (mixed methods, cross-references between any subrequests), which is why it is the primary path here.


5. Test

  1. In Postman, run the token request, then the composite POST. Expect the top-level 200 with a compositeResponse array of two 201 results, each carrying an id.
  2. Verify the link in Workbench or Developer Console: SELECT Id, LastName, AccountId, Account.Name FROM Contact WHERE Account.Name = 'Acme Inc' and confirm AccountId matches the Account's id.
  3. Test rollback: set the Contact's LastName to an empty string (required field) and resend with allOrNone: true. The whole request rolls back and no Account is created. Flip allOrNone to false and observe the Account persisting while the Contact fails.

6. Common gotchas

Gotcha Fix
allOrNone misunderstood true = any failure rolls back the whole request. false = independent subrequests still commit and only dependent ones are skipped. Choose deliberately.
Reference syntax wrong It is @{referenceId.id} with the exact referenceId you assigned and a dotted field. A typo yields a "cannot resolve reference" error.
Order matters Subrequests run top to bottom. The Account must come before the Contact that references it.
referenceId not unique Each referenceId must be unique within the request, or references become ambiguous.
Too many subrequests Limit is 25 subrequests per composite call. For more, or for multiple independent transactional groups, use Composite Graph.
Missing required fields / FLS Each subrequest still enforces validation rules, required fields, and the integration user's FLS. A 400/403 on one subrequest can roll back the rest when allOrNone is true.

7. Extension challenge

  • Add a third subrequest that creates an Opportunity linked to the same Account via @{refAccount.id}, and confirm all three commit together.
  • Switch the Account insert to an upsert by External Id subrequest (PATCH .../sobjects/Account/External_Account_Id__c/A-1001) so reruns do not duplicate the parent.
  • Rebuild the same parent-children insert with Composite Graph and compare the response shape and limits.

Interview angle

This demonstrates the senior move of collapsing chatty, dependent calls into one transactional round trip: the @{refId.id} reference passes a freshly created Id between subrequests, allOrNone controls all-or-nothing commit, and you respect the 25-subrequest ceiling (escalating to Composite Graph beyond it). Knowing when sObject Tree suffices versus when you need full Composite flexibility is the differentiator.


Sources (Verified June 2026)


Next: 05-apex-rest-custom-inbound.md - expose a custom inbound endpoint with Apex REST.

Project 05 - Custom Apex REST Inbound Endpoint

Pattern: Remote Call-In (External → Salesforce, synchronous). Tools: Apex @RestResource + RestContext + an OAuth token + Postman. You will learn: how to publish your own REST endpoint under /services/apexrest/, secure it correctly, and call it from outside.

This is Module 11, hands-on builds. Each project follows the same shape: problem to architecture to auth to build to test to gotchas to extension. Concepts behind this one live in Apex REST.


1. Business problem

An external order system needs to create and read Cases in Salesforce using a shape you define, not the generic sObject REST API. You want one clean URL, a custom request body, and a custom response, with all the validation living in Apex.


2. Architecture

sequenceDiagram autonumber participant EXT as External System participant OAuth as Salesforce Token Endpoint participant REST as Apex REST class CaseService participant DB as Case object EXT->>OAuth: POST /services/oauth2/token OAuth-->>EXT: access_token + instance_url EXT->>REST: POST /services/apexrest/Cases with Bearer token REST->>DB: insert Case with sharing + FLS check DB-->>REST: new Case Id REST-->>EXT: 200 OK + JSON CaseId

3. Auth setup

Apex REST requires an authenticated session. The cleanest route for a server-to-server caller is an OAuth 2.0 access token from a Connected App.

  1. Setup to App Manager to New Connected App (or New External Client App). Enable OAuth Settings.
  2. Add the OAuth scopes Manage user data via APIs (api) and Perform requests at any time (refresh_token, offline_access).
  3. Save, then copy the Consumer Key and Consumer Secret.
  4. In Postman, POST to https://MyDomain.my.salesforce.com/services/oauth2/token with grant_type, client_id, client_secret, and (for a user flow) username + password+securitytoken. Read more in Module 03.
  5. The response gives you access_token and instance_url. Send the token as Authorization: Bearer <access_token> on every call.

4. Step-by-step build

1. Write the Apex REST class. The class is annotated with @RestResource and the urlMapping registers the path. One method per HTTP verb. Use RestContext to read the request and shape the response.

@RestResource(urlMapping='/Cases/*')
global with sharing class CaseService {

    @HttpPost
    global static Map<String, Object> createCase(String subject, String description) {
        Case c = new Case(Subject = subject, Description = description, Origin = 'API');
        // Apex REST runs in system context, so enforce FLS yourself.
        SObjectAccessDecision d = Security.stripInaccessible(AccessType.CREATABLE,
            new List<Case>{ c });
        insert d.getRecords();
        Case inserted = (Case) d.getRecords()[0];
        return new Map<String, Object>{ 'caseId' => inserted.Id, 'status' => 'created' };
    }

    @HttpGet
    global static Case getCase() {
        // Read the Id from the trailing URL segment via RestContext.
        RestRequest req = RestContext.request;
        String caseId = req.requestURI.substring(req.requestURI.lastIndexOf('/') + 1);
        return [SELECT Id, CaseNumber, Subject, Status FROM Case WHERE Id = :caseId WITH USER_MODE];
    }
}

2. Deploy it. Use sf project deploy start or the Developer Console. The endpoint is live the moment the class saves.

3. Call POST from Postman. Method POST, URL {{instance_url}}/services/apexrest/Cases, header Authorization: Bearer {{access_token}} and Content-Type: application/json, body:

{ "subject": "Login fails on mobile", "description": "User cannot log in from the app." }

4. Call GET from Postman. Method GET, URL {{instance_url}}/services/apexrest/Cases/<caseId>, same Bearer header. The trailing segment is parsed by RestContext.


5. Test

  • In Postman, the POST should return 200 OK with { "caseId": "500...", "status": "created" }.
  • Confirm the record in Salesforce (Service to Cases) or via the GET call.
  • Write an Apex unit test that builds a RestRequest, assigns it to RestContext.request, and invokes CaseService.createCase(...) directly, then asserts the Case exists. Apex REST methods are plain static methods, so they unit-test like any other.

6. Common gotchas

Gotcha Fix
Two @HttpPost methods in one class Not allowed. One method per HTTP verb per class. Split into separate resources.
Endpoint returns data the caller should not see Apex REST runs in system context. Add with sharing and enforce FLS with stripInaccessible or WITH USER_MODE.
401 Unauthorized / INVALID_SESSION_ID Missing or expired token. Re-run the OAuth token request and resend the Bearer header.
404 on the URL Wrong path. It is /services/apexrest + your urlMapping, case-sensitive.
Class will not save Apex REST classes must be global (the class and the methods).

7. Extension challenge

  • Add an @HttpPatch method to update Case Status, and an @HttpDelete to close it.
  • Return a custom error body with the right status by setting RestContext.response.statusCode = 400 on bad input.
  • Accept the raw JSON body via RestContext.request.requestBody and JSON.deserialize into a typed wrapper instead of named parameters.

Interview angle

This proves you can build inbound APIs the Salesforce way: a @RestResource with urlMapping, the one-method-per-verb rule, reading requests through RestContext, and the security point that matters most. Apex REST runs in system context, so with sharing plus FLS enforcement is on you, not the platform.


Sources (Verified June 2026)


Next: 06-soapui-test-apex-soap.md - expose an Apex SOAP service and call it from SOAP UI.

Project 06 - Apex SOAP Web Service Tested in SOAP UI

Pattern: Remote Call-In (External to Salesforce, synchronous, SOAP). Tools: Apex webservice method + generated WSDL + SOAP UI + a session id. You will learn: how to expose an Apex method over SOAP, generate its contract, and exercise it from a classic SOAP client.

This is Module 11, hands-on builds. Each project follows the same shape: problem to architecture to auth to build to test to gotchas to extension. Concepts behind this one live in Apex SOAP Web Services.


1. Business problem

A legacy partner integration speaks SOAP only and needs a strongly typed contract (a WSDL) to generate its client stubs. You must publish a Salesforce operation that creates an Account and returns its Id, then prove it works from SOAP UI before handing the WSDL to the partner.


2. Architecture

sequenceDiagram autonumber participant UI as SOAP UI participant Login as Enterprise WSDL login participant SOAP as Apex SOAP service AccountWS participant DB as Account object UI->>Login: login(username, password) Login-->>UI: sessionId + serverUrl UI->>SOAP: createAccount(name) with SessionHeader SOAP->>DB: insert Account DB-->>SOAP: new Account Id SOAP-->>UI: Account Id in SOAP response

3. Auth setup

A SOAP call into Salesforce needs a session id. You get one by calling login() from the Enterprise WSDL (or Partner WSDL), then pass that id on the custom call.

  1. Setup to API to Generate Enterprise WSDL, then Generate and save the file.
  2. In SOAP UI, File to New SOAP Project, point it at the Enterprise WSDL.
  3. Run the login operation with your username and password + security token. Copy the returned sessionId and serverUrl from the response.
  4. The same session id authenticates the custom Apex service call. (You can also use an OAuth access_token as the session id.)

4. Step-by-step build

1. Write the Apex SOAP class. The class must be global, and each exposed method uses the webservice static keywords.

global with sharing class AccountWS {

    webservice static Id createAccount(String name) {
        Account a = new Account(Name = name);
        insert a;
        return a.Id;
    }

    webservice static Account getAccount(Id accountId) {
        return [SELECT Id, Name, AccountNumber FROM Account WHERE Id = :accountId];
    }
}

2. Deploy with sf project deploy start or the Developer Console.

3. Generate the class WSDL. Setup to Apex Classes, click AccountWS, then Generate WSDL. Save it (for example AccountWS.wsdl). This WSDL describes only your custom operations and points at /services/Soap/class/AccountWS.

4. Import the WSDL into SOAP UI. Right-click your existing project to Add WSDL and choose AccountWS.wsdl. SOAP UI generates a sample request for createAccount.

5. Set the endpoint and session. Make sure the request endpoint host matches the serverUrl instance from login. In the request XML, populate the SessionHeader with the session id:

<soapenv:Header>
   <urn:SessionHeader>
      <urn:sessionId>YOUR_SESSION_ID</urn:sessionId>
   </urn:SessionHeader>
</soapenv:Header>
<soapenv:Body>
   <urn:createAccount>
      <urn:name>Acme via SOAP UI</urn:name>
   </urn:createAccount>
</soapenv:Body>

5. Test

  • Submit the createAccount request in SOAP UI. The response body returns the new Account Id inside a <result> element.
  • Verify the record in Salesforce (Sales to Accounts), or call getAccount with that Id.
  • A INVALID_SESSION_ID fault means the session id is stale, re-run login and paste the fresh one.

6. Common gotchas

Gotcha Fix
Class will not compile Methods exposed over SOAP must use the webservice keyword, and the class must be global.
webservice on a non-static or inner detail webservice methods must be static; they cannot be in a trigger, and the keyword has placement rules.
INVALID_SESSION_ID fault No session, or it expired. Get a fresh sessionId from the Enterprise WSDL login call.
Request hits the wrong host Use the serverUrl instance returned by login as the endpoint, not login.salesforce.com.
Namespace mismatch in the XML The urn: prefix must match the target namespace in the generated class WSDL. Let SOAP UI build the request from the WSDL.

7. Extension challenge

  • Add a webservice static method that accepts a custom Apex wrapper class as a parameter and returns one, then see how it appears as a complex type in the WSDL.
  • Generate the Partner WSDL instead and compare how it handles sObjects generically.
  • Turn the SOAP UI request into an automated TestCase with an assertion on the returned Id, the start of a regression suite.

Interview angle

This shows you can still deliver the SOAP half of Remote Call-In: the global class plus webservice static rules, generating a class WSDL versus using the Enterprise WSDL, and the authentication detail interviewers probe, you need a session id (from login or OAuth) carried in the SessionHeader.


Sources (Verified June 2026)


Next: 07-bulk-api-load-100k.md - load 100k records with Bulk API 2.0.

Project 07 - Load 100k Records with Bulk API 2.0

Pattern: Batch Data Synchronization (External to Salesforce, asynchronous, high volume). Tools: Bulk API 2.0 ingest jobs via the Salesforce CLI or raw REST + a CSV. You will learn: the Bulk API 2.0 job lifecycle, how to load ~100,000 records two ways, and how to make it idempotent and reviewable.

This is Module 11, hands-on builds. Each project follows the same shape: problem to architecture to auth to build to test to gotchas to extension. Concepts behind this one live in Bulk API 2.0.


1. Business problem

A nightly export from an ERP produces ~100,000 Account rows that must land in Salesforce. Row-by-row REST calls would blow the API limits and take hours. Bulk API 2.0 is built for exactly this, you hand it a CSV and it processes the rows asynchronously in optimized batches.


2. Architecture

flowchart LR A["CSV ~100k rows"] --> B["Create job<br/>POST /jobs/ingest"] B --> C["Upload data<br/>PUT .../batches"] C --> D["Close job<br/>PATCH state UploadComplete"] D --> E["Salesforce processes<br/>InProgress"] E --> F["JobComplete"] F --> G["GET successfulResults"] F --> H["GET failedResults"] style A fill:#00A1E0,color:#fff,stroke:#0070D2 style F fill:#00A1E0,color:#fff,stroke:#0070D2 style H fill:#FFB75D,color:#032D60,stroke:#FE9339

3. Auth setup

Bulk API 2.0 is a REST resource, so it uses the same OAuth access token as any REST call. The CLI handles auth for you once the org is connected.

  1. CLI route: run sf org login web --alias myorg once. The CLI stores and refreshes the token.
  2. Raw REST route: get an access_token and instance_url from POST /services/oauth2/token (see Module 03). Send Authorization: Bearer <access_token> on every Bulk call.

4. Step-by-step build

CSV shape

A header row of API field names, then one record per line. For an upsert, include the External Id column.

Name,AccountNumber,Industry,ERP_Id__c
Acme Corp,A-1001,Technology,ERP-1001
Globex,A-1002,Manufacturing,ERP-1002

Route 1 - Salesforce CLI (simplest)

Insert with Bulk API 2.0 and wait for completion:

sf data import bulk --file accounts.csv --sobject Account --wait 10 --target-org myorg

For an idempotent upsert keyed on an External Id field, use the upsert command:

sf data upsert bulk --file accounts.csv --sobject Account --external-id ERP_Id__c --wait 10 --target-org myorg

The CLI creates the job, uploads the CSV, closes it, and polls for you. Drop --wait to return immediately and track the job later.

Route 2 - Raw REST (the lifecycle in full)

1. Create the job. POST {{instance_url}}/services/data/v66.0/jobs/ingest

{ "object": "Account", "operation": "upsert", "externalIdFieldName": "ERP_Id__c", "contentType": "CSV", "lineEnding": "LF" }

The response returns a job id and state: Open.

2. Upload the CSV. PUT {{instance_url}}/services/data/v66.0/jobs/ingest/<jobId>/batches with header Content-Type: text/csv and the raw CSV as the body.

3. Close the job. PATCH {{instance_url}}/services/data/v66.0/jobs/ingest/<jobId>

{ "state": "UploadComplete" }

Salesforce now moves the job through InProgress to JobComplete.

4. Poll for status. GET {{instance_url}}/services/data/v66.0/jobs/ingest/<jobId> until state is JobComplete (or Failed). Watch numberRecordsProcessed and numberRecordsFailed.

5. Pull the results. Both return CSV bodies:

  • GET .../jobs/ingest/<jobId>/successfulResults
  • GET .../jobs/ingest/<jobId>/failedResults returns each bad row with sf__Id and sf__Error.

5. Test and monitor

  • Start with a 10-row CSV before the full 100k file.
  • Watch progress live in Setup to Bulk Data Load Jobs, you see records processed, failed, and throughput per job.
  • After JobComplete, download failedResults and read the sf__Error column to fix data issues, then reload only those rows.
  • Re-run the same upsert file, record counts should not double, proving idempotency.

6. Common gotchas

Gotcha Fix
Rows silently fail or shift columns CSV formatting. Match the header to API field names, quote fields with commas, and set lineEnding (LF or CRLF) to match the file.
Re-running the load creates duplicates Use operation: upsert with externalIdFieldName so existing rows update instead of insert.
Job says complete but data looks wrong Always pull failedResults, sf__Error tells you exactly which rows and why.
Job size exceeds limits A single job allows up to 150 MB of data, split very large files into multiple jobs.
Hitting the daily ceiling Bulk API 2.0 allows up to 100 million records per rolling 24 hours, batch your loads accordingly.

7. Extension challenge

  • Script the full raw lifecycle in a shell or Apex Http client and add exponential-backoff polling.
  • Add a hard-delete ingest job (operation: hardDelete) to purge test data, note the permission it requires.
  • Compare wall-clock time of the CLI insert versus upsert on the same 100k file and reason about why upsert costs more.

Interview angle

This proves you reach for the right tool at volume: Bulk API 2.0 over row-by-row REST, and that you know the job lifecycle cold (create to upload to UploadComplete to poll to results). The senior signals are upsert by External Id for idempotency, always reviewing failedResults, and respecting the 150 MB per job and 100M records / 24h limits.


Sources (Verified June 2026)


Next: 08-outbound-message-pipedream.md - fire a declarative Outbound Message to an external listener.

Project 08 - Outbound Message to a Webhook (Pipedream)

Pattern: Fire and Forget (Salesforce → External, asynchronous, no reply consumed). Tools: a record-triggered Flow + an Outbound Message (SOAP XML) + a free webhook receiver (Pipedream or webhook.site). You will learn: how Salesforce pushes a SOAP notification to an external endpoint with zero Apex, why it needs an acknowledgment, and why this feature is legacy.

This is Module 11, hands-on builds. Each project follows the same shape: problem → architecture → setup → build → test → gotchas → extension. Concepts behind this one live in Module 06.


1. Business problem

When an Opportunity is marked Closed Won, an external billing system needs to know immediately so it can raise an invoice. We do not need a response back into Salesforce, just a reliable push of the record data. This is the classic fire-and-forget shape: notify and move on.

An Outbound Message fits because it is declarative, it sends the field values as a SOAP XML notification, and the platform retries automatically until the receiver acknowledges.


2. Architecture

flowchart LR REC[Opportunity record<br/>Closed Won] --> FLOW[Record-triggered Flow] FLOW --> OM[Outbound Message<br/>SOAP XML] OM -->|HTTPS POST| WH[Pipedream webhook] WH -->|Ack true| OM style REC fill:#00A1E0,stroke:#032D60,color:#fff style FLOW fill:#0070D2,stroke:#032D60,color:#fff style OM fill:#1B96FF,stroke:#032D60,color:#fff style WH fill:#16325C,stroke:#032D60,color:#fff

The Outbound Message delivers asynchronously in a separate queue. Salesforce expects the endpoint to reply with an acknowledgment so it knows delivery succeeded.


3. Setup

A. Create a free webhook receiver.

  1. Go to Pipedream (pipedream.com) and create a new Workflow with an HTTP / Webhook trigger. Copy the generated URL, for example https://eoxxxxxxxx.m.pipedream.net. (Or use webhook.site and copy its unique URL.)
  2. This URL must be HTTPS and publicly reachable. Salesforce only sends to valid SSL endpoints.

B. No Named Credential is required. The endpoint URL is set directly on the Outbound Message, and Salesforce signs the call with its own org certificate. The receiver can verify the sender via the OrganizationId in the payload.


4. Step-by-step build

Step 1 - Create the Outbound Message.

  1. Setup → quick find Outbound Messages → New Outbound Message.
  2. Object: Opportunity.
  3. Name: Notify_Billing_Closed_Won.
  4. Endpoint URL: paste the Pipedream URL.
  5. User to send as: pick an integration user (their field-level security governs what is sent).
  6. Fields to send: select Id, Name, Amount, StageName, CloseDate. Id and OrganizationId are always included.
  7. (Optional) tick Send Session ID if the receiver will call back into Salesforce.
  8. Save.

Step 2 - Build a record-triggered Flow to fire it.

Workflow Rules are no longer enhanced by Salesforce, so use Flow as the trigger.

  1. Setup → Flows → New Flow → Record-Triggered Flow.
  2. Object: Opportunity. Trigger: A record is updated.
  3. Entry conditions: StageName Equals Closed Won. Set Optimize for: Actions and Related Records.
  4. Add an Action element → search the action type Outbound Message → select Notify_Billing_Closed_Won.
  5. Save and Activate the Flow.

Step 3 - The SOAP payload that leaves the org.

Salesforce sends a SOAP notifications envelope. The receiver sees something like this:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <notifications xmlns="http://soap.sforce.com/2005/09/outbound">
      <OrganizationId>00Dxx0000001gPLEAY</OrganizationId>
      <ActionId>04kxx00000000A1AAI</ActionId>
      <SessionId xsi:nil="true"/>
      <EnterpriseUrl>https://yourorg.my.salesforce.com/services/Soap/c/66.0/00Dxx</EnterpriseUrl>
      <PartnerUrl>https://yourorg.my.salesforce.com/services/Soap/u/66.0/00Dxx</PartnerUrl>
      <Notification>
        <Id>006xx000004TmiQAAS</Id>
        <sObject xsi:type="sf:Opportunity" xmlns:sf="urn:sobject.enterprise.soap.sforce.com">
          <sf:Id>006xx000004TmiQAAS</sf:Id>
          <sf:Amount>50000.0</sf:Amount>
          <sf:CloseDate>2026-06-30</sf:CloseDate>
          <sf:Name>Acme - 500 Widgets</sf:Name>
          <sf:StageName>Closed Won</sf:StageName>
        </sObject>
      </Notification>
    </notifications>
  </soapenv:Body>
</soapenv:Envelope>

Step 4 - Acknowledge. The endpoint must reply with a SOAP body containing <Ack>true</Ack>. Pipedream returns HTTP 200 by default, but for a real listener you return:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Body>
    <notificationsResponse xmlns="http://soap.sforce.com/2005/09/outbound">
      <Ack>true</Ack>
    </notificationsResponse>
  </soapenv:Body>
</soapenv:Envelope>

5. Test

  1. Open an Opportunity and set Stage to Closed Won, then save.
  2. Switch to the Pipedream workflow and watch the inspector: a new event appears with the SOAP XML body within a few seconds.
  3. Confirm the field values match the record.
  4. To see retries, point the endpoint at a URL that returns a 500, change a record, then check Setup → Outbound Messages → View Message Delivery Status (the Outbound message delivery status page) to watch the queued/failed attempts.

6. Common gotchas

Gotcha Fix
Receiver gets the message but Salesforce keeps re-sending The endpoint did not return <Ack>true</Ack>. Salesforce retries with exponential back-off for up to 24 hours.
Endpoint never receives anything URL must be HTTPS with a valid SSL cert and publicly reachable. Check Outbound message delivery status for the error.
Tried to fire it from a Workflow Rule Workflow Rules are no longer enhanced, build the trigger in Flow instead.
Field missing from payload The send-as user lacks field-level read access, or the field was not selected on the Outbound Message.
Need loose coupling or many subscribers Outbound Messages are legacy and point-to-point. Use Platform Events (see Project 10) for the modern pub/sub approach.

7. Extension challenge

  • Replace the Outbound Message with a Platform Event published from the same Flow, then subscribe externally via the Pub/Sub API. Compare the developer experience.
  • Build a tiny SOAP listener (a Pipedream code step) that parses the XML and returns a proper <Ack>true</Ack> envelope so retries stop cleanly.
  • Add error handling: log every delivery failure from the delivery status page into a custom object for monitoring.

Interview angle

This project shows you know the declarative fire-and-forget option and its trade-offs: Outbound Messages need an acknowledgment, auto-retry for 24 hours, and send SOAP XML point-to-point. Crucially, you can say why you would not pick it today, it is legacy, Workflow Rules are frozen, and Platform Events give loose coupling, multiple subscribers, and replay.


Sources (Verified June 2026)


Next: 09-external-services-openapi.md - generate invocable actions from an OpenAPI 3.0 schema and use one in a Flow.

Project 09 - External Services from an OpenAPI 3.0 Schema

Pattern: Request and Reply (Salesforce → External, synchronous) done low-code. Tools: an External Service registered from an OpenAPI 3.0 schema + a Named Credential + Flow Builder. You will learn: how to turn a REST API into clickable invocable actions with no Apex, and use one inside a Flow.

This is Module 11, hands-on builds. Each project follows the same shape: problem → architecture → auth → build → test → gotchas → extension. Concepts behind this one live in Module 05.


1. Business problem

The team needs to call an external REST API (here, a simple number-to-words or quotes service) from automation, but the admins do not write Apex. External Services lets you register the API's OpenAPI specification once, and Salesforce auto-generates invocable actions you can drop straight into a Flow, all declaratively. Authentication and the endpoint host are handled by a Named Credential.


2. Architecture

flowchart LR SCHEMA[OpenAPI 3.0 schema<br/>SwaggerHub or pasted] --> ES[External Service<br/>registration] NC[Named Credential] --> ES ES --> ACT[Generated invocable actions] ACT --> FLOW[Flow Builder] FLOW -->|callout via NC| API[External REST API] API -->|JSON response| FLOW style SCHEMA fill:#00A1E0,stroke:#032D60,color:#fff style ES fill:#0070D2,stroke:#032D60,color:#fff style NC fill:#1B96FF,stroke:#032D60,color:#fff style ACT fill:#16325C,stroke:#032D60,color:#fff style FLOW fill:#0070D2,stroke:#032D60,color:#fff style API fill:#16325C,stroke:#032D60,color:#fff

The schema describes the operations, Salesforce converts each operation into an action, and the Named Credential supplies the base URL plus auth at run time.


3. Auth setup

External Services requires a Named Credential for the endpoint, you cannot point it at a raw URL.

  1. Setup → Named Credentials → New. Create an External Credential first if the API needs auth (OAuth, API Key); for an open API choose No Authentication. See Module 03.
  2. Label/Name: Numbers_API.
  3. URL: the API base host, for example https://api.example.com.
  4. Save. The host in your OpenAPI servers block should match this base so paths resolve cleanly.

4. Step-by-step build

Step 1 - Prepare an OpenAPI 3.0 schema.

Host it on SwaggerHub (and copy the public URL) or paste the JSON directly during registration. A minimal OpenAPI 3.0 spec:

{
  "openapi": "3.0.0",
  "info": { "title": "Numbers API", "version": "1.0.0" },
  "servers": [{ "url": "https://api.example.com" }],
  "paths": {
    "/math/{number}": {
      "get": {
        "operationId": "getMathFact",
        "parameters": [
          {
            "name": "number",
            "in": "path",
            "required": true,
            "schema": { "type": "integer" }
          }
        ],
        "responses": {
          "200": {
            "description": "A math fact",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "text": { "type": "string" },
                    "number": { "type": "integer" },
                    "found": { "type": "boolean" }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

The operationId (getMathFact) becomes the action name, so give every operation a clear, unique one.

Step 2 - Register the External Service.

  1. Setup → quick find External Services → New (or Add an External Service).
  2. Choose From API Specification.
  3. Service Name: NumbersService (this prefixes the generated action API names).
  4. Service Schema - Named Credential: select Numbers_API.
  5. Schema source: pick Service Schema Complete URL and paste the SwaggerHub URL, or pick Schema and paste the JSON above.
  6. Click Save & Next. Salesforce parses the spec and lists the operations it found.
  7. Review the operations and parameters, then Finish.

Step 3 - Confirm the generated actions.

After registration, each operation appears as an invocable action. In Flow Builder you will find it under Action → category External Services → NumbersService, with getMathFact available.

Step 4 - Use the action in a Flow.

  1. Setup → Flows → New Flow → Screen Flow (simplest to test) or an autolaunched flow.
  2. (Screen Flow) Add a Screen with a Number input variable inputNumber.
  3. Add an Action element → External Services → NumbersService → getMathFact.
  4. Map the input number = {!inputNumber}.
  5. Store the output, then add a second Screen that displays the returned text field from the action output.
  6. Save and Activate.

5. Test

  1. Run the Flow (Flow Builder → Run, or Debug with a sample input).
  2. Enter a number, for example 42, on the first screen.
  3. The Flow calls the API through the Named Credential and the second screen shows the returned fact text.
  4. If something fails, open Setup → Debug Logs (or the Flow debug panel) to see the raw request/response and confirm the callout used callout:Numbers_API.

6. Common gotchas

Gotcha Fix
Registration fails on a large spec The schema has a 100,000 character maximum. Trim unused paths/definitions or split the API.
Spec rejected as unsupported External Services supports OpenAPI 2.0 and 3.0. Validate the document and confirm the openapi/swagger version field.
Action name or parameter missing A derived object/property type name must be under 255 characters to surface in Apex or Flow, and each operation needs a unique operationId.
No Named Credential to choose External Services requires a Named Credential, create one first (Section 3).
API changed and the action is stale Re-register / regenerate the External Service from the updated schema, the actions do not auto-sync.
Callout blocked The Named Credential registers the host, so no Remote Site Setting is needed, confirm the servers URL matches the Named Credential base.

7. Extension challenge

  • Swap the open API for an authenticated one and wire up an OAuth 2.0 or API Key External Credential, no code changes needed in the Flow.
  • Call the same generated action from Apex (ExternalService.NumbersService.getMathFact(...)) to compare low-code vs pro-code invocation.
  • Add a POST operation with a request body to the schema, regenerate, and pass a structured Apex-defined object from the Flow.

Interview angle

This proves you can deliver an integration without Apex: register an OpenAPI 3.0 spec, let the platform generate invocable actions, and consume them in Flow. You can speak to the real constraints, the 100,000-character schema limit, OpenAPI 2.0/3.0 support, the mandatory Named Credential, and the need to regenerate when the contract changes, which is exactly what separates a low-code shortcut from a maintainable one.


Sources (Verified June 2026)


Next: 10-platform-event-lwc.md - publish a Platform Event from Apex and react to it live in an LWC.

Project 10 - Platform Event to a Live LWC

Pattern: Fire and Forget for the publisher, UI Update Based on Data Changes for the subscriber. Tools: a Platform Event (__e) + Apex EventBus.publish() + an LWC using the lightning/empApi module. You will learn: how to fire an event from Apex and update a UI in real time without polling.

This is Module 11, hands-on builds. Each project follows the same shape: problem → architecture → setup → build → test → gotchas → extension. Concepts behind this one live in Module 06 and the replay deep-dive.


1. Business problem

An agent dashboard should light up the moment a back-office process finishes, for example "Order ORD-1001 shipped", without the user refreshing. Polling the database every few seconds is wasteful and laggy.

Platform Events decouple the producer from the consumer: Apex publishes an event, and any number of subscribers, including an LWC on screen, receive it over the event bus in near real time.


2. Architecture

flowchart LR APX[Apex<br/>EventBus.publish] --> BUS[Event bus<br/>/event/Order_Notification__e] BUS --> EMP[lightning/empApi<br/>subscribe] EMP --> LWC[LWC updates UI] style APX fill:#00A1E0,stroke:#032D60,color:#fff style BUS fill:#1B96FF,stroke:#032D60,color:#fff style EMP fill:#0070D2,stroke:#032D60,color:#fff style LWC fill:#16325C,stroke:#032D60,color:#fff

EventBus.publish() writes to the bus, the channel name is /event/<EventName>__e, and the LWC subscribes to that channel through CometD via the empApi module.


3. Setup

No external system or Named Credential is needed, this is entirely inside the platform. You need:

  1. Permission to create a Platform Event (Customize Application).
  2. A Lightning page (App, Home, or Record page) where the LWC can be dropped.
  3. Desktop browser for testing, empApi does not run in the Salesforce mobile app (see gotchas).

4. Step-by-step build

Step 1 - Define the Platform Event.

  1. Setup → quick find Platform Events → New Platform Event.
  2. Label: Order Notification. The API name becomes Order_Notification__e (the __e suffix marks a Platform Event).
  3. Publish Behavior: Publish After Commit (fires only if the transaction commits) or Publish Immediately (fires regardless). Choose Publish Immediately so a failed DML still notifies the UI.
  4. Add custom fields: - Order_Number__c (Text) - Status__c (Text) - Message__c (Text)
  5. Save.

Step 2 - Publish from Apex.

public with sharing class OrderNotifier {
    public static void notifyShipped(String orderNumber) {
        Order_Notification__e evt = new Order_Notification__e(
            Order_Number__c = orderNumber,
            Status__c       = 'Shipped',
            Message__c      = 'Order ' + orderNumber + ' has shipped.'
        );

        Database.SaveResult sr = EventBus.publish(evt);

        if (!sr.isSuccess()) {
            for (Database.Error err : sr.getErrors()) {
                System.debug('Publish failed: ' + err.getMessage());
            }
        }
    }
}

EventBus.publish() returns a Database.SaveResult, always check isSuccess().

Step 3 - Build the LWC.

orderNotifier.html:

<template>
    <lightning-card title="Live Order Notifications" icon-name="standard:event">
        <div class="slds-p-around_medium">
            <template if:true={messages.length}>
                <ul class="slds-has-dividers_bottom">
                    <template for:each={messages} for:item="m">
                        <li key={m.id} class="slds-item">
                            <strong>{m.order}</strong> - {m.status}: {m.text}
                        </li>
                    </template>
                </ul>
            </template>
            <template if:false={messages.length}>
                <p>Waiting for events...</p>
            </template>
        </div>
    </lightning-card>
</template>

orderNotifier.js:

import { LightningElement } from 'lwc';
import { subscribe, unsubscribe, onError, isEmpEnabled } from 'lightning/empApi';

export default class OrderNotifier extends LightningElement {
    channelName = '/event/Order_Notification__e';
    subscription = {};
    messages = [];

    async connectedCallback() {
        // empApi runs only in desktop browsers
        const enabled = await isEmpEnabled();
        if (!enabled) {
            console.warn('EMP API is not available in this environment.');
            return;
        }

        onError((error) => {
            console.error('empApi error: ', JSON.stringify(error));
        });

        // -1 = replay only new events from now on
        this.subscription = await subscribe(this.channelName, -1, (response) => {
            const payload = response.data.payload;
            this.messages = [
                {
                    id: response.data.event.replayId,
                    order: payload.Order_Number__c,
                    status: payload.Status__c,
                    text: payload.Message__c
                },
                ...this.messages
            ];
        });
    }

    disconnectedCallback() {
        if (this.subscription && this.subscription.id) {
            unsubscribe(this.subscription, () => {
                this.subscription = {};
            });
        }
    }
}

orderNotifier.js-meta.xml:

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>66.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
        <target>lightning__RecordPage</target>
    </targets>
</LightningComponentBundle>

Step 4 - Place the component. Edit a Lightning App or Home page in the Lightning App Builder, drag the orderNotifier component onto it, then Save and Activate.


5. Test

  1. Open the Lightning page with the component, it shows "Waiting for events...".
  2. In another tab, open Developer Console → Debug → Open Execute Anonymous (or sf apex run) and run:
OrderNotifier.notifyShipped('ORD-1001');
  1. Within a second or two, the LWC list updates with ORD-1001 - Shipped with no page refresh.
  2. Publish a few more with different order numbers and watch them stack at the top.

6. Common gotchas

Gotcha Fix
Component never receives events empApi is supported only in desktop browsers with web/shared worker support, it does not run in the Salesforce mobile app. Test on desktop.
Subscribe fails silently Check the channel name is exactly /event/Order_Notification__e (leading /event/, __e suffix).
Component must be a top-level window You can use empApi only on the main window, not in a child/popup window.
Duplicate or missed processing Platform Events are at-least-once delivery, design subscribers to be idempotent.
Events fire even on rollback That is Publish Immediately by design. Use Publish After Commit if you only want events when the DML succeeds.
isEmpEnabled returns false in tests It is a runtime browser check, guard your subscribe call behind it (as shown) and skip gracefully.

7. Extension challenge

  • Subscribe with a replay ID of -2 to receive events from the last 72 hours on load, then study replay semantics in the replay note.
  • Publish the event from a record-triggered Flow instead of Apex (the Create Records element on a Platform Event), and confirm the same LWC reacts.
  • Add an Apex trigger on the Platform Event (Order_Notification__e) that also writes an audit record server-side when each event fires.

Interview angle

This is the modern, decoupled counterpart to Project 08: instead of a legacy SOAP push, you publish to the event bus with EventBus.publish() and subscribe in the UI with lightning/empApi. Be ready to name the constraints, desktop-only empApi, the /event/...__e channel, at-least-once delivery (so idempotent handlers), and Publish After Commit vs Immediately. That shows real-time UI fluency, not just CRUD.


Sources (Verified June 2026)


Next: 11-cdc-pubsub-listener.md - stream Change Data Capture events to an external Pub/Sub API listener.

Project 11 - Change Data Capture Listener via Pub/Sub API

Pattern: UI Update Based on Data Changes / external sync (Salesforce → External, near real-time, event-driven). Tools: Change Data Capture + the Pub/Sub API (gRPC + HTTP/2 + Avro) and a small external client. You will learn: how to enable CDC for an object and have an external service subscribe, decode the Avro payload, and track its place in the stream with a Replay ID.

This is Module 11, hands-on builds. Each project follows the same shape: problem → architecture → auth → build → test → gotchas → extension. Concepts behind this one live in Change Data Capture and Pub/Sub API.


1. Business problem

An external system (a cache, a search index, a data warehouse) must stay in sync with Salesforce Account records in near real-time, without polling. Every time an Account is created, updated, deleted, or undeleted, Salesforce should push the change to the external service. Change Data Capture fires the event for free, and the Pub/Sub API is the modern gRPC channel an external client uses to receive it.


2. Architecture

flowchart LR U[User edits Account] --> SF[Salesforce platform] SF -->|CDC fires| EB[Event Bus<br/>/data/AccountChangeEvent] EB -->|gRPC Subscribe| CL[External client<br/>Node.js or Python] CL -->|GetSchema once| EB CL -->|decode Avro| STORE[(External store)] style SF fill:#00A1E0,color:#fff style EB fill:#032D60,color:#fff style CL fill:#1B96FF,color:#fff style STORE fill:#747474,color:#fff

The client opens one long-lived gRPC stream, requests a batch of events, decodes each Avro payload, persists it, then requests the next batch.


3. Auth and setup

Enable CDC for Account:

  1. Setup → quick-find Change Data Capture.
  2. Move Account from Available Entities to Selected Entities.
  3. Save. Salesforce now publishes to the channel /data/AccountChangeEvent.

Get an access token (the client needs one before gRPC):

The Pub/Sub API authenticates with a standard Salesforce OAuth access token plus your org's instance URL and org ID, passed as gRPC metadata on every call. Any OAuth flow works; for a headless service the Client Credentials flow is the clean choice (see Project 12). You will send three headers: accesstoken, instanceurl, and tenantid (the org ID).


4. Step-by-step build

The Pub/Sub API endpoint is api.pubsub.salesforce.com:7443 (gRPC over HTTP/2). Grab the protobuf definition (pubsub_api.proto) from the official repo to generate your client stubs. The flow is the same in Node.js or Python; pseudo-steps below.

Step 1 - Authenticate and build gRPC metadata.

token, instanceUrl, orgId = oauthClientCredentials()   // see Project 12
metadata = {
  "accesstoken": token,
  "instanceurl": instanceUrl,
  "tenantid":    orgId
}

Step 2 - Open a secure gRPC channel.

channel = grpc.secureChannel("api.pubsub.salesforce.com:7443", sslCredentials())
stub    = PubSubStub(channel)

Step 3 - Fetch and cache the schema (call once).

GetSchema returns the Avro schema as a JSON string, keyed by schema_id. Because the schema rarely changes, call it once and reuse it.

topic     = stub.GetTopic({ topic_name: "/data/AccountChangeEvent" }, metadata)
schemaRes = stub.GetSchema({ schema_id: topic.schema_id }, metadata)
avroSchema = parse(schemaRes.schema_json)

Step 4 - Subscribe with a FetchRequest and a replay preset.

Subscribe is a bidirectional stream. The first FetchRequest names the topic, the replay preset, and num_requested (the flow-control batch size, max 100). Use LATEST to receive only new events from now on, EARLIEST to read from the start of the 3-day retention window, or CUSTOM with a stored replay_id to resume exactly where you left off.

firstRequest = {
  topic_name:    "/data/AccountChangeEvent",
  replay_preset: LATEST,        // or CUSTOM + replay_id to resume
  num_requested: 10
}
stream = stub.Subscribe(metadata)
stream.write(firstRequest)

Step 5 - Receive, decode Avro, and re-request to keep the stream flowing.

Each response carries a list of events. The event.payload is binary Avro decoded with the cached schema. Every CDC payload includes a ChangeEventHeader (entity name, change type, changed fields, record IDs). Save the replay_id so you can resume later. When the server has drained your requested batch, it sends an empty keep-alive whose latest_replay_id you store; then send another FetchRequest to ask for more.

for response in stream:
    for event in response.events:
        record = avroDecode(event.event.payload, avroSchema)
        header = record["ChangeEventHeader"]
        persist(header.changeType, header.recordIds, record)
        save(event.replay_id)          // for CUSTOM resume
    // top up the credit so delivery continues
    stream.write({ num_requested: 10 })

5. Test

  1. In the org, open any Account and change a field (for example, Phone), then Save.
  2. Watch your client log. Within a second or two it should print the decoded event: changeType = UPDATE, the Account's record ID, and the changed-fields list from the ChangeEventHeader.
  3. Create a new Account and delete one to see CREATE and DELETE change types arrive on the same stream.

No connected app on the publish side is needed; CDC publishes automatically. If nothing arrives, confirm Account is in Selected Entities and that your token's run-as user can see Account records.


6. Common gotchas

Gotcha Fix
Payload looks like garbage bytes It is binary Avro, not JSON. Decode event.payload with the schema from GetSchema.
Schema fetched on every event Call GetSchema once and cache it; the schema ID changes only when the object's fields change.
Stream goes quiet after the first batch This is flow control. You must send a new FetchRequest with num_requested to request more; num_requested max is 100.
Missed events after downtime Events live in the bus for 72 hours (3 days) only. Persist the replay_id and resubscribe with replay_preset: CUSTOM to backfill within that window.
replay_id reused after retention A stored Replay ID older than 72 hours is invalid; you will get an error. Fall back to EARLIEST or LATEST.
Need the changed fields, not the whole record CDC sends only changed fields plus the ChangeEventHeader; read header.changedFields to know what moved.
Auth errors on the gRPC call All three metadata headers are required: accesstoken, instanceurl, tenantid (org ID).

7. Extension challenge

  • Persist the replay_id to a datastore and restart the client with replay_preset: CUSTOM to prove zero-gap resume.
  • Subscribe to a custom channel that bundles multiple objects, or to a custom Platform Event, using the same code path.
  • Try Managed Event Subscriptions (Beta), where Salesforce tracks the Replay ID server-side so the client carries no replay state.
  • Push decoded events into a real sink (a search index or a message queue) instead of logging.

Interview angle

This proves you understand event-driven sync end-to-end: CDC publishes for free, the Pub/Sub API is the modern gRPC replacement for the CometD Streaming API, payloads are Avro (so you decode with a cached schema), and resilience comes from Replay ID plus the 72-hour retention window. Bonus points for explaining flow control (num_requested) as back-pressure that stops a client being overwhelmed during a publish spike.


Sources (Verified June 2026)


Next: 12-oauth-client-credentials.md - get a token for a backend integration with no user.

Project 12 - OAuth 2.0 Client Credentials Flow

Pattern: Server-to-server authentication (external backend → Salesforce, no user present). Tools: A Connected App (or External Client App) with the Client Credentials Flow enabled, a run-as integration user, and Postman/cURL. You will learn: how a headless backend trades only its client id and secret for an access token, with no login screen and no refresh token.

This is Module 11, hands-on builds. Each project follows the same shape: problem → architecture → auth → build → test → gotchas → extension. The concept behind this one lives in Client Credentials Flow.


1. Business problem

A backend service (a nightly job, a middleware connector, a microservice) needs to call the Salesforce API, but no human is logged in. You do not want to embed a user's password, and the retiring Username-Password flow is not an option. The OAuth 2.0 Client Credentials flow lets the service exchange its consumer key + consumer secret for an access token that always runs as one fixed integration user.


2. Architecture

sequenceDiagram autonumber participant APP as Backend service participant TOK as Token Endpoint participant API as Salesforce REST API APP->>TOK: POST /services/oauth2/token grant_type=client_credentials TOK-->>APP: access_token (runs as integration user) APP->>API: GET /services/data/v66.0/... Bearer access_token API-->>APP: 200 OK + JSON Note over APP,TOK: No user login, no refresh token. Re-request when it expires.

3. Auth and setup

Prerequisite: My Domain must be deployed. The Client Credentials flow only works against your https://MyDomainName.my.salesforce.com host, never the generic login URL.

Create and configure the Connected App:

  1. Setup → App Manager → New Connected App (choose the classic Connected App form, or use an External Client App).
  2. Enable OAuth Settings. Set any Callback URL (it is unused here but required), and add OAuth scopes such as Manage user data via APIs (api).
  3. Check Enable Client Credentials Flow and accept the security warning. Save.
  4. Reopen the app: Manage → Edit Policies. Under Client Credentials Flow, set Run As to a dedicated integration user (Salesforce recommends a user with API Only access). Save.
  5. From Manage Consumer Details, copy the Consumer Key and Consumer Secret.

The run-as user is who the token acts as, so its profile, permission sets, and sharing decide what the integration can read and write. Apply least privilege.


4. Step-by-step build

Step 1 - Request a token. POST to your My Domain token endpoint with grant_type=client_credentials. You can pass the key/secret either in the body or as HTTP Basic auth.

cURL, credentials in the body:

curl -X POST \
  https://MyDomainName.my.salesforce.com/services/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CONSUMER_KEY" \
  -d "client_secret=YOUR_CONSUMER_SECRET"

Step 2 - Read the response. You get back an access_token and the instance_url. Note there is no refresh_token in this flow.

{
  "access_token": "00D...!AQ...",
  "instance_url": "https://MyDomainName.my.salesforce.com",
  "token_type": "Bearer",
  "issued_at": "...",
  "signature": "..."
}

Step 3 - Call an API with the Bearer token. Use the instance_url from the response as the base, and API version v66.0.

curl https://MyDomainName.my.salesforce.com/services/data/v66.0/query?q=SELECT+Id,Name+FROM+Account+LIMIT+5 \
  -H "Authorization: Bearer 00D...!AQ..."

5. Test

Do it in Postman:

  1. POST https://MyDomainName.my.salesforce.com/services/oauth2/token. In Body → x-www-form-urlencoded add grant_type=client_credentials, client_id, client_secret. Send and confirm a 200 with an access_token. - Alternative: leave the body with only grant_type, and put the key/secret under Authorization → Basic Auth.
  2. Copy the access_token. New request: GET {{instance_url}}/services/data/v66.0/limits, set Authorization → Bearer Token, paste it, send. A 200 with org limits confirms the token works as the integration user.
  3. Save the token to a Postman variable so the query request reuses it.

6. Common gotchas

Gotcha Fix
invalid_client / flow rejected My Domain is required. POST to https://MyDomainName.my.salesforce.com, not login.salesforce.com.
invalid_grant after enabling the app You did not set a Run As user under Edit Policies → Client Credentials Flow. Assign the integration user.
Token works but API returns no data / INSUFFICIENT_ACCESS The run-as user lacks object/field perms or a suitable license. Grant via permission sets; consider an API Only integration user.
Looking for a refresh token There is none in client credentials. When the token expires, just request a new one.
Secret leaked Anyone with the key + secret can mint tokens. Rotate the consumer secret and store it in a secret manager, never in code.
Over-privileged integration Apply least privilege to the run-as user; do not reuse an admin.

7. Extension challenge

  • Wrap step 1 in your backend so it caches the token and only re-requests on a 401/expiry, since there is no refresh token.
  • Lock the run-as user down to a single object with a permission set and prove the API can only touch what that user can see.
  • Move the same credentials into a Salesforce Named Credential + External Credential (Client Credentials type) so Apex callouts reuse them, see Named Credentials.

Interview angle

This proves you can authenticate a headless server-to-server integration the modern way: Client Credentials replaces the retiring Username-Password flow, requires My Domain, runs as a fixed integration user (so security is the user's permissions, not the app's), and issues no refresh token (re-request on expiry). The certificate-based alternative is the JWT Bearer flow, which signs the request with a private key instead of sending a secret, also issuing no refresh token, and is the better fit when you cannot safely store a shared secret.


Sources (Verified June 2026)


Next: README.md - back to the Module 11 project index.

Module 12 - Cheatsheets (Quick Reference)

Goal: Single-page references you keep open during real work and skim right before an interview. API version: v66.0 (Spring '26). When you're stuck mid-task, find the answer in 10 seconds, not 10 minutes.

These condense Modules 01-11 into dense, scannable cards. Each links back to its full module for the deep dive.


The cheat sheets

Card What it gives you
oauth-flows Every OAuth flow, grant type, tokens, decision tree
patterns The 6 integration patterns in one table + picker
api-picker Which API for which use case (12 APIs compared)
event-comparison Platform Events vs CDC vs Outbound vs Streaming vs Pub/Sub
callout-limits Every callout and async governor limit
status-codes HTTP status codes Salesforce returns + causes
error-codes Common exceptions and API errors with fixes
named-credentials-syntax Copy-paste Named Credential / callout snippets
postman-setup One-time Postman + Salesforce OAuth setup
debug-checklist "My callout failed, now what?" triage flow

The numbers worth memorizing

Thing Value
Callouts per transaction 100
Callout request/response 6 MB sync, 12 MB async
Callout timeout 120 s max
Event retention (high-volume PE + CDC) 72 hours
Composite subrequests 25
sObject Collections records 200
Bulk API 2.0 100M records/24h, 150 MB/job
@future / Queueable per transaction 50
Batch Apex concurrent jobs 5

How to use

Skim the relevant card before you build or before an interview round. For the "why" behind any line, follow the link to the full module. Start anywhere, these are reference, not a sequence.


These cards summarize official Salesforce documentation. Each card cites its specific source.

API Picker One-Pager (Spring '26)

Which Salesforce API for which job, by purpose, sync vs async, format, and volume. Full detail in ../08-Modern-APIs/04-modern-api-landscape.md.


The full comparison table

API Purpose Sync / Async Format Volume When to use
REST API General CRUD + SOQL, the workhorse Sync JSON / XML 1 record per call Default for most app integrations
SOAP API Legacy, strongly typed, WSDL contract Sync XML 1-200 per call Old / enterprise clients needing a WSDL
Bulk API 2.0 Mass load / extract, job-based Async CSV / JSON Thousands to millions Large data loads you can poll
GraphQL Pick exact fields across related objects Sync JSON Read-shaped, paged Avoid over / under-fetching. Dashboards
Composite Chain several different ops in one trip Sync JSON Up to 25 subrequests Dependent calls, all-or-none
sObject Collections Same op on many records, one object Sync JSON Up to 200 records 50-200 records, need it now
Pub/Sub API Publish + subscribe to events over gRPC Async stream Avro / gRPC Event stream Modern event-driven, CDC, replay
Streaming API (legacy) CometD push (PushTopic, generic) Async stream JSON Event stream Older event subscribers. Being superseded
Connect REST API Chatter, Experience Cloud, Files, feeds Sync JSON Feature-scoped Social, communities, files programmatically
UI API Build Salesforce-like UIs, FLS + layout aware Sync JSON UI-scoped Custom UIs needing layout / FLS metadata
Metadata API Deploy / retrieve config and metadata Async XML Org metadata CI/CD, deployments, org setup
Tooling API Developer tooling, fine-grained metadata Sync JSON / XML Dev artifacts IDEs, code analysis, debug logs

What do you need? (decision flowchart)

flowchart TD START["What are you trying to do?"] --> KIND{Move records,<br/>stream events,<br/>or deploy metadata?} KIND -->|Deploy / retrieve config| META["Metadata API async<br/>Tooling API dev tooling"] KIND -->|React to live events| EV{Modern or legacy?} KIND -->|Move record data| DATA{How many records?} EV -->|Modern, gRPC, replay| PS[Pub/Sub API] EV -->|Legacy CometD| STR[Streaming API] DATA -->|"Millions, async OK"| BULK[Bulk API 2.0] DATA -->|"Up to 200, one object, now"| COLL[sObject Collections] DATA -->|"A few, different chained ops"| COMP[Composite API] DATA -->|"One record or simple CRUD"| ONE{Special need?} ONE -->|Exact fields, nested objects| GQL[GraphQL] ONE -->|Chatter / Files / Community| CONN[Connect REST] ONE -->|Layout + FLS aware UI| UIAPI[UI API] ONE -->|Plain CRUD / SOQL| REST[REST API] style PS fill:#9C27B0,color:#fff style BULK fill:#ff9900,color:#fff style COLL fill:#00A1E0,color:#fff style COMP fill:#9C27B0,color:#fff style GQL fill:#00A1E0,color:#fff style REST fill:#4CAF50,color:#fff style META fill:#607D8B,color:#fff style START fill:#607D8B,color:#fff

The three-scale spine (the core of every answer)

Scale Records API Why
One 1 at a time REST API Simple, synchronous, one call per record
Some up to 200, one object, sync sObject Collections One round-trip, one API call, immediate
Many thousands to millions, async Bulk API 2.0 Job-based, parallelized, poll for results

Then layer the shape exceptions: GraphQL for field selection, Composite for chaining different ops with all-or-none, Pub/Sub for events, Metadata / Tooling for config.


Gotchas

  • Sync vs async first. It splits the toolbox in two. Async (Bulk, Metadata, Pub/Sub) unlocks scale.
  • API limits. Every sync call counts against the daily allocation. Batch with Collections / Composite. Offload volume to Bulk. See ../09-Security-Limits/README.md.
  • Events: modern over legacy. Pick Pub/Sub for new event work. Streaming API (CometD) is legacy.
  • GraphQL is read-shaped, not a bulk loader. Use it to shape reads, not mass-writes.
  • Metadata vs data. Metadata API moves config, not rows. Never cross the two.
  • Pin the version. Use v66.0 consistently across the integration.

Note: Data Cloud APIs also exist (ingest / query / profile, massive volume) but require a Data Cloud license. See ../08-Modern-APIs/03-data-cloud-apis.md.

Source: Which API Do I Use? — REST API Developer Guide. Full module: ../08-Modern-APIs/04-modern-api-landscape.md. Verified June 2026.

Callout & Async Limits One-Pager (v66.0)

Every callout and async governor limit in one place. Full detail in ../05-Outbound-Callouts/07-callout-limits-and-testing.md and ../09-Security-Limits/05-governor-and-api-limits.md.


Per-transaction callout limits

Limit Value Notes
Callouts per transaction 100 Combined HTTP + Web service calls, sync and async
Request + response size (sync) 6 MB Counts against the 6 MB sync heap; body + headers
Request + response size (async) 12 MB Counts against the 12 MB async heap
Default callout timeout 10 s Per single callout if setTimeout not set
Max timeout per single callout 120 s Set via req.setTimeout(ms); range 1 - 120,000 ms
Total callout time per transaction 120 s Cumulative across all callouts in the transaction
Callout after uncommitted DML Not allowed Throws You have uncommitted work pending; do DML-then-callout split or use async

Concurrency limits (org-wide, not per-transaction)

Limit Value Notes
Concurrent long-running sync requests 10 A sync request counts once it runs > 5 s
Scales by user licenses up to ~50 +1 per license bucket above the base; org-wide cap
Synchronous callouts excluded from the long-running limit A sync request waiting on a callout does not count (Spring '20+)

Continuation (long-running async callout)

Limit Value Notes
Parallel callouts per Continuation 3 Run simultaneously, suspend the request
Timeout per callout 120 s Applies regardless of how many callouts
Chained Continuations 3 Max 3 Continuation objects in a chain
Max request/response size 1 MB Per Continuation callout

Async Apex resource limits (vs synchronous)

Resource Synchronous Asynchronous
Heap size 6 MB 12 MB
CPU time 10,000 ms 60,000 ms
Total callouts 100 100
Single callout timeout 120 s 120 s

Async invocation limits per transaction

Type Limit per transaction Notes
@future 50 No sObject/object params; primitives only
Queueable (System.enqueueJob) 50 From sync context; only 1 from another Queueable/async
Batch (Database.executeBatch) 5 concurrent Org-wide; holding (queued) jobs limited to 100
@future + Queueable + ScheduleBatch 50 combined-type caps Each type capped at 50 per transaction

Quick gotchas

  • DML before callout = error. Split into separate transactions, or move the callout to @future(callout=true) / Queueable / Continuation.
  • @future(callout=true) is required for any callout from a @future method.
  • Test classes must use HttpCalloutMock / WebServiceMock and wrap in Test.startTest() / Test.stopTest(); real callouts in tests throw.
  • 6 MB / 12 MB is the heap, and request + response share it. Stream or chunk large payloads.

Source: Apex Developer Guide - Callout Limits and Limitations and Execution Governors and Limits. Verified June 2026. See ../05-Outbound-Callouts/07-callout-limits-and-testing.md.

Callout Debug Checklist One-Pager (Spring '26, v66.0)

"My callout failed — now what?" Work top to bottom. Limits + testing in ../05-Outbound-Callouts/07-callout-limits-and-testing.md; security layers in ../09-Security-Limits/README.md.


Decision flowchart

flowchart TD START[Callout failed] --> EX{Exception or<br/>bad status code?} EX -->|CalloutException| MSG{Read the message} EX -->|HTTP 2xx not returned| CODE{Status code?} MSG -->|Unauthorized endpoint| RSS[Add host to Named Credential<br/>or Remote Site Settings] MSG -->|uncommitted work pending| DML[Move callout BEFORE DML<br/>or run it async] MSG -->|Read timed out| TMO[Raise setTimeout max 120s<br/>or go async / Continuation] MSG -->|do not support callouts| MOCK[Test context: add Test.setMock] CODE -->|401 or 403| AUTH[Token expired or no perms<br/>check External Credential + perm set] CODE -->|404| URL[Wrong path or base URL] CODE -->|429 or 5xx| RETRY[Throttled or server down<br/>retry with backoff] AUTH --> FLS{Writing to SF data?} FLS -->|Yes| PERM[Check FLS / CRUD / sharing<br/>USER_MODE] FLS -->|No| LOG[Open the debug log<br/>filter Callout + Apex] style START fill:#F44336,color:#fff style RSS fill:#00A1E0,color:#fff style DML fill:#ff9900,color:#fff style TMO fill:#9C27B0,color:#fff style AUTH fill:#ff9900,color:#fff style RETRY fill:#607D8B,color:#fff style PERM fill:#4CAF50,color:#fff style LOG fill:#607D8B,color:#fff

Checklist (in order)

# Check Symptom / error Fix
1 Status code 401/403 vs 404 vs 429/5xx tells you the layer Triage by class: 4xx = your request, 5xx = their server
2 Host allowlisted Unauthorized endpoint, please check Setup->Security->Remote site settings Prefer Named Credential; else add the host to Remote Site Settings
3 Uncommitted DML You have uncommitted work pending. Please commit or rollback before calling out Call out before any DML, or move it async (Queueable/@future)
4 Timeout Read timed out / CalloutException after a hang req.setTimeout(ms) (max 120000); slow UI → Continuation
5 Auth / token 401, INVALID_SESSION_ID, expired token Check External Credential principal + perm-set mapping; token may need refresh/rotate
6 IP / CORS Inbound browser blocked, or LWC fetch blocked Inbound browser → CORS allowlist; LWC outbound → CSP Trusted Site
7 FLS / object perms Callout OK but the DML after it fails Enforce + verify CRUD/FLS/sharing (WITH USER_MODE, stripInaccessible)
8 Governor limits Too many callouts: 100, or heap/size error Bulkify (100/txn cap); payload 6 MB sync / 12 MB async; total callout time 120 s/txn
9 Concurrent long-running New sync callouts rejected under load Sync calls >5 s count vs org pool (10, scales by license) → go async/Continuation
10 Test context Methods defined as TestMethod do not support Web service callouts Real calls blocked in tests → Test.setMock(HttpCalloutMock.class, ...)
11 Debug log Nothing obvious above Setup → Debug Logs; set a trace flag, raise Callout + Apex Code to FINEST, re-run, read request/response

Fast reminders

  • 4xx = don't blindly retry (fix the request / auth). 5xx + timeouts = bounded retry with backoff + idempotency key.
  • callout:Named_Credential/path skips Remote Site Settings entirely and injects auth — the secret-free default.
  • Async ≠ free pass: still subject to limits, just a higher payload ceiling and outside the concurrent-sync pool.
  • Never log secrets when persisting request/response for traceability.

Source: Callout Limits and Limitations — Apex Developer Guide (v66.0) · Execution Governors and Limits. Verified June 2026. Full modules: ../05-Outbound-Callouts/07-callout-limits-and-testing.md · ../09-Security-Limits/README.md.

Integration Errors & Exceptions One-Pager (v66.0)

Common integration errors and exceptions, with cause and fix. Full detail in ../05-Outbound-Callouts/07-callout-limits-and-testing.md.


Apex callout / runtime exceptions

Error Cause Fix
CalloutException: Unauthorized endpoint Endpoint not whitelisted; no Remote Site Setting or Named Credential covers the URL Add a Named Credential (preferred) or Remote Site Setting; call via callout:My_Named_Cred/path
You have uncommitted work pending Callout attempted after a DML in the same transaction Do the callout before DML, or move the callout to @future(callout=true) / Queueable / Continuation
System.LimitException A governor limit was hit (callouts, heap, CPU, SOQL, DML rows) Bulkify, reduce loops, move to async (12 MB heap, 60,000 ms CPU), check Limits methods
CalloutException: Read timed out Remote endpoint slower than the timeout Raise req.setTimeout(ms) up to 120,000 ms; use Continuation for long-running calls
System.JSONException Malformed JSON, or JSON.deserialize type mismatch with the response Validate response shape; use JSONParser / typed wrapper classes; null-check before parse

API errors (REST / SOAP / Bulk) - returned in errorCode

Error Cause Fix
INVALID_SESSION_ID Session/token expired or invalid (HTTP 401) Re-authenticate; refresh the OAuth access token, then retry
REQUEST_LIMIT_EXCEEDED Org exceeded its 24-hour API request allocation (HTTP 403) Throttle, batch records, use Composite/Bulk API; monitor in Setup > System Overview
INVALID_FIELD SOQL/DML references a field that does not exist or is not accessible Check spelling, API name, FLS, and object access for the integration user
MALFORMED_QUERY SOQL syntax error (bad clause, invalid number/date format) Fix the query; test in Developer Console / Query Editor first
DUPLICATE_VALUE Value collides with a unique field, external ID, or duplicate rule Dedupe before insert; use upsert on the external ID; review duplicate rules
INSUFFICIENT_ACCESS_OR_READONLY No CRUD/FLS/sharing rights, or field is read-only/formula Grant via permission set, check OWD / sharing / role hierarchy; do not write formula fields
UNABLE_TO_LOCK_ROW Row-lock contention; another process holds the lock (waits up to 10 s) Serialize updates, reduce batch size/concurrency, retry with backoff; avoid parent-record contention
STORAGE_LIMIT_EXCEEDED Org data or file storage allocation is full Purge/archive data, raise storage, or delete attachments/files
JSON_PARSER_ERROR Request body is not valid JSON, or null input to the parser Send well-formed JSON; set correct Content-Type: application/json; null-check input

Triage cheat

  • Unauthorized endpoint = setup gap, not code. Named Credential first.
  • uncommitted work pending = the classic DML-then-callout ordering bug. Reorder or go async.
  • 401 / INVALID_SESSION_ID = refresh token. 403 / REQUEST_LIMIT_EXCEEDED = back off, do not just retry.
  • UNABLE_TO_LOCK_ROW and 5xx = transient. Retry with exponential backoff.
  • Always log the errorCode + message from the response body, not just the HTTP status.

Source: REST API Developer Guide - Status Codes and Error Responses and Apex Developer Guide - Callout Limits and Limitations. Verified June 2026. See ../05-Outbound-Callouts/07-callout-limits-and-testing.md.

Event Mechanisms One-Pager (Spring '26)

Platform Events vs CDC vs Outbound Messages vs Streaming API vs Pub/Sub API. Full detail in ../06-Event-Driven/README.md.


The comparison table

Mechanism Who creates the event Schema Transport Retention Subscribers Status
Platform Events You (deliberate business signal) Custom fields you define (__e) Event bus (Pub/Sub, empApi, CometD) 72h (high-volume) Apex, Flow, empApi, Pub/Sub API Modern
Change Data Capture Salesforce (auto on record change) Fixed: ChangeEventHeader + changed fields Event bus (Pub/Sub, empApi, CometD) 72h Apex, Flow, empApi, Pub/Sub API Modern
Outbound Messages Workflow Rule / Flow (declarative) Configured field set SOAP XML to one endpoint Auto-retry up to 24h (7 days via Support) One SOAP endpoint Legacy
Streaming API PushTopic (SOQL) / generic channel JSON (PushTopic row or custom payload) CometD long-polling (Bayeux) 24h (PushTopic) CometD clients Legacy
Pub/Sub API n/a (transport for PE + CDC) Avro per topic gRPC over HTTP/2 72h (carries PE / CDC) External apps, microservices Modern

Pick a mechanism

flowchart TD Start([Need event-driven integration]) --> Q1{What is the source?} Q1 -->|Any record create/update/delete| Q2{Custom payload needed?} Q2 -->|No - mirror the change| CDC[Change Data Capture] Q2 -->|Yes - business signal| PE1[Platform Event] Q1 -->|An app, Flow, or Apex action| PE2[Platform Event] Q1 -->|Legacy, already built| LEG[Streaming API / Outbound Messages] CDC --> SUB{Who subscribes?} PE1 --> SUB PE2 --> SUB SUB -->|External app / microservice| PUB[Pub/Sub API] SUB -->|Inside Salesforce| INT[Apex trigger / Flow / empApi] style CDC fill:#00A1E0,color:#fff style PE1 fill:#ff9900,color:#fff style PE2 fill:#ff9900,color:#fff style PUB fill:#9C27B0,color:#fff style INT fill:#4CAF50,color:#fff style LEG fill:#F44336,color:#fff

Platform Events vs CDC (the classic question)

Question Platform Events CDC
Who creates the event? You (a deliberate signal) Salesforce (automatic)
Schema You define the fields Fixed: ChangeEventHeader + changed fields
Customizable payload? Yes No
Best for Business events (OrderShipped) Replicating record changes to a store

Must-knows

  • Retention is 72 hours for high-volume Platform Events and CDC. The "96 hours" figure is a myth. Legacy standard-volume events were 24h and are retired.
  • PushTopic / Streaming API events retain for 24h, shorter than the 72h event-bus window.
  • Replay IDs: store the last one and resubscribe within the window. -1 = new only, -2 = all retained, or a specific ID to resume.
  • Delivery is at-least-once, not exactly-once. Subscribers must be idempotent.
  • Transport, not source: the Pub/Sub API (gRPC + Avro) is the modern subscriber for both Platform Events and CDC. The CometD Streaming API is legacy for new builds.
  • Outbound Messages still work but Workflow Rules / Process Builder get no enhancements. Build new declarative push in Flow, or prefer Platform Events.

Source: Pub/Sub API — Event Message Durability and Platform Events Developer Guide. Full module: ../06-Event-Driven/README.md. Verified June 2026.

Named Credentials Syntax One-Pager (Spring '26, v66.0)

Copy-paste outbound auth reference. Never hardcode secrets — reference callout:Name. Full detail in ../03-Authentication/14-named-credentials-and-external-credentials.md.


The model in one line

Named Credential = where (endpoint URL). External Credential = how (auth protocol + principals + permission-set mappings). Split since Winter '23; one External Credential backs many Named Credentials.

flowchart LR APX[Apex / Flow<br/>callout:My_NC/path] --> NC[Named Credential<br/>endpoint URL] NC --> EC[External Credential<br/>protocol + principal] EC --> API[External API<br/>auth header injected] style APX fill:#607D8B,color:#fff style NC fill:#00A1E0,color:#fff style EC fill:#9C27B0,color:#fff style API fill:#4CAF50,color:#fff

Apex callout — the whole point

HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_Named_Cred/v1/orders'); // 'callout:' + NC name + path
req.setMethod('GET');
HttpResponse res = new Http().send(req);
System.debug(res.getStatusCode() + ' ' + res.getBody());

Salesforce swaps callout:My_Named_Cred for the real URL and injects the auth header at send time. Your code, repo, logs, and packages stay secret-free. Rotating a credential is a config change — zero deploys.

POST with a merge field (only if Allow Formulas in HTTP Body is enabled on the NC):

HttpRequest req = new HttpRequest();
req.setEndpoint('callout:My_Named_Cred/v1/tickets');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody('{"agent":"{!$Credential.My_External_Cred.Username}"}');
HttpResponse res = new Http().send(req);

Custom header / API key merge field (define on the NC, every callout carries it):

// Header value references a field stored in the External Credential at runtime
req.setHeader('X-API-Key', '{!$Credential.My_External_Cred.ApiKey}');

Merge-field syntax: {!$Credential.<ExternalCredential>.<field>}. Standard fields: .Username, .Password. Custom auth params are referenced by their configured name.


Named Principal vs Per-User

Principal type Identities Use when Example
Named Principal One shared for all users External system does not care which SF user Nightly sync under one service account
Per-User Principal Each user authenticates individually External system needs the real end user User connects their own DocuSign/Google
Client Credentials The app's own client identity OAuth Client Credentials, no user context Backend service under app client id/secret

Per-User: each user must self-authenticate via Authenticate in their personal settings. Multiple mappings on one user → lowest sequence number wins.


Permission-set mapping is REQUIRED

A principal is inert until mapped to a permission set (or PS group / profile) and that set assigned to the running user. No mapping = auth failure, even for an admin.

External Credential -> Principal -> mapped to Permission Set -> assigned to User

Auth protocols (External Credential)

Protocol Authenticates with Typical use
OAuth 2.0 — Browser Flow User logs in via browser callback Per-user SaaS needing consent
OAuth 2.0 — JWT / JWT Bearer Signed JWT (org cert) → token Server-to-server, no browser
OAuth 2.0 — Client Credentials Client id + secret → token Backend, one system identity
Basic Static username + password Legacy Basic-auth APIs
AWS Signature v4 AWS key + secret sign each request S3, API Gateway (also STS Roles Anywhere via cert)
API Key Secret in a Custom header APIs using a static key
Custom Your own headers / params Anything non-standard
mTLS Client + server certificates High-security APIs requiring client certs

Gotcha: API Key is not its own dropdown — implement it as a Custom external credential with a custom header carrying the key.


Setup order (do auth first)

  1. Setup → Named Credentials → External Credentials → New → pick protocol.
  2. Add a Principal (Named, Per-User, or Client Credentials) + enter secrets.
  3. Map the principal to a permission set (mandatory).
  4. Named Credentials → New → set URL + select the External Credential.
  5. Callout options: Generate Authorization Header (usually on); Allow Formulas in HTTP Header/Body only if you need merge fields.
  6. Assign the permission set to users who run the callout.

Source: Authentication Protocols for Named Credentials — Salesforce Help · Named Credentials as Callout Endpoints — Apex Developer Guide. Verified June 2026. Full module: ../03-Authentication/14-named-credentials-and-external-credentials.md.

OAuth Flows One-Pager (Spring '26)

Fast pre-interview cram sheet. Full detail in ../03-Authentication/.


Pick a flow in 10 seconds

flowchart TD Q{Human user<br/>logging in?} Q -->|No - system to system| S{Proof of identity?} S -->|Secret + run-as user| CC[Client Credentials] S -->|Certificate| JWT[JWT Bearer] Q -->|Yes - has backend| WS[Web Server] Q -->|Yes - mobile/SPA| PK[Web Server + PKCE] Q -->|Yes - no browser| DV[Device Flow] Q -->|SF calls OUT| NC[Named Credential] style CC fill:#4CAF50,color:#fff style JWT fill:#4CAF50,color:#fff style WS fill:#4CAF50,color:#fff style PK fill:#4CAF50,color:#fff style NC fill:#00A1E0,color:#fff style DV fill:#ff9900,color:#fff

All flows at a glance

Flow grant_type User? Refresh? Use when
Web Server authorization_code ✅ ✅ Web app, user login (default)
Web Server + PKCE authorization_code + code_challenge ✅ ✅ Mobile / SPA / public client
JWT Bearer urn:ietf:params:oauth:grant-type:jwt-bearer ❌ ❌ Server-to-server, certificate, CI/CD
Client Credentials client_credentials ❌ ❌ Modern server-to-server, secret
Device device (after response_type=device_code) ✅ ✅ TV, CLI, IoT
Refresh Token refresh_token ❌ reuses Renew an expired access token
SAML Bearer urn:ietf:params:oauth:grant-type:saml2-bearer ❌ ❌ Server-to-server, existing SAML
SAML Assertion assertion (SSO browser) ✅ ❌ API access after web SSO
Asset Token urn:ietf:params:oauth:grant-type:token-exchange ❌ n/a IoT device/asset binding
Auth Code & Credentials authorization_code (headless) ✅ ✅ Headless / branded customer login
⛔ Username-Password password ✅ ❌ Retiring — do not use
⚠️ User-Agent response_type=token ✅ opt Legacy — avoid

Tokens

Token What Sent as Refresh-returning flows
Access API key (= session id), ~2h Authorization: Bearer ... all
Refresh Renew access silently POST /token grant_type=refresh_token Web Server, Device, Auth Code & Creds
ID token OIDC JWT describing the user returned when openid requested flows with openid scope

Common scopes: api (data APIs) · refresh_token/offline_access (refresh) · openid (ID token) · web (frontdoor.jsp) · full (everything, but still add refresh_token).


Endpoints (under your My Domain /services/oauth2/)

/authorize (browser login) · /token (app exchange) · /revoke (logout/kill) · /introspect (is it valid?) · /userinfo (OIDC profile).


Outbound (Salesforce → external)

Always a Named Credential + External Credential. Apex: req.setEndpoint('callout:My_Named_Credential/path'). No hardcoded secrets. Principal = Named (one shared identity) or Per-User.


2025-26 must-knows

  • Username-Password retiring (Winter '27; blocked by default for orgs created Summer '23+).
  • External Client Apps replace Connected Apps (new Connected App creation disabled by default in Spring '26).
  • Client Credentials = server-to-server default. PKCE = recommended for all interactive flows.
  • Device Flow removed from Data Loader's auto-installed app (Sept 2, 2025).

30-second talking points

  • AuthN vs AuthZ: identity vs permission. OAuth = authorization; OpenID Connect = the authentication layer (ID token).
  • Why code-then-token?: keeps the access token off the browser/URL.
  • Machine flows have no refresh token: no user session to keep alive, just re-run.
  • Never hardcode credentials: Named Credentials centralize and rotate them.

Source: Salesforce Help — OAuth Authorization Flows. Verified June 2026.

Integration Patterns One-Pager (Spring '26)

The 6 official Salesforce integration patterns at a glance. Full detail in ../02-Integration-Patterns/README.md.


The 6 patterns (memorize this)

Pattern Direction Timing Volume Salesforce tech Use when
Request & Reply SF → External Sync Low HTTP callout, External Services You need the answer now to continue.
Fire & Forget SF → External Async Low-Med Platform Events, Outbound Messages You announce an event and don't wait.
Batch Data Sync Both ways Scheduled / async High Bulk API 2.0, ETL / MuleSoft Large volumes on a schedule.
Remote Call-In External → SF Sync or async Any REST / SOAP API, Apex REST An external system drives the CRUD.
Data Virtualization SF reads External Real-time read Low-Med Salesforce Connect (OData) View external data without copying it.
UI Update on Change SF → subscribers Async push Med CDC, Pub/Sub API, empApi UI must auto-refresh on a change.

Pick a pattern in 10 seconds

flowchart TD Start([New integration requirement]) --> Dir{Who initiates,<br/>and what is needed?} Dir -->|External system calls Salesforce| RCI[Remote Call-In<br/>REST / SOAP / Apex REST] Dir -->|Show external data without storing it| DV[Data Virtualization<br/>Salesforce Connect] Dir -->|UI must refresh when data changes| UI[UI Update on Change<br/>CDC / Pub-Sub API] Dir -->|Salesforce calls out| Vol{Volume and<br/>response need?} Vol -->|High volume, scheduled| BATCH[Batch Data Sync<br/>Bulk API 2.0 / ETL] Vol -->|Real-time, need the answer| RR[Request and Reply<br/>HTTP callout] Vol -->|Real-time, no answer needed| FF[Fire and Forget<br/>Platform Events] style RCI fill:#9C27B0,color:#fff style DV fill:#4CAF50,color:#fff style UI fill:#ff9900,color:#fff style BATCH fill:#9C27B0,color:#fff style RR fill:#00A1E0,color:#fff style FF fill:#ff9900,color:#fff

Scenario → pattern (rapid mapping)

Requirement Pattern
"Check a credit score before saving the loan." Request & Reply
"Tell the ERP an order shipped." Fire & Forget
"Sync 2M accounts from SAP every night." Batch Data Sync
"Our website must create Leads in Salesforce." Remote Call-In
"Show SAP invoices on the Account, don't store them." Data Virtualization
"The agent console must update the moment a Case changes." UI Update on Change

30-second talking points

  • Request & Reply vs Fire & Forget: Reply waits for and uses the response (sync). Forget sends and moves on (async). Choose by whether the result drives the next step.
  • Sync first decision: if you can wait, async unlocks scale (Bulk, Platform Events). If a user is blocked, stay sync and keep payloads small.
  • Remote Call-In is the only pattern where the external system is the client. Authenticate with a Connected App / External Client App + OAuth.
  • Data Virtualization triggers a callout on every view, so it reads live but does not scale to heavy traffic. Use for low-to-medium read volume.
  • Never loop synchronous callouts for bulk. That is a Batch Data Sync, not many Request & Reply calls.

Source: Integration Patterns and Practices (v66.0, Spring '26). Full module: ../02-Integration-Patterns/README.md. Verified June 2026.

Postman + Salesforce Setup One-Pager (Spring '26, v66.0)

One-time setup to hit Salesforce REST APIs by hand. Full detail in ../10-Tools-Middleware/01-postman.md.


What Postman does

Runs the OAuth dance, captures access_token + instance_url into environment variables, then sends Authorization: Bearer {{access_token}} against {{instance_url}}.

flowchart LR PM[Postman] --> OA[POST /services/oauth2/token] OA --> TOK[access_token + instance_url<br/>stored as variables] TOK --> API[GET instance_url/services/data/v66.0/...] style PM fill:#ff9900,color:#fff style OA fill:#00A1E0,color:#fff style TOK fill:#9C27B0,color:#fff style API fill:#4CAF50,color:#fff

Setup steps

  1. Install Postman (desktop or web) and sign in.
  2. Fork the official "Salesforce Platform APIs" collection from the Salesforce Developers workspace on Postman (right side → Fork). You get ready-made query/CRUD/composite/bulk requests with variables wired up.
  3. Create a Connected App or External Client App in Setup (App Manager → New): - Enable OAuth Settings. - Callback URL: https://oauth.pstmn.io/v1/callback - Scopes: api refresh_token (add refresh_token so the token can renew). - Save → copy the Consumer Key and Consumer Secret (wait ~10 min for activation).
  4. Select an environment and set variables:
Variable Value
login_url Your My Domain URL, e.g. https://mydomain.my.salesforce.com
client_id Consumer Key
client_secret Consumer Secret
username (per flow) integration username
  1. Run an OAuth flow on the collection's Authorization tab → Configure New Token: - Logged-in user → Authorization Code (browser consent via the pstmn.io callback). - Server-to-server → Client Credentials (no user; the app's run-as identity). - Click Get New Access Token → Use Token. Postman stores access_token (and usually instance_url).
  2. Send a query:
GET {{instance_url}}/services/data/v66.0/query?q=SELECT+Id,Name+FROM+Account+LIMIT+5
Authorization: Bearer {{access_token}}

Use + for spaces in the q param, or let Postman URL-encode the value.


Troubleshooting

Symptom Fix
401 INVALID_SESSION_ID Token expired → Get New Access Token again, or use refresh token.
redirect_uri_mismatch Callback must be exactly https://oauth.pstmn.io/v1/callback.
Data call hits login.salesforce.com Use My Domain / {{instance_url}}, never the login host, for data.
invalid_client_id / invalid_client Wrong key/secret, or app not yet active (wait ~10 min).
Client Credentials returns no token App needs a run-as user set under OAuth policies for that flow.
Username-Password flow fails It's blocked/retiring — use Authorization Code or Client Credentials.
Session expired after some time Re-auth; consider refresh_token scope for silent renew.
Forgot API version Pin v66.0 in the path.

Source: Salesforce Platform APIs — Postman Collection · Quick Start: Connected App for the REST API. Verified June 2026. Full module: ../10-Tools-Middleware/01-postman.md.

REST API HTTP Status Codes One-Pager (v66.0)

HTTP status codes Salesforce REST APIs return, what each means, and the common Salesforce cause. Full detail in ../04-Inbound-APIs/01-standard-rest-api.md.


Success (2xx)

Code Meaning Typical Salesforce cause
200 OK GET / PATCH / HEAD succeeded; body returned
201 Created POST created a record; Location + new Id returned
204 No Content DELETE or PATCH succeeded with no body to return

Redirection (3xx)

Code Meaning Typical Salesforce cause
300 Multiple Choices External ID in upsert matched more than one record
304 Not Modified Conditional GET with If-Modified-Since / If-None-Match; resource unchanged, use cache

Client errors (4xx)

Code Meaning Typical Salesforce cause
400 Bad Request Malformed request, invalid field/SOQL, bad JSON/XML, validation rule failure
401 Unauthorized Session expired or invalid OAuth token (INVALID_SESSION_ID); re-authenticate
403 Forbidden Valid session but no permission, or REQUEST_LIMIT_EXCEEDED (24h API cap)
404 Not Found Record/resource gone, wrong Id, wrong API version, or no object access
405 Method Not Allowed HTTP verb not supported on that resource (e.g. POST to a record URL)
409 Conflict Concurrent update conflict; cannot complete due to a conflicting change
412 Precondition Failed An If-* header condition was not met
415 Unsupported Media Type Bad Content-Type / entity format; sent XML where JSON expected (or vice versa)
431 Request Header Fields Too Large Headers (often the auth/cookie header) exceed the allowed size

Server errors (5xx)

Code Meaning Typical Salesforce cause
500 Internal Server Error Salesforce-side error, often an unhandled Apex exception in a trigger/REST class
503 Service Unavailable Server busy / too many requests; back off and retry with exponential backoff

Quick reading guide

  • 2xx = done. 204 means success but check there is no body before parsing.
  • 300 on upsert = your external ID is not unique; fix the data or the matching field.
  • 401 vs 403: 401 = who are you (bad/expired token); 403 = not allowed or over your limit.
  • 4xx = fix the request (yours). 5xx = retry (theirs), with backoff.
  • Error body returns an array of { "message", "errorCode", "fields" } - log the errorCode, not just the HTTP status.

Source: REST API Developer Guide - Status Codes and Error Responses. Verified June 2026. See ../04-Inbound-APIs/01-standard-rest-api.md.

Module 13 - What's New in 2026

Goal: Know what's arriving and what's going away so you sound current and never get blindsided. API version: v66.0 (Spring '26); current live is v67.0 (Summer '26). Learn Modules 01-12 first, then use this for awareness.

Two forces define 2026: the AI-agent shift (Agentforce + MCP turning integrations into agent-callable Actions) and a wave of security-driven retirements (OAuth + External Client Apps everywhere). This module is awareness-depth, but accurate and sourced.


Map of this module

# File What it covers
01 retirements-and-deprecations The 2026-2028 retirement timeline + migrations
02 agentforce-mcp-and-integration AI agents calling integrations as Actions via MCP
03 mulesoft-2026 Agent Fabric, Composer, API Catalog, governance
04 platform-and-api-additions Named Query API, Pub/Sub default, Data Cloud APIs

Headline changes (the quick table)

Theme What When / status
Retiring SOAP login() Summer '27 (Jun 1, 2027)
Retiring OAuth Username-Password flow Winter '27
Off by default New Connected App creation Spring '26
Discontinued Salesforce-to-Salesforce Summer '26
New (GA) Salesforce-hosted MCP servers Summer '26
New (GA) Named Query API 2026
Default Pub/Sub API for events (Streaming legacy) now
Beta Managed Event Subscriptions 2026

Full dated timeline in 01-retirements-and-deprecations.md.


How AI agents change integration

flowchart LR USER[User asks an agent] --> AGENT[Agentforce agent] AGENT --> MCP{MCP / Action router} MCP --> FLOW[Salesforce Flow] MCP --> APEX[Apex action / Named Query] MCP --> MULE[MuleSoft API] MCP --> EXT[External Service] FLOW --> SF[(Salesforce data)] MULE --> SAP[(SAP / NetSuite)] EXT --> API[(Public API)] style AGENT fill:#00A1E0,color:#fff style MCP fill:#ff9900,color:#fff style MULE fill:#9C27B0,color:#fff

The key shift: integrations are now exposed as Actions that AI agents discover and call. Your Apex action, Flow, Named Query, or MuleSoft API can become an agent tool, accessed over standard OAuth. Detail in 02-agentforce-mcp-and-integration.md.


Accuracy note

The event-bus retention for high-volume Platform Events and Change Data Capture is 72 hours (3 days), not 96. If you see "96-hour replay" anywhere, it's wrong. See Module 06.


Outcome

You won't be blindsided when someone says "wire this into Agentforce," "expose this as an MCP action," or "we need to migrate off SOAP login before Summer '27."


Sources (Verified June 2026)

Each file has its own Sources section with specific official docs.

01 - Retirements & Deprecations (the 2026-2028 timeline)

One-liner: Salesforce is retiring legacy, password-based, and old-API integration paths and pushing everything to OAuth + External Client Apps + current API versions. Why it matters: These are the "what's changing on the platform?" questions interviewers love, and the migrations you'll actually be asked to do.

This is Module 13, awareness of what's new and what's going away. The replacements are all covered in Modules 03-09. Dates use Salesforce release names; a release rolls out over a few weeks around its season.


1. The idea in plain English

Salesforce retires things for two reasons: security (kill password-in-the-request and weak auth) and modernization (move everyone onto current APIs and External Client Apps). The pattern is always the same: a feature is deprecated (still works, no new investment), then disabled by default for new orgs, then retired (stops working). If you know the dates, you're never blindsided, and you sound current in an interview.


2. The timeline (memorize the big four)

flowchart LR A[Summer '25<br/>API v21-30 retired] --> B[Feb 16 2026<br/>Outbound Msg session Ids blocked] B --> C[Spring '26<br/>New Connected App creation off] C --> D[Summer '26<br/>Salesforce-to-Salesforce ends] D --> E[Winter '27<br/>Username-Password flow retired] E --> F[Summer '27 - Jun 1<br/>SOAP login retired] F --> G[Dec 2027<br/>Salesforce for Outlook retired] G --> H[Summer '28<br/>API v31-40 retired] style C fill:#ff9900,color:#fff style E fill:#F44336,color:#fff style F fill:#F44336,color:#fff style H fill:#9C27B0,color:#fff
When What retires / changes What to do instead
Done (Summer '25) API versions 21.0-30.0 retired Move to v41.0+ (ideally current v66/67).
Feb 16, 2026 Outbound Messages can no longer send a Session ID Authenticate the callback with OAuth.
Spring '26 New Connected App creation disabled by default Build new apps as External Client Apps.
Summer '26 (v67.0) Salesforce-to-Salesforce discontinued; "Use Any API Auth" perm added; API v31.0-40.0 deprecated Migrate S2S to Data Cloud One / MuleSoft / Partner tools.
Winter '27 OAuth Username-Password flow retired Client Credentials (server) or Web Server + PKCE (user).
Summer '27 (Jun 1, 2027) SOAP API login() retired (v31.0-64.0; already gone in v65.0+) OAuth via ECA. SOAP now accepts JWT access tokens.
Dec 2027 Salesforce for Outlook retired Outlook Integration + Einstein Activity Capture.
Summer '28 API versions 31.0-40.0 retired Move to v41.0+.

3. The big four, explained

1. New Connected Apps are off by default (Spring '26). Creation is blocked in the UI and Metadata API (except package installs); re-enabling needs a Salesforce Support request. Build new integrations as External Client Apps, which separate developer config from admin policy and support only modern OAuth flows. See Module 03.

2. Username-Password OAuth flow retires (Winter '27). Already blocked by default for orgs created Summer '23+. It passes raw credentials and bypasses MFA. Move servers to Client Credentials, users to Web Server + PKCE. See Module 03.

3. SOAP login() retires (Summer '27, June 1 2027). The most common legacy auth call. Gone from API v65.0+ already; disabled by default in new orgs; from Summer '26 needs the "Use Any API Auth" permission. SOAP now accepts JWT OAuth tokens, so migrate auth to OAuth and keep your SOAP payloads if needed. See Module 04.

4. Old API versions retire (v31-40 in Summer '28). Deprecated versions stop getting fixes, then fail. Audit integrations and bump to a current version (this vault targets v66.0).


4. How to find what affects your org

  • Read each Release Notes "Retirement" section every season.
  • Check API Total Usage and the per-version usage in Setup → API Usage, and the /services/data version list.
  • Review Connected Apps OAuth Usage and Login History to spot legacy flows (Username-Password, SOAP login) still in use.
  • Search Salesforce Help for the specific "... Retirement" knowledge article for an exact enforcement date.

5. Interview Q&A

Q: What major integration retirements are coming? A: New Connected App creation is off by default (Spring '26), the Username-Password OAuth flow retires (Winter '27), SOAP login() retires (Summer '27), and API versions 31-40 retire (Summer '28). The throughline is OAuth + External Client Apps + current API versions.

Q: A legacy job authenticates with SOAP login() and a password. How do you modernize it? A: Move auth to OAuth via an External Client App, using JWT Bearer or Client Credentials. SOAP API now accepts JWT access tokens, so the SOAP calls can stay while the auth modernizes.

Q: Connected App or External Client App for a new build? A: External Client App. New Connected App creation is disabled by default as of Spring '26.

Q: How do you know if a retirement affects you? A: Release notes retirement sections, API usage by version in Setup, and Login History / OAuth usage to find legacy auth still running.

Talking point to explain it to anyone: "Salesforce is turning off the old, password-based doors and moving everyone to modern keycards. The dates are published, so you migrate before the door locks."


6. Key terms

Deprecation, retirement, disabled-by-default, External Client App, JWT Bearer, Client Credentials - defined in Module 03 and the README.


Sources (Verified June 2026)


Next: 02-agentforce-mcp-and-integration.md - how AI agents now call your integrations as Actions.

02 - Agentforce, MCP & Integration (the AI-agent shift)

One-liner: AI agents can now call your integrations directly. An MCP-compatible client (Claude, ChatGPT, Cursor) connects to an org over OAuth and uses your Flows, Apex actions, and Named Queries as tools. Why it matters: This is the biggest 2026 shift for integration engineers. The integrations you build are no longer just for apps and ETL jobs, they become actions an AI agent can discover and invoke. Status: Salesforce Hosted MCP Servers are GA (announced Apr 29, 2026, headlined in Summer '26, API v67.0). Some adjacent pieces (Data 360 MCP server, Multi-Agent Orchestration) are still Preview/Beta. Flags below.

This is Module 13, awareness depth. For the underlying mechanics, see Authentication and Modern APIs.


1. The idea in plain English

For years, an integration meant "system A calls system B over an API." In 2026 a third caller shows up: an AI agent. A user types a request in plain English, and an agent figures out which tool to call to satisfy it.

Two names you must know:

  • Agentforce is Salesforce's platform for building and running AI agents (autonomous assistants that reason over a request and take actions). An agent reasons, then calls Actions, the discrete operations it is allowed to perform.
  • MCP (Model Context Protocol) is an open standard for connecting AI clients to systems. Think of it as USB-C for AI tools: one plug shape so any compatible client can talk to any compatible server. Salesforce did not invent it, it adopted it.

The practical headline: Salesforce now hosts MCP servers for you. Enable a server in Setup and any MCP client can connect to your org over OAuth, with your existing permissions (CRUD, FLS, sharing) enforced automatically. No infrastructure to run.


2. What's new (the specifics)

A hosted MCP server is a Salesforce-managed endpoint that exposes your org's logic and data to any client that speaks MCP. You get two flavors: standard (prebuilt) and custom (you choose what to expose).

Capability What it is Status (June 2026)
Hosted MCP Servers Salesforce-managed MCP endpoints, OAuth + PKCE, permissions enforced per user GA (Enterprise Edition and above)
Standard servers Prebuilt: SObject (CRUD, SOQL, search), Data 360 (queries, graph traversal), Tableau (analytics) GA
Custom servers You pick which tools and prompts to expose, full sharing model respected GA
mcp_api OAuth scope A dedicated scope for MCP, separate from the existing REST API scope GA
Prompt templates Prebuilt natural-language starting points (such as account review, deal analysis) GA
Agentforce as MCP tool Expose an Agentforce agent itself as an MCP tool Rolling out
Data 360 MCP Server Open-source server fronting roughly 200 Data 360 REST ops behind 3 facade tools Developer Preview
Marketing Cloud Engagement MCP Exposes data extensions and journeys as tools GA (from July 2026)

The big shift: your integrations become agent tools. A custom MCP server can build tools from any of these, with no integration glue code:

  • Apex Actions - methods annotated @InvocableMethod.
  • Apex REST - your existing custom @RestResource endpoints.
  • AuraEnabled Apex - @AuraEnabled methods.
  • Lightning Flows - autolaunched flows.
  • Named Query API - parameterized SOQL exposed as a resource (see 04-platform-and-api-additions.md).
  • Prompt Builder prompts and the API Catalog (your curated REST registry).

So the Apex REST class you wrote in Module 04, the Flow from a callout in Module 05, or a MuleSoft API can all become an action an agent calls by name.


3. How it works (architecture)

flowchart LR USER[User asks in plain English] --> CLIENT[MCP Client<br/>Claude / ChatGPT / Cursor] CLIENT -->|OAuth + PKCE<br/>scope mcp api| MCP{Hosted MCP Server<br/>action router} MCP --> APEX[Apex Action<br/>InvocableMethod] MCP --> FLOW[Autolaunched Flow] MCP --> NQ[Named Query API] MCP --> MULE[MuleSoft API] APEX --> SF[(Salesforce Data)] FLOW --> SF NQ --> SF MULE --> EXT[(SAP / NetSuite / External)] style MCP fill:#0d9dda,color:#fff style CLIENT fill:#00a1e0,color:#fff style MULE fill:#ff9900,color:#fff

Walkthrough

  1. The user asks for something in natural language inside their AI client.
  2. The client connects to the org's hosted MCP server using OAuth (a dedicated mcp_api scope, plus PKCE). Every transaction runs as the authenticated user.
  3. The server is the action router. It advertises only the tools you explicitly exposed, the agent cannot call an API that was not published.
  4. The chosen tool runs: an Apex action, a Flow, a Named Query, or a MuleSoft-backed endpoint.
  5. Security is automatic. CRUD, FLS, and sharing apply. If the user lacks permission, the agent cannot do it either. The user's name lands in the audit trail.

Why "human at the wheel" matters: MCP is itself a safety mechanism. Structured tool calls replace open API access, the server defines exactly what is callable, and the mcp_api scope does not grant the old REST APIs. Salesforce layers its permission model, audit trails, and a secure-by-default posture (servers must be explicitly enabled) on top.


4. What it means for integration work

The design rule changes. You are no longer building only for a known caller, you are building for an agent that discovers your tool and decides whether to call it. Design integrations to be agent-consumable.

Old mindset (app-to-app) New mindset (agent-consumable)
Endpoint named for an internal system Clear name + description so an agent can pick it by intent
Inputs documented in a wiki Typed, self-describing inputs/outputs (invocable variables, OpenAPI)
One service account, broad rights Runs as the user, least-privilege, FLS and sharing do the gating
Auth via password or session ID OAuth + PKCE, the mcp_api scope
Governance is "trust the caller" Governance is the design: expose only what is safe, log everything

When you would reach for this: a sales rep wants an account briefing without leaving Slack or Claude. A finance team reconciles closed-won opportunities against ERP entries from one chat. An ISV exposes industry-specific Apex actions so customer agents speak the data model in plain English. When you would not: high-volume system-to-system ETL still belongs on Bulk API 2.0 and Pub/Sub, MCP is for agent-driven, conversational access, not batch throughput.

Getting started (under 30 minutes per Salesforce): enable a server in Setup -> API Catalog -> MCP Servers, create an External Client App with mcp_api and refresh_token scopes, then connect a client. Start with the read-only sobject-reads server in a sandbox.


5. Interview Q&A

Q: What is MCP and why does Salesforce care? A: The Model Context Protocol is an open standard for connecting AI clients to systems, like USB-C for AI tools. Salesforce adopted it and now hosts MCP servers (GA, Summer '26), so any MCP-compatible client connects to an org over OAuth and uses Salesforce data and logic as tools, with permissions enforced.

Q: How does an agent authenticate, and is it safe? A: OAuth with PKCE, using a dedicated mcp_api scope that does not unlock the regular REST APIs. Every transaction runs as the authenticated user, so CRUD, FLS, and sharing apply and the action is audited. The server only exposes tools you explicitly publish.

Q: My team has an Apex REST class and a Flow. How do they become agent tools? A: Build a custom hosted MCP server and map them in. Custom servers can expose @InvocableMethod Apex actions, Apex REST, @AuraEnabled methods, autolaunched Flows, Named Queries, and Prompt Builder prompts, with no integration code.

Q: How is this different from just calling the REST API? A: REST is open access to whatever the token allows. MCP gives the agent structured, named tool calls limited to what the server publishes, and a separate scope. It is safer and self-describing, the agent picks tools by intent rather than constructing arbitrary calls.

Q: Should I move my nightly ETL to MCP? A: No. MCP suits agent-driven, conversational access. High-volume batch still belongs on Bulk API 2.0 and Pub/Sub API. Use the right tool per workload.

Talking point to explain it to anyone: "We gave the org a universal AI plug. Any AI assistant can plug in, but it can only touch the doors we unlocked, only as the person using it, and every move is recorded."


6. Key terms

Agentforce (Salesforce's AI agent platform), MCP (open AI-to-system protocol), hosted MCP server (Salesforce-managed MCP endpoint), Action (a discrete operation an agent can call), mcp_api scope (OAuth scope for MCP), standard vs custom server. OAuth, PKCE, and External Client Apps are defined in Module 03.


Sources (Verified June 2026)


Next: 03-mulesoft-2026.md - MuleSoft as the governance and connectivity fabric for agents.

03 - MuleSoft in 2026 (the agent era)

One-liner: MuleSoft is repositioning from "connect apps and APIs" to "govern and connect AI agents." Agent Fabric manages agent sprawl, Composer lets business users build integrations, and the API Catalog keeps agents on approved APIs. Why it matters: When agents start calling integrations (see 02-agentforce-mcp-and-integration.md), someone has to govern which agents and APIs they can use. MuleSoft is making that its job. Status: Agent Fabric components are GA (Registry, Broker, Governance, Visualizer, since Oct 2025), Agent Scanners and AI Gateway LLM Governance GA in early 2026. API Governance for Salesforce is Beta. Flags below.

This is Module 13, awareness depth. For middleware and iPaaS fundamentals, see ../10-Tools-Middleware/06-middleware-and-ipaas.md.


1. The idea in plain English

MuleSoft's classic value was API-led connectivity: build reusable APIs in three layers (System APIs to back-end systems, Process APIs for business logic, Experience APIs for each channel), then assemble apps from them instead of point-to-point spaghetti.

In 2026 that same idea is being pointed at AI agents. Enterprises are spinning up agents on every platform (Agentforce, Bedrock, Vertex, Copilot Studio), and the result is agent sprawl: redundant agents, no shared catalog, compliance blind spots. MuleSoft's pitch: it has governed APIs and applications for over a decade, so it is positioned to be the governance and connectivity fabric for agents too.

Think air traffic control for a fleet of agents: a single place to register, route, govern, and observe every agent, no matter who built it.


2. What's new (the specifics)

Capability What it does Status (June 2026)
MuleSoft Agent Fabric Discover, orchestrate, govern, observe any AI agent across clouds GA (suite)
- Agent Registry Central catalog where any agent or tool, including MCP and A2A servers, is registered and made discoverable GA
- Agent Broker Intelligent router that groups agents into business domains and routes tasks across them GA
- Agent Governance Enterprise guardrails: security, compliance, policy on every agent interaction GA
- Agent Visualizer Live map of the agent network, interactions, and dependencies GA
- Agent Scanners Auto-discover agents across Salesforce, Bedrock, Vertex AI, Copilot Studio GA (early 2026)
AI Gateway LLM Governance Central visibility of token usage, cost, and data flows for third-party models GA
MuleSoft Composer No-code "citizen integrator" tool inside Salesforce GA
API Catalog with MuleSoft Sync Governance and discovery layer so agents reach only approved APIs Available
API Governance for Salesforce Apply API governance rules and standards to Salesforce-built APIs Beta

MuleSoft Composer is the no-code piece. It is a declarative, clicks-not-code integration tool embedded in Salesforce, built for business teams (the "citizen integrator"). It ships prebuilt connectors for systems such as Slack, Jira, NetSuite, Workday, and Google Sheets, and turns manual multi-step processes into event-based automations. Typical flows: when a Salesforce opportunity closes, create the NetSuite sales order and post to Slack, or turn an urgent Service Cloud case into a Jira ticket plus a Slack alert. Business users build on IT-built assets and templates, so it complements, not replaces, full Anypoint development.

API Catalog with MuleSoft Sync is the discovery and governance layer. It is the curated registry agents and developers browse to find approved APIs, so an agent reaches a sanctioned, governed endpoint rather than an arbitrary one. API Governance for Salesforce (Beta) extends MuleSoft's governance rules onto Salesforce-built APIs so they meet the same standards.


3. How it works (Agent Fabric between agents and APIs)

flowchart TB subgraph AGENTS[AI Agents - any platform] AF[Agentforce Agent] BR[Bedrock Agent] CP[Copilot Studio Agent] end subgraph FABRIC[MuleSoft Agent Fabric - governance layer] REG[Agent Registry<br/>discover and catalog] BROK[Agent Broker<br/>route by domain] GOV[Agent Governance<br/>security and policy] VIS[Agent Visualizer<br/>observe] end subgraph CONN[Connectivity layer] CAT[API Catalog<br/>approved APIs only] SYS[System / Process / Experience APIs] end AF --> REG BR --> REG CP --> REG REG --> BROK BROK --> GOV GOV --> CAT CAT --> SYS SYS --> BACK[(Salesforce / SAP / NetSuite / SaaS)] style FABRIC fill:#0d9dda,color:#fff style REG fill:#00a1e0,color:#fff style BROK fill:#00a1e0,color:#fff style GOV fill:#ff9900,color:#fff style CAT fill:#ff9900,color:#fff

Walkthrough

  1. Agents from any platform register in the Agent Registry, which also catalogs MCP and A2A (agent-to-agent) servers.
  2. A request hits the Agent Broker, which sorts agents and tools into business domains and routes the task to the best-fit agent.
  3. Agent Governance applies security, compliance, and policy to every hop before anything runs.
  4. Calls flow through the API Catalog, so agents reach only approved, governed APIs, which sit on classic System / Process / Experience layers to the back ends.
  5. Agent Visualizer gives IT a live map for spotting bottlenecks and failures.

4. What it means for integration work

MuleSoft is staking out the control plane for agents. The skills carry over directly: API-led layering, an API catalog, and governance policies are exactly what an agent estate needs.

Classic MuleSoft job The 2026 agent-era version
API catalog for developers Registry + API Catalog for agents and developers
Anypoint policies on API traffic Agent Governance on every agent interaction
Route between systems Agent Broker routes between agents and tools
Monitor API health Visualizer observes the agent network and LLM cost

When you would use it: multi-cloud agents (some on Agentforce, some on Bedrock or Copilot) that must work together safely, or business teams who need simple SaaS-to-SaaS automations without IT for every change (Composer). The throughline: as agents multiply, MuleSoft sells itself as the governance + connectivity fabric that keeps them discoverable, routed, and compliant. Note the MuleSoft AI Gateway also acts as an MCP gateway for centralized control across all your MCP servers, not just Salesforce, which pairs naturally with the hosted MCP servers from Module 13.02.


5. Interview Q&A

Q: What is MuleSoft Agent Fabric? A: A solution to discover, orchestrate, govern, and observe any AI agent, regardless of where it was built. Four parts: Registry (catalog), Broker (routing), Governance (guardrails), Visualizer (observability). It tackles "agent sprawl" the way MuleSoft tackled API sprawl.

Q: Where does API-led connectivity fit now? A: Underneath. Agents route through the API Catalog to approved APIs, which still sit on System / Process / Experience layers. The layered model is the safe, reusable foundation agents call into.

Q: What is MuleSoft Composer and who is it for? A: A no-code integration tool inside Salesforce for business users (citizen integrators). Clicks, not code, with connectors for Slack, Jira, NetSuite, and more, building on IT-provided assets. Great for SaaS-to-SaaS automations, not a replacement for full Anypoint work.

Q: An agent should only call approved APIs. How? A: Publish them in the API Catalog (with MuleSoft Sync) as the governed registry, and enforce policy through Agent Governance and the AI Gateway. The agent discovers sanctioned endpoints rather than arbitrary ones. API Governance for Salesforce (Beta) extends those rules onto Salesforce-built APIs.

Q: Agent Fabric or Salesforce hosted MCP servers? A: Different layers. Hosted MCP servers expose one Salesforce org's tools to MCP clients. Agent Fabric governs and routes many agents across many clouds. They complement each other, and MuleSoft AI Gateway can govern MCP servers centrally.

Talking point to explain it to anyone: "Companies suddenly have AI agents everywhere, like dozens of new employees nobody hired through HR. MuleSoft is the HR-plus-air-traffic-control: it registers them, decides who handles what, and enforces the rules."


6. Key terms

Agent Fabric (govern any agent), Agent Registry / Broker / Governance / Visualizer (the four parts), agent sprawl (uncontrolled agent growth), A2A (agent-to-agent protocol), MuleSoft Composer (no-code citizen-integrator tool), API Catalog (approved-API registry), API-led connectivity (System/Process/Experience layering). iPaaS and middleware basics live in ../10-Tools-Middleware/06-middleware-and-ipaas.md.


Sources (Verified June 2026)


Next: 04-platform-and-api-additions.md - new platform and API capabilities for integration.

04 - Platform & API Additions (new integration capabilities)

One-liner: Several APIs matured in 2026: Named Query API turns SOQL into a scalable REST resource, Pub/Sub API is the default event API with Managed Event Subscriptions for server-side replay, and Data 360 APIs unify ingestion and query. Why it matters: These are the building blocks you will reach for in new integrations, and the ones agents call as tools (see 02-agentforce-mcp-and-integration.md). Status: API v67.0 (Summer '26). Named Query API is GA (after Winter '26 Beta). Managed Event Subscriptions is Beta. Data 360 APIs are GA. Flags below. Note: Platform Event / CDC retention is 72 hours, the "96 hours" you may see elsewhere is wrong.

This is Module 13, awareness depth. For deeper coverage see Data Cloud APIs, Event-Driven, and the modern API landscape.


1. The idea in plain English

Three retrieval-and-events building blocks got better, and the reason is the same as everywhere in 2026: things must be callable by apps and by agents, securely and at scale.

  • Need to read data? A new Named Query API lets you define a SOQL query once and expose it as a fast REST resource, no Apex, no Flow.
  • Need events? The Pub/Sub API is now the default subscriber API, and it can track your replay position for you.
  • Need unified customer data? Data 360 (formerly Data Cloud) has a full API surface for ingestion, query, profiles, and insights.

2. What's new (the specifics)

Capability What it is Status (June 2026)
Named Query API Define a SOQL query, expose it as a REST resource and an agent action, no code GA (Beta in Winter '26)
Pub/Sub API gRPC event API for publish and subscribe, the default event API GA
Managed Event Subscriptions Server-side replay tracking, Salesforce keeps your Replay ID Beta
Streaming API (CometD) Legacy event subscriber API Legacy, avoid for new builds
Data 360 APIs Ingestion, Query, Profile, Calculated Insights, Connect GA
Platform Events capacity Higher daily delivery on top tiers, 72-hour retention GA
GraphQL chained mutations Reference an earlier operation's fields in one request GA (v67.0)
LWC State Managers / API v67.0 State lives outside components, lightning/accApi drives Agentforce panel GA (v67.0)

Named Query API

You provide a label, an API version, and a SOQL query, and bind parameters from the query string, for example WHERE Name = :name and LIMIT :maxrecords. Salesforce validates it and surfaces it as a reusable REST resource. It is faster than Flow or Apex for retrieval because it is a thin, optimized query path, no automation overhead. The same definition can be viewed in the API Catalog, invoked as a REST API, downloaded as an OpenAPI document, and turned into an agent action. This is the cleanest way to give apps and agents safe, parameterized read access without writing code.

Pub/Sub API and Managed Event Subscriptions

Pub/Sub API is the modern gRPC API for both publishing and subscribing to Platform Events and Change Data Capture, with efficient binary Avro payloads. It is now the default, Streaming API (CometD) is legacy, keep it only for existing subscribers.

Historically a subscriber had to store its own Replay ID to resume after a disconnect. Managed Event Subscriptions (Beta) flips that: you define a ManagedEventSubscription (via Tooling or Metadata API) with a topic and a defaultReplay position, and Salesforce maintains the replay store on the server side. Your client can be truly stateless, no replay bookkeeping.

Data 360 APIs

Data 360 (rebranded from Data Cloud in Oct 2025) unifies customer data and exposes it through a full API surface:

  • Ingestion API - stream or batch data into Data 360.
  • Query API - run Data 360 SQL against unified data (the new SET OPTIONS clause sets the dataspace and NULL-handling for data lake objects).
  • Profile API - read unified customer profiles.
  • Calculated Insights API - query precomputed metrics (dimensions and measures).
  • Connect API - high-performance ingest plus query and insight management.

Deeper treatment lives in ../08-Modern-APIs/03-data-cloud-apis.md.

Platform Events capacity, and the 72-hour fact

Top tiers get higher daily event delivery allocations in 2026 for high-volume event-driven apps. The number to memorize correctly:

Platform Events and Change Data Capture are retained for 72 hours. This is the window in which Pub/Sub API can replay events. You will see "96 hours / 4 days" in some blog posts (and this vault's old README) - that is incorrect. The official Pub/Sub API "Event Message Durability" doc states 72 hours.


3. How it works (retrieval and events)

flowchart LR subgraph CALLERS[Callers] APP[App or Service] AGENT[AI Agent via MCP] end subgraph READ[Read path] NQ[Named Query API<br/>parameterized SOQL] D360[Data 360 Query API<br/>Data 360 SQL] end subgraph EVENTS[Event path] PS[Pub/Sub API<br/>gRPC plus Avro] MES[Managed Event Subscription<br/>server-side replay] end APP --> NQ AGENT --> NQ APP --> PS NQ --> SF[(Salesforce Data)] D360 --> DATA[(Data 360 unified data)] PS --> BUS{{Event Bus<br/>72h retention}} MES --> BUS BUS --> SUB[Subscribers] style NQ fill:#00a1e0,color:#fff style PS fill:#0d9dda,color:#fff style MES fill:#ff9900,color:#fff style BUS fill:#9c27b0,color:#fff

Read path: callers (apps or agents) hit a Named Query for parameterized SOQL, or the Data 360 Query API for unified data. Event path: publishers and subscribers use Pub/Sub API, the event bus retains messages 72 hours, and a Managed Event Subscription lets Salesforce track the replay position so the subscriber stays stateless.


4. What it means for integration work

Need Reach for Why
Parameterized read for an app or agent Named Query API No code, faster than Flow/Apex, OpenAPI + agent action for free
Subscribe to events (new build) Pub/Sub API The default, efficient gRPC + Avro, Streaming API is legacy
Resume events without client state Managed Event Subscriptions (Beta) Salesforce keeps your Replay ID
Unified customer data in or out Data 360 APIs One surface for ingest, query, profile, insights

Quick dev-improvement notes relevant to integration (all v67.0, GA unless flagged): GraphQL chained mutations create linked records in one round trip, cutting callouts. Apex now defaults to user mode and with sharing, so callout-and-DML logic enforces the running user's permissions by default, exactly the posture agent-called actions need. LWC State Managers move state out of components, and the new lightning/accApi module lets an LWC open and drive the Agentforce side panel.

Net: new integrations should default to Named Query for reads, Pub/Sub for events, and Data 360 APIs for unified data, all of which double as agent tools via hosted MCP servers.


5. Interview Q&A

Q: What is the Named Query API and when would you use it over Flow or Apex? A: It exposes a predefined, parameterized SOQL query as a REST resource with no code. Use it for retrieval when you would otherwise write a Flow or Apex just to return records, it is faster, comes with an OpenAPI doc, appears in the API Catalog, and can be an agent action.

Q: Pub/Sub API or Streaming API for a new subscriber? A: Pub/Sub API. It is the default modern gRPC API with efficient Avro payloads for Platform Events and CDC. Streaming API (CometD) is legacy, only keep it for existing integrations.

Q: What problem do Managed Event Subscriptions solve? A: Clients no longer have to store their own Replay ID. With a ManagedEventSubscription, Salesforce maintains the replay store server-side, so the subscriber can be stateless and resume cleanly. It is Beta.

Q: How long are Platform Events retained? A: 72 hours. That is the Pub/Sub API replay window. (Some sources say 96, that is wrong.)

Q: What are the main Data 360 APIs? A: Ingestion (data in), Query (Data 360 SQL out), Profile (unified profiles), Calculated Insights (precomputed metrics), and the high-performance Connect API. Data 360 is the Oct 2025 rebrand of Data Cloud.

Talking point to explain it to anyone: "We made the common jobs one-liners. Reading data is a saved query you call by name, listening for changes is a single modern pipe that remembers where you left off, and all of it doubles as a tool an AI agent can use."


6. Key terms

Named Query API (SOQL as a REST resource and action), Pub/Sub API (default gRPC event API), Managed Event Subscription (server-side replay tracking), Replay ID (event position marker), Avro (binary payload format), Data 360 (unified data platform, ex-Data Cloud), 72-hour retention (event replay window). Events are covered in Module 06, Data 360 APIs in Module 08.


Sources (Verified June 2026)


Next: back to the README.md for the full Module 13 map.

Salesforce Integration · Complete Vault Handbook · 13 modules · 114 files · Built from official Salesforce documentation · June 18, 2026