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.
Explore the Vault 13 modules · read in order or jump to any topic
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
- Start with 01-what-and-why-of-integration.md for the big picture and the three questions every integration answers.
- Lock in the vocabulary with 02-core-vocabulary.md, then work through the "vs" files (they are short and high-yield).
- Finish with 09 (the capstone model) and 10 (why Salesforce is special).
- 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.
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
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)
- Integration Patterns and Practices (v66.0) — Salesforce Architect
- Platform Multitenant Architecture — Salesforce Architect
- SOAP API login() Retirement — Salesforce Help
- Salesforce Developers — API documentation
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.
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.
- Who is talking? Salesforce calling out, an external system calling in, or both. (See 06-inbound-vs-outbound.md.)
- How do they prove identity? The authentication layer. (Whole of Module 03.)
- 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)
- Integration Patterns and Practices (v66.0, Spring '26) — Salesforce Architect
- Platform Multitenant Architecture — Salesforce Architect
- Salesforce Developers — API overview
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.
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 asinstance_urlwhen 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) andContent-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, asuccessflag, and an emptyerrorsarray.
7. How it shows up in Salesforce
- The base URL is your My Domain instance (e.g.
acme.my.salesforce.com), returned asinstance_urlafter login. - Almost every data endpoint sits under
/services/data/vXX.X/. ThevXX.Xis 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)
- REST API Developer Guide — Create a Record (v66.0)
- REST API Developer Guide — Working with Records (HTTP methods)
- Salesforce Developers — API overview
- HTTP response status codes — MDN
- HTTP request methods — MDN
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.
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, thoselogin()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 callslogin(), 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)
- Platform SOAP API login() Retirement — Salesforce Help
- SOAP API login() Call in Versions 31.0-64.0 Is Being Retired — Release Notes
- SOAP API End-of-Life Policy — SOAP API Developer Guide
- Which API Do I Use? — Salesforce Help
- REST API Developer Guide — Introduction
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.
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.xmlmanifest for a Metadata API deploy is pure XML with namespaces. A record returned fromGET /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)
- REST API Developer Guide — Introduction (JSON default)
- Metadata API Developer Guide — Introduction (XML)
- SOAP API Developer Guide — Introduction (XML/WSDL)
- JSON — MDN Web Docs
- XML introduction — MDN Web Docs
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.
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.
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.
@futuremethods. Mark an Apex method@futureand it runs later in its own background context. The classic way to make a callout from a trigger without blocking.- Queueable Apex. Like
@futurebut 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
@futurefor 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)
- Bulk API 2.0 — Salesforce Developers (v66.0, Spring '26)
- Asynchronous Processing — Salesforce Architects Decision Guide
- Callout Limits and Limitations — Apex Developer Guide
- Pub/Sub API — Salesforce Developers
- Future Methods — Apex Developer Guide
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).
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
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 (webservicekeyword). 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)
- Integration Patterns and Practices (v66.0, Spring '26) — Salesforce Architect
- REST API Developer Guide — Salesforce Developers
- Named Credentials as Callout Endpoints — Apex Developer Guide
- Outbound Messaging — SOAP API Developer Guide
- Authorize Apps with OAuth — Salesforce Help
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.
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).
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)
- Pub/Sub API — Salesforce Developers
- Platform Events Developer Guide — Salesforce Developers
- Change Data Capture Developer Guide — Salesforce Developers
- Outbound Messaging — SOAP API Developer Guide
- Subscribe to Platform Events with Pub/Sub API — Salesforce Developers
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.
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.
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.
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)
- What is MuleSoft? - Salesforce
- MuleSoft Anypoint Platform - Salesforce
- Integration Patterns and Practices (v66.0, Spring '26) - Salesforce Architect
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:
- A way to travel - the postal road, a truck, a plane. (Transport)
- A language inside the envelope - English, French, a shared alphabet. (Format)
- 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)
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:
- Transport - the form posts over HTTPS so the data is encrypted in transit.
- Format - the body is JSON:
{ "LastName": "Khan", "Company": "Acme" }. - Auth - the server attaches an OAuth 2.0 Bearer token in the
Authorizationheader.
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)
- Which API Do I Use? - Salesforce Help
- REST API Developer Guide (v66.0, Spring '26) - Salesforce Developers
- Pub/Sub API Documentation - Salesforce Developers
- SOAP API Developer Guide - Salesforce Developers
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
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
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 |
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)
- Platform Multitenant Architecture - Salesforce Architect
- API Request Limits and Allocations - Salesforce Developer Limits Quick Reference
- Execution Governors and Limits - Apex Developer Guide
- Callout Limits and Limitations - Apex Developer Guide
- API Limits and Monitoring Your API Usage - Salesforce Developers Blog
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
- Read the patterns in order (01 → 06). Each is short and built the same way.
- Use the decision tree and comparison table below as your interview cram sheet.
- 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)
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)
- Integration Patterns and Practices (v66.0, Spring '26) — Salesforce Developers
- Pattern Selection Guide — Salesforce Developers
- Pattern Summary — Salesforce Developers
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)
Walkthrough
- A user action or process kicks off the transaction.
- Apex or Flow makes an outbound HTTP callout (through a Named Credential, so no hardcoded secrets).
- Salesforce waits synchronously for the reply. The transaction is paused.
- The external system returns data.
- Salesforce parses it and acts (saves a field, makes a decision).
- 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)
- Request and Reply - Integration Patterns and Practices (v66.0)
- Pattern Selection Guide - Salesforce Developers
- Apex Callouts - Apex Developer Guide
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)
Walkthrough
- A user action or backend process completes some work.
- Salesforce publishes a platform event to the event bus (or sends a one-way Outbound Message).
- Salesforce does not block — control returns to the user/process right away.
- The transaction finishes normally.
- The event bus delivers the message to each external subscriber asynchronously, on their schedule.
- Each subscriber processes independently and owns its own error handling.
- 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)
- Remote Process Invocation - Fire and Forget (Integration Patterns, v66.0)
- Platform Events Developer Guide
- Platform Event Allocations
- Considerations for Outbound Messages - Salesforce Help
- Pub/Sub API - Event Message Durability
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)
Walkthrough
- A scheduler (cron, middleware, or a Salesforce scheduled job) kicks off the run during off-peak hours.
- Delta sync: extract only records that changed since the last run (using a timestamp or high-water mark), not the whole table.
- A middleware/ETL layer transforms and maps fields between the two data models.
- The data is loaded asynchronously in batches via Bulk API 2.0.
- The load uses upsert keyed on an External ID so each row either updates a match or inserts a new record.
- Bulk API returns per-batch success and error results.
- 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)
- Batch Data Synchronization (Integration Patterns, v66.0)
- Bulk API 2.0 - Developer Guide
- Upsert Records (Bulk API 2.0, External ID)
- API Request Limits and Allocations - Quick Reference
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)
Walkthrough
- The external app authenticates to Salesforce with OAuth, using credentials from a Connected App / External Client App.
- Salesforce returns an access token and the instance URL to call.
- The app sends the actual request (e.g. create a Lead) with
Authorization: Bearer <token>. - Salesforce enforces authentication, the user's sharing/permissions, and validation rules.
- The CRUD operation runs against the database.
- Salesforce returns the result.
- The app receives an HTTP status code and body (e.g.
201 Createdwith 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 APIlogin()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 SOAPlogin(). Plan inbound integrations on OAuth, notlogin().
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)
- Remote Call-In (Integration Patterns, v66.0)
- Exposing Apex Classes as REST Web Services
- Platform SOAP API login() Retirement (Summer '27) - Salesforce Help
- API Request Limits and Allocations - Quick Reference
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)
Walkthrough
- A user opens an External Object list view, report, or detail page.
- Salesforce hands the request to Salesforce Connect and the configured adapter.
- The adapter issues a live callout to the source (an OData query for the OData adapter, a REST call for cross-org/Apex).
- The source returns only the rows for that screen (paged).
- Salesforce maps the response onto the External Object's fields.
- 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)
- Data Virtualization - Integration Patterns and Practices (v66.0, Spring '26)
- OData Adapters for Salesforce Connect - Salesforce Help
- Get Started with the Apex Connector Framework - Apex Developer Guide
- Salesforce Platform Features Supported by Salesforce Connect - Salesforce Help
- General Limits for Salesforce Connect - Salesforce Help
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)
Walkthrough
- A record changes in Salesforce, or an external system decides something happened.
- An event is published. Use Change Data Capture (CDC) for record changes, or a custom Platform Event for business signals.
- The event lands on the event bus, which stores and sequences it and tags each with a replay ID.
- Subscribers receive a push. Inside the Salesforce UI, an LWC subscribes via
lightning/empApiover CometD. Outside Salesforce, services subscribe via the Pub/Sub API. - 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
empApirides 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)
- UI Update Based on Data Changes - Integration Patterns and Practices (v66.0, Spring '26)
- Pub/Sub API - Get Started - Salesforce Developers
- lightning/empApi - Lightning Component Library
- Change Data Capture Developer Guide - Salesforce Developers
- Message Durability - Streaming API Developer Guide
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
- Read 01-authentication-fundamentals.md once. It is the spine.
- Walk the flows in order (02 → 12), or jump to the one you need via the table below.
- Read the four ecosystem files (13 → 17) — these are the containers and surrounding plumbing interviewers love to probe.
- 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?"
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)
- Authorize Apps with OAuth — Salesforce Help
- OAuth Authorization Flows — Salesforce Help
- External Client Apps and Connected Apps — Salesforce Help
- Retirement of OAuth 2.0 Username-Password Flow — Release Notes
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.
| 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 |
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 requestrefresh_token(oroffline_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 legacytest.salesforce.comhost maps to sandboxes;login.salesforce.comto 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:
- It is the issuer of your tokens — external IdPs and partners pin to it.
- Enhanced Domains changed the URL format; old hardcoded
*.salesforce.comhostnames break. Always build URLs from My Domain. - SSO, branded login pages, and many newer flows (Client Credentials, Auth Code & Credentials) require My Domain.
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_challengepair 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
webscope).
Sources (Verified June 2026)
- OAuth Tokens and Scopes — Salesforce Help
- Authorize Apps with OAuth — Salesforce Help
- OAuth Endpoints — Salesforce Help
- OpenID Connect Token Introspection Endpoint — Salesforce Help
- External Client Apps and Connected Apps — Salesforce Help
- Retirement of OAuth 2.0 Username-Password Flow — Release Notes
- My Domain — Salesforce Help
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 ifopenid).
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)
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.
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
- Create a Connected App (or External Client App — preferred). Enable OAuth Settings.
- Set the Callback URL to exactly your
redirect_uri. - Select scopes (e.g.
Access and manage your data (api),Perform requests at any time (refresh_token, offline_access)). - Keep "Require Proof Key for Code Exchange (PKCE)" checked.
- Copy the Consumer Key (client_id) and Consumer Secret (client_secret).
- 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)
- OAuth 2.0 Web Server Flow for Web App Integration — Salesforce Help
- Implement the OAuth 2.0 Web Server Flow — Trailhead
- OAuth Tokens and Scopes — Salesforce Help
- PKCE (RFC 7636)
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)
Walkthrough
- The app sends the user's browser to the authorize endpoint with
response_type=token, itsclient_id, theredirect_uri, requestedscope, and a randomstate(CSRF guard). 2-3. Salesforce authenticates the user (password, MFA, or SSO) and shows the consent screen. - Salesforce redirects the browser back to the
redirect_uriwith 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. - The app's JavaScript reads the token from
window.location.hash. Best practice: immediately callwindow.location.replace()to strip the token out of browser history. 6-7. The app calls the API directly withAuthorization: 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 fromRefererheaders. 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
- Create a Connected App (or External Client App). Enable OAuth Settings.
- Set the Callback URL to exactly your
redirect_uri. - Select scopes. For the user-agent flow you typically need
apiandweb. Addrefresh_token/offline_accessonly if you intend to receive a refresh token (and accept that storing it in a browser is risky). - Because this is a public client, the Consumer Secret is not sent. Do not embed it in client code.
- 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)
- OAuth 2.0 User-Agent Flow for Desktop or Mobile App Integration — Salesforce Help
- OAuth 2.0 User-Agent Flow — Mobile SDK Development Guide
- OAuth 2.0 Hybrid User-Agent Token Flow for Web Session Management — Salesforce Help
- Block Authorization Flows to Improve Security — Salesforce Help
- OAuth 2.0 Implicit Grant (RFC 6749 §4.2)
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)
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. Keepexpshort (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:
- Create a Connected App (or External Client App — preferred). Enable OAuth Settings.
- Check "Use digital signatures" and upload
server.crt. - Select OAuth scopes (e.g.
api,refresh_tokenis irrelevant here). A callback URL is required by the form but unused by this flow. - Copy the Consumer Key (this becomes
iss/client_id). - 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
subuser 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)
- OAuth 2.0 JWT Bearer Flow for Server-to-Server Integration — Salesforce Help
- Authorize an Org Using the JWT Flow — Salesforce DX Developer Guide
- org login jwt — Salesforce CLI Command Reference
- Create a Private Key and Self-Signed Digital Certificate — Salesforce DX Developer Guide
- OAuth 2.0 Bearer Flow JWT Claims — Named Credentials Reference
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)
Walkthrough
- The app POSTs to the token endpoint (on your My Domain host) with
grant_type=client_credentialsplus itsclient_idandclient_secret. - Salesforce validates the secret and resolves the run-as integration user configured on the app.
- 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.comandtest.salesforce.comare not supported for this flow. The token endpoint must behttps://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
- Create a Connected App (or External Client App — preferred) and enable OAuth Settings.
- Check "Enable Client Credentials Flow."
- Select the scopes you need — for pure API access,
apiis enough (least privilege). - Copy the Consumer Key and Consumer Secret (via Manage Consumer Details).
- 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."
- 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)
- OAuth 2.0 Client Credentials Flow for Server-to-Server Integration — Salesforce Help
- Configure a Connected App for the OAuth 2.0 Client Credentials Flow — Salesforce Help
- Invoke REST APIs with the Salesforce Integration User and OAuth Client Credentials — Salesforce Developers Blog
- Using the Client Credentials Flow for Easier API Authentication — Salesforce Developers Blog
- Give Integration Users API Only Access — Salesforce Help
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), thengrant_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 ifrefresh_token/offline_accessscope 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)
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
- Create a Connected App (or External Client App). Enable OAuth Settings.
- 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
isDeviceFlowEnabledsetting). - Select scopes (
api, plusrefresh_token/offline_accessif you want a refresh token). - A callback URL is not used by the device flow itself, but the app still requires one configured.
- 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)
- OAuth 2.0 Device Flow for IoT Integration — Salesforce Help
- Configure an OAuth 2.0 Device Flow for External Client Apps — Salesforce Help
- Support for OAuth 2.0 Device Flow Authentication Is Being Removed in Data Loader on September 2, 2025 — Release Notes
- Data Loader OAuth 2.0 Device Flow Removal — Salesforce Help
- OAuth 2.0 Device Authorization Grant (RFC 8628)
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)
Walkthrough
- The app already has the user's password stored somewhere. That storage is the core risk.
- The app POSTs to the token endpoint with
grant_type=password, theusername, thepassword(with the user's security token appended), plus the Connected App'sclient_idandclient_secret. There is no browser, no consent screen, and no MFA. - 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. - 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):
- The flow works only with a Connected App (External Client Apps do not support it).
- Enable OAuth Settings and select scopes (e.g.
api). - 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).
- Copy the Consumer Key and Consumer Secret.
- 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)
- OAuth 2.0 Username-Password Flow for Special Scenarios — Salesforce Help
- Retirement of OAuth 2.0 Username-Password Flow for Connected Apps — Release Notes
- OAuth 2.0 Username-Password Flow Blocked by Default in New Orgs — Release Notes
- Migrate from OAuth Username-Password to Client Credentials — Salesforce Help
- Block Authorization Flows to Improve Security — Salesforce Help
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_tokenscope can use this.
3. How it works (sequence diagram)
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_secretas an HTTP BasicAuthorizationheader rather than in the body. Theclient_secretis 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)
- OAuth 2.0 Refresh Token Flow for Renewed Sessions — Salesforce Help
- OAuth 2.0 Refresh Token Flow — Mobile SDK Development Guide
- Manage OAuth Access Policies for a Connected App — Salesforce Help
- OAuth Endpoints — Salesforce Help
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)
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, noid_tokenby 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
- Generate a key pair and self-signed certificate (e.g.
keytool -genkey -keyalg RSA -keysize 2048 ..., export the.crt). - Create a Connected App and enable OAuth Settings.
- Check "Use digital signatures" and upload the certificate (
.crt) — Salesforce stores the public key; your app keeps the private key. - Add at least one OAuth scope (e.g.
api). At least one scope beyond the default is required. - Save and copy the Consumer Key — this becomes the assertion's Issuer.
- 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)
- OAuth 2.0 SAML Bearer Assertion Flow — Salesforce Help
- OAuth 2.0 SAML Bearer Assertion Flow for Previously Approved Apps — Salesforce Help
- Integrating Multiple Orgs using the OAuth 2.0 SAML Bearer Assertion Flow — Salesforce Developers Blog
- SAML 2.0 Profile for OAuth 2.0 Client Authentication and Authorization Grants — RFC 7522
- SAML Bearer Flow — Cloud Sundial
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)
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)
- Configure SAML Single Sign-On in Setup: register your Identity Provider, its certificate, Issuer, and Entity ID.
- Note the org's Login URL and OAuth 2.0 Token Endpoint from the SSO settings page — these are the valid Recipient values.
- Ensure the IdP issues the SAML response with the correct Recipient and Audience for that org.
- 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)
- SAML Assertion Flow for Accessing the API — Salesforce Help
- Configure SSO with Salesforce as a SAML Service Provider — Salesforce Help
- SAML Assertion Flow — Cloud Sundial
- Salesforce Single Sign-On Flows — Cloud Sundial
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)
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):
- Split the JWT into
header.payload.signatureand base64url-decode the header. - Read the
kidfrom the header and fetch the public signing key from the key endpoint: your site's My Domain URL with/id/keysappended. - Verify the JWS signature per RFC 7515, and validate the device binding using the public key in the
cnfclaim. - Use a standard JWT library (RFC 7519). Do not hand-roll signature checks.
Connected App setup checklist (asset token settings)
- Create a Connected App (or External Client App) and enable OAuth Settings.
- Select Enable Asset Tokens in the app's OAuth settings.
- Asset tokens require the
apiandopenidscopes. Add both. - Configure the asset token signing certificate (the cert Salesforce uses to sign the issued asset token) and the audiences the token is valid for.
- 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)
- OAuth 2.0 Asset Token Flow for Securing Connected Devices — Salesforce Help
- Using and Validating Asset Tokens — Salesforce Help
- OAuth 2.0 Token Exchange Flow — Salesforce Help
- AssetTokenEvent — Platform Events Developer Guide
- Enable OAuth Settings for API Integration — Salesforce Help
Next: 12-authorization-code-and-credentials-flow.md — the headless identity flow for fully branded customer login.
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)
Walkthrough
- The developer sets the consumer key/secret in the global file. It is sealed and never enters a package.
- The developer sets callback URL, enabled flows, scopes, and PKCE in the local file, which only references the global file.
- The admin sets runtime policies (who is permitted, session limits, IP rules).
- Only the local + policy metadata go into the 2GP package, so a subscriber installs the app with no exposed secrets.
- 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)
- Setup -> App Manager -> New External Client App (or External Client App Manager).
- Enter name, contact email, and distribution state (Local for this org, or Packageable for 2GP).
- Enable OAuth and set the Callback URL (your exact
redirect_uri). - Select OAuth scopes (for example
api,refresh_token offline_access,openid). - Choose the OAuth flows to enable (web server / PKCE, client credentials, JWT, device — note Username-Password is not offered).
- Keep Require PKCE on for public clients.
- Save, then open Policies and set Permitted Users = Admin-approved users are pre-authorized.
- 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)
- New Connected Apps Can No Longer Be Created in Spring '26 — Salesforce Help
- Comparison of Connected Apps and External Client Apps Features — Salesforce Help
- External Client Apps — Salesforce Help
- External Client Apps and Connected Apps — Salesforce Help
- Connected App to External Client App Migration — Salesforce Help
- Configure the External Client App OAuth Settings — Salesforce Help
- Configure External Client App Policies — Salesforce Help
- Packageable External Client Apps — Salesforce Help
- Configure Canvas Plugin for External Client App (Spring '26) — Release Notes
- Create an External Client App — Mobile SDK Developer Guide
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_Credentialand 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 |
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)
Walkthrough
- Apex (or Flow) issues a callout to
callout:My_Named_Credential/path. - The Named Credential resolves which External Credential and principal apply, using the running user's permission-set mapping.
- The External Credential obtains or refreshes the token, or constructs the auth header (OAuth token, API key header, AWS v4 signature, etc.).
- Salesforce injects the Authorization header automatically.
- The request goes to the real endpoint over HTTPS.
- The response returns to Apex. At no point did your code hold a secret or a token.
6. Setup / configuration
Step-by-step (UI)
- Setup -> Named Credentials -> External Credentials -> New.
- Choose the Authentication Protocol (for example OAuth 2.0 — Client Credentials or Custom).
- Add a Principal (Named Principal name, or enable Per-User). Enter the credential's secrets here (client id/secret, key, username/password).
- Map the principal to a permission set (or PS group/profile). This is mandatory.
- Setup -> Named Credentials -> Named Credentials -> New.
- Set the URL (endpoint base) and select the External Credential from step 1.
- Choose callout options: Generate Authorization Header (usually on), and whether to Allow Formulas in HTTP Header / Body if you need merge fields.
- 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 swapscallout:My_Named_Credentialfor 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)
- Authentication Protocols for Named Credentials — Salesforce Help
- Create Named Credentials and External Credentials — Salesforce Help
- Enable External Credential Principals — Salesforce Help
- Map External Credential Principals to Permission Sets — Release Notes
- Use API Keys in Custom Headers with Named Credentials — Salesforce Help
- Create or Edit an AWS Signature v4 External Credential — Salesforce Help
- Named Credentials as Callout Endpoints — Apex Developer Guide
- Create an OAuth Named Credential — Named Credentials Developer Guide
- Populate External Credential Principals — Named Credentials Packaging Guide
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:
- Sign-on INTO Salesforce — a user clicks "Log in with Microsoft" on your Experience Cloud login page.
- 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)
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):
- Register your app with the external IdP. Get the Client ID, Client Secret, Authorize, Token, and User Info endpoint URLs.
- Setup → Quick Find → Auth. Providers → New → Provider Type = OpenID Connect (or Google, Microsoft, etc.).
- Enter a Name and a URL Suffix (used in the generated client URLs).
- Consumer Key = the IdP's Client ID. Consumer Secret = the IdP's Client Secret.
- Paste the Authorize, Token, and User Info endpoint URLs.
- Check Use Proof Key for Code Exchange (PKCE) Extension to harden the flow.
- Set the Registration Handler Apex class (or click Automatically create a registration handler template).
- Set Execute Registration As to a running user. That user must have the Manage Users permission.
- 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)
- Authentication Provider SSO with Salesforce as the Relying Party — Salesforce Help
- Configure an Authentication Provider Using OpenID Connect — Salesforce Developers
- Configure a Predefined Authentication Provider — Salesforce Help
- Create a Custom External Authentication Provider — Salesforce Help
- AuthProviderPluginClass Class — Apex Reference Guide
- AuthProvider — Metadata API Developer Guide
- OAuth 2.0 Browser Flow with a Per User Principal — Salesforce Help
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
openidscope, 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.
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):
- Enable SAML.
- Gather from your IdP: the Issuer (Entity ID), the Identity Provider Login URL, and the IdP's signing certificate.
- Set the SAML Identity Type — Federation ID (recommended, an external-stable key on the User) or the Salesforce Username.
- Set the Entity ID (usually your My Domain URL) and upload the IdP certificate.
- Salesforce shows your ACS URL and Entity ID — give those to the IdP so it can target Salesforce.
- Optionally enable Just-in-Time provisioning to create/update users from the assertion.
- 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.
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)
- SAML SSO with Salesforce as the Service Provider — Salesforce Help
- Configure SSO with Salesforce as a SAML Service Provider — Salesforce Help
- Salesforce as a SAML Identity Provider — Salesforce Help
- Just-in-Time Provisioning for SAML — Salesforce Help
- Just-in-Time SAML Assertion Fields for Salesforce — Salesforce Help
- Configure an Authentication Provider Using OpenID Connect — Salesforce Developers
- Salesforce as an OpenID Connect Identity Provider — Salesforce Help
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)
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.
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)
- Modify Session Security Settings — Salesforce Help
- Session Security — Salesforce Help
- Require High-Assurance Session Security for Sensitive Operations — Salesforce Help
- Connected App IP Relaxation and Continuous IP Enforcement — Salesforce Help
- Manage OAuth Access Policies for a Connected App — Salesforce Help
- Revoke OAuth Tokens Programmatically — Salesforce Help
- OpenID Connect Token Introspection Endpoint — Salesforce Help
- OAuth 2.0 Refresh Token Flow for Renewed Sessions — Salesforce Help
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
- Start with 01-standard-rest-api.md, the default for most integrations.
- Learn the custom options (Apex REST) for when built-in CRUD isn't enough.
- Use the decision tree and comparison table below to choose fast.
- 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)
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)
- REST API Developer Guide (v66.0) — Salesforce Developers
- Apex REST — Apex Developer Guide
- Composite Resources — REST API Developer Guide
- SOAP API login() Retirement — Salesforce Help
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)
Walkthrough
- The external app sends an HTTP request to a versioned endpoint with an OAuth Bearer token.
- Salesforce validates the token, then the running user's permissions, FLS, and validation rules.
- The operation hits the database.
4-5. Salesforce returns a status code and a JSON body (the new
idon 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:
PATCHon 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)
- REST API Developer Guide (v66.0) — Salesforce Developers
- Work with Records — REST API Developer Guide
- Execute a SOQL Query — REST API Developer Guide
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)
Walkthrough
- The external tool downloads the WSDL (Enterprise or Partner) and generates typed client code from it once.
- It sends a SOAP XML envelope over HTTPS to the org endpoint, carrying an OAuth access token for authentication.
- Salesforce validates the token, then the running user's permissions, FLS, sharing, and validation rules.
- The core operation runs against the database.
- 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
sessionIdis just your OAuth access token. Get it from a modern OAuth flow (see section 5), then place it inSessionHeader. You do not need the oldlogin()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)
- SOAP API Developer Guide (v66.0) - Salesforce Developers
- Using the Partner WSDL - SOAP API Developer Guide
- SOAP API login() Call in Versions 31.0 Through 64.0 Is Being Retired (Release Update)
- Platform SOAP API login() Retirement - Salesforce Help
- SOAP API End-of-Life Policy - SOAP API Developer Guide
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)
Walkthrough
- The external app sends an HTTP request with a JSON body and an OAuth Bearer token to your
/services/apexrest/...URL. - Salesforce validates the token, then routes the request based on the HTTP verb to the matching annotated method.
- Your Apex method reads the request via
RestContext.request, validates and applies business logic. - It performs DML, possibly across several objects, as one transaction.
- 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.responsedirectly.
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)
- Apex REST Methods - Apex Developer Guide (v66.0)
- RestResource Annotation - Apex Developer Guide
- Apex REST Basic Code Sample - Apex Developer Guide
- RestContext Class - Apex Reference Guide
- Writing Apex REST Services and When Not To - Salesforce Developers Blog
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
webservicekeyword. 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)
Walkthrough
- You write a global Apex class with one or more
webservice staticmethods containing your logic. - In Setup you open the class and click Generate WSDL. Salesforce produces a WSDL describing your method.
- You hand the WSDL to the external consumer, who generates typed client stubs from it.
- The client sends a SOAP XML request with an OAuth access token as the session ID.
- 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
webserviceand must bestatic. - Methods with
webserviceare inherently global, so the enclosing class must beglobal. - You cannot apply
webserviceto 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)
- Exposing Apex Methods as SOAP Web Services - Apex Developer Guide (v66.0)
- Webservice Methods - Apex Developer Guide
- Considerations for Using the webservice Keyword - Apex Developer Guide
- SOAP API End-of-Life Policy - SOAP API Developer Guide
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)
Walkthrough
- One
POSTcarries a body with acompositeRequestarray of up to 25 subrequests, each tagged with areferenceId. 2-3. Subrequest 1 creates an Account. Its result is stored under itsreferenceId. - Subrequest 2 references the new Id with
@{NewAccount.id}, so the child links to the parent without a second round-trip. - If
allOrNoneistrue, any failure rolls back every completed subrequest. - 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)
- Send Multiple Requests Using Composite (v66.0) — REST API Developer Guide
- Composite Batch — REST API Developer Guide
- Create Nested Records (sObject Tree) — REST API Developer Guide
- Composite Graph Limits — REST API Developer Guide
- allOrNone Parameters in Composite and Collections Requests — REST API Developer Guide
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
Walkthrough
- The external app authenticates with an OAuth Bearer token, exactly like the Standard REST API.
- It calls a feature-oriented resource under
/connect/or/chatter/, for example posting a feed element. - Connect REST enforces the running user's permissions, sharing, and visibility, then performs the action.
- 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)
- Connect REST API Introduction (v66.0) — Connect REST API Developer Guide
- Build the Resource URL — Connect REST API Developer Guide
- Feeds Resources — Connect REST API Developer Guide
- Experience Cloud Sites — Connect REST API Developer Guide
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
Walkthrough
- The custom app authenticates with an OAuth Bearer token and requests a record-ui resource for a record Id.
- 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).
- All of it is filtered by the running user's permissions and FLS, so the response only contains what they may see.
- 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)
- Get Started with User Interface API (v66.0) — UI API Developer Guide
- Get Record Data and Object Metadata — UI API Developer Guide
- Get Record Layout Metadata — UI API Developer Guide
- Lightning Data Service — Lightning Web Components Developer Guide
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
- Start with 01-http-callouts.md, the core Apex callout.
- Lock in 02-named-credentials-for-callouts.md, the secure way to authenticate.
- Learn when to go async (05) or use a Continuation (06).
- 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)
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)
- Invoking Callouts Using Apex — Apex Developer Guide
- Callout Limits and Limitations — Apex Developer Guide
- Named Credentials — Salesforce Help
- Continuations — Apex Developer Guide
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, andHttpResponseclasses. 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)
Walkthrough
- Apex builds an
HttpRequestwhose endpoint starts withcallout:NamedCredentialName. 2-3. At send time Salesforce swaps in the real base URL and adds the auth header from the Named Credential. - The external API responds.
5-6. Apex receives the
HttpResponseand 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)
- Invoking Callouts Using Apex — Apex Developer Guide
- Callout Limits and Limitations — Apex Developer Guide
- Named Credentials as Callout Endpoints — Apex Developer Guide
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)
Walkthrough
- Apex sets the endpoint to
callout:VendorX/v1/orders. - 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.).
- 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)
- Named Credentials as Callout Endpoints — Apex Developer Guide
- Create Named Credentials and External Credentials — Salesforce Help
- Authentication Protocols for Named Credentials — Salesforce Help
- Use API Keys in Custom Headers with Named Credentials — Salesforce Help
- Map External Credential Principals to Permission Sets — Release Notes
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)
Walkthrough
- You provide the API's OpenAPI schema (JSON, version 2.0 or 3.0) by URL or paste.
- You point the registration at a Named Credential that holds the base URL and auth.
- Salesforce parses each operation into an invocable action, mapping parameters to inputs and the response to outputs.
- An admin drags the action into a Flow and binds the inputs to record fields or variables.
- 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:
- Create the Named Credential (and External Credential) for the API's base URL and auth. See 02-named-credentials-for-callouts.md.
- Setup → External Services → Add an External Service.
- Choose From API Specification, select the Named Credential, and supply the OpenAPI schema (by URL or by pasting the JSON).
- Salesforce validates the schema, then shows the operations it found. Select the ones to import.
- Finish. Salesforce generates one invocable action per selected operation.
- 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
operationIdvalues 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)
- Invoke External Service Callouts — Salesforce Help
- External Services Considerations — Salesforce Help
- OpenAPI 2.0 and 3.0 Support — Salesforce Help
- External Services OpenAPI 3.0 Schema — Salesforce Help
- Learn MOAR in Spring '22: OpenAPI 3.0 Support for External Services — Salesforce Developers Blog
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)
Walkthrough
- You obtain the external service's WSDL (the XML contract).
- In Setup you run Generate from WSDL. Salesforce parses it.
- Salesforce writes Apex stub classes that mirror the operations and types.
- Your Apex calls a generated stub method, passing typed inputs.
- The stub uses the Named Credential for the endpoint and auth, builds the SOAP envelope, and sends it.
- The SOAP reply XML is parsed back into Apex objects you read directly.
4. The setup and code
Generate the stubs
- Create the Named Credential (and External Credential) for the SOAP endpoint and auth. See 02-named-credentials-for-callouts.md.
- Setup → Apex Classes → Generate from WSDL.
- Upload the WSDL file and click Parse WSDL.
- Salesforce proposes one or more Apex class names (one per namespace in the WSDL). Adjust the names, then Generate Apex.
- 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_xtocallout:Named_Credentialso 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)
- SOAP Services: Defining a Class from a WSDL Document — Apex Developer Guide
- Understanding the Generated Code (WSDL2Apex) — Apex Developer Guide
- Create an Apex Class from a WSDL — Salesforce Help
- Testing Web Service Callouts (WebServiceMock) — Apex Developer Guide
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:
- 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. - 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.
- 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.
| 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)
- Asynchronous Apex - Apex Developer Guide (v66.0)
- Queueable Apex - Apex Developer Guide
- Invoking Callouts Using Apex - Apex Developer Guide
- Queueable Apex: More Than an @future - Salesforce Developers Blog
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)
Walkthrough
- The user triggers an
@AuraEnabled(or VF action) method. - The controller creates a
Continuation, sets the callback method name, and adds one to threeHttpRequestobjects whose endpoint iscallout:NamedCredential/.... - 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.
- 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)
- Make Long-Running Callouts with Continuations - Apex Developer Guide (v66.0)
- Asynchronous Callout Limits - Apex Developer Guide
- Making Multiple Asynchronous Callouts - Apex Developer Guide
- Continuations - Lightning Web Components Developer Guide
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. |
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)
- Callout Limits and Limitations - Apex Developer Guide (v66.0)
- Execution Governors and Limits - Apex Developer Guide
- Testing HTTP Callouts - Apex Developer Guide
- Testing HTTP Callouts by Implementing HttpCalloutMock - Apex Developer Guide
- Avoiding the Concurrent Request Limit - Salesforce Developers Blog
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
- Read 01-event-driven-basics.md (event bus, replay, retention).
- Learn the two event sources: Platform Events (you define) and CDC (automatic).
- Learn the modern subscriber API: Pub/Sub API.
- 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)
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)
- Platform Events Developer Guide
- Change Data Capture Developer Guide
- Pub/Sub API — Event Message Durability
- Platform Event Allocations
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.
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)
- Event Message Durability — Pub/Sub API Developer Guide
- Platform Events Developer Guide — Event Bus
- Platform Event Allocations
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)
Walkthrough
- You publish an event instance to its channel,
/event/Order_Shipped__e. - Salesforce persists it to the event bus (time-ordered, 72-hour retention for high-volume events).
- Each event gets a Replay ID marking its position in the stream.
- All subscribers receive the event independently and asynchronously.
- Apex triggers and Flows run server-side; LWC and external apps stream it down.
- 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
SaveResultmeans 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)
- Platform Events Developer Guide - Intro (v66.0)
- Publish Event Messages with Apex
- Subscribe to Platform Event Notifications with Apex Triggers
- Platform Event Allocations
- Standard-Volume Platform Events Retirement
- Event Message Durability - Pub/Sub API Developer Guide
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)
Walkthrough
- A record changes (create, update, delete, or undelete).
- CDC automatically generates a change event, no publish code from you.
- The event lands on the object channel, for example
/data/AccountChangeEvent. - It carries a ChangeEventHeader plus the changed record fields.
- Subscribers (Apex, Flow, or external apps via Pub/Sub API) apply the change.
- 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
changeTypeis suffixed, likeGAP_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)
- Change Data Capture Developer Guide - Intro (v66.0)
- ChangeEventHeader Fields
- Subscription Channels
- Gap Events
- Overflow Events
- Change Data Capture Allocations
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)
Walkthrough
- The client fetches the Avro schema (
GetSchema) and topic info (GetTopic) so it can decode payloads. - It calls
Subscribe, passing aFetchRequestwithnumRequested(how many events it can handle, max 100) and areplayPreset(new-only, earliest retained, or a specific Replay ID). - Salesforce returns up to that many events in one or more
FetchResponsemessages. - Each event carries a Replay ID. The client persists the last one it processed.
- To keep receiving, the client sends another
FetchRequest. It is never sent more than it asked for. That is flow control. - 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)
- Pub/Sub API Developer Guide - Get Started
- Pub/Sub API as a gRPC API
- Event Data Serialization with Apache Avro
- Pull Subscription and Flow Control
- Managed Event Subscriptions (Beta)
- Subscribe RPC Method Reference
- Event Message Durability
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)
Streaming API walkthrough
- You define a PushTopic (a named record holding a SOQL query) or a Generic Streaming channel (for arbitrary user-defined payloads).
- 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).
- The subscriber chooses a replay option:
-1to receive only new events,-2to receive all retained events from the start of the window, or a specific Replay ID to resume from where it left off. - PushTopic events are retained for 24 hours (note: shorter than the 72-hour window for high-volume Platform Events and CDC).
Outbound Messages walkthrough
- A declarative action (a Flow Action element, or a legacy Workflow Rule) triggers when a record meets criteria.
- 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.
- The endpoint must return a positive acknowledgment (an
Ackoftrue). 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)
- Bayeux Protocol, CometD, and Long Polling - Streaming API Developer Guide
- Replay PushTopic Streaming Events - Streaming API Developer Guide
- Message Durability - Streaming API Developer Guide
- Understanding Outbound Messaging - SOAP API Developer Guide
- Understanding Notifications - SOAP API Developer Guide
- Considerations for Outbound Messages - Salesforce Help
- Transition to Flow: Workflow and Process Builder Retirement - Salesforce Help
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)
Walkthrough
- 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.
- The bus assigns a Replay ID and stores the event in time order for 72 hours.
- Each subscriber receives the event. Apex triggers fire
after inserton the event object, in batches of up to 2,000 event messages. - A subscriber that disconnects resumes by passing a replay option:
-1(new only),-2(all retained), or a specific Replay ID it persisted. - 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)
- Publish Event Messages with Apex - Platform Events Developer Guide
- Get the Result of Async Publishing with Apex Publish Callbacks - Platform Events Developer Guide
- Subscribe to Platform Event Notifications with Apex Triggers - Platform Events Developer Guide
- Retry Event Triggers with EventBus.RetryableException - Platform Events Developer Guide
- Subscribe to Platform Event Notifications in a Lightning Component (empApi) - Platform Events Developer Guide
- Managed Event Subscriptions (Beta) - Pub/Sub API
- Subscribe RPC Method (ReplayPreset) - Pub/Sub API
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
- 01-bulk-api-2.md for external large-data loads/extracts.
- 02 for the legacy batch API and chunking huge queries.
- The async Apex tools: Batch, Queueable, @future, Scheduled.
- 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)
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)
- Bulk API 2.0 and Bulk API Developer Guide
- Asynchronous Apex — Apex Developer Guide
- Execution Governors and Limits — Apex Developer Guide
- Limits and Allocations Quick Reference
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)
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)
- Bulk API 2.0 — Bulk API 2.0 and Bulk API Developer Guide
- Bulk API 2.0 Ingest — Developer Guide
- Bulk API and Bulk API 2.0 Limits and Allocations
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
Walkthrough
- Create a job specifying the object, the operation (
insert,update,upsert,delete,hardDelete,query), and the content type. - 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.
- Salesforce processes batches in either parallel (default, fastest) or serial mode, set by
concurrencyModeon the job. - Close the job so no more batches can be added.
- 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)
- Bulk API (1.0) - Bulk API 2.0 and Bulk API Developer Guide
- Work with Batches - Bulk API Developer Guide
- PK Chunking Header - Bulk API Developer Guide
- Use PK Chunking to Extract Large Data Sets - Salesforce Developers Blog
- Bulk API and Bulk API 2.0 Limits and Allocations
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)
Walkthrough
- You kick off the job with
Database.executeBatch(new MyBatch(), scopeSize). start()returns aDatabase.QueryLocator(a query that can stream up to 50 million records) or anIterable.- Salesforce splits those records into chunks, default 200 per chunk (configurable 1 to 2,000 for a QueryLocator).
execute()runs once per chunk. Each run is a separate transaction with governor limits reset, so the job survives volume.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)
- Using Batch Apex - Apex Developer Guide
- Database.Batchable Interface - Apex Reference Guide
- Execution Governors and Limits - Apex Developer Guide
- FlexQueue Class - Apex Reference Guide
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
AsyncApexJobId. 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)
Walkthrough
- From a trigger or Apex, call
System.enqueueJob(new MyQueueable(...)). You can pass it sObjects and other objects directly. - It returns an
AsyncApexJobId, which you can query or watch in Setup to monitor status. - The job sits in the queue, then
execute(QueueableContext)runs in the background with a fresh set of governor limits. - 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. - 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)
- Queueable Apex - Apex Developer Guide
- Queueable Interface - Apex Reference Guide
- Future Methods - Apex Developer Guide
- AsyncApexJob - Object Reference for the Salesforce Platform
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
@futureand 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)
Walkthrough
- Your main transaction (often a trigger) runs and commits its DML.
- You call the
@futuremethod. To Apex this looks like an ordinary method call, but nothing executes yet. - Salesforce queues the request and lets your original transaction finish.
- Sometime soon, the platform runs the future method in a separate transaction with a fresh, higher (async) set of limits.
- 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@futureinside aforloop 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)
- Future Methods — Apex Developer Guide
- Apex Governor Limits — Limits and Allocations Quick Reference
- Execution Governors and Limits — Apex Developer Guide
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
Schedulableinterface 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)
Walkthrough
- You register the class once — via
System.schedule()in Apex or through the Setup UI. - The CRON expression tells the scheduler exactly when to fire.
- The platform watches the clock and does nothing until the scheduled moment arrives.
- At that moment it calls your
execute(SchedulableContext sc)method. - 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)
- Apex Scheduler — Apex Developer Guide
- Schedulable Interface — Apex Reference Guide
- Apex Governor Limits — Limits and Allocations Quick Reference
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
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)
- Execution Governors and Limits — Apex Developer Guide
- Apex Governor Limits — Limits and Allocations Quick Reference
- AsyncApexJob — Object Reference for the Salesforce Platform
- Asynchronous Apex — Apex Developer Guide
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)
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)
- GraphQL API — Salesforce Developers
- sObject Collections — REST API Developer Guide
- Data Cloud — Salesforce Developers
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)
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
aggregateroot, 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)
- GraphQL API — Salesforce Developers
- What is GraphQL API? — Get Started
- Aggregate Queries — GraphQL API
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.
4. How it works (sequence diagram)
Walkthrough
- One
POST(orPATCH/DELETE/GET) carries the batch. For writes, the body has anallOrNoneflag and arecordsarray. - Salesforce processes every record. Each carries its
attributes.typeso the platform knows the object. - With
allOrNone:true, a single failure rolls back the entire batch. Withfalse(the default), good records commit and only the bad ones report errors, partial success. - You get back one array, one entry per input record, in the same order. Each entry has
success, the newid(on create), and anyerrors.
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)
- sObject Collections (v66.0) — REST API Developer Guide
- Create Records Using sObject Collections — REST API Developer Guide
- Update Records Using sObject Collections — REST API Developer Guide
- Upsert Records Using sObject Collections — REST API Developer Guide
- Delete Records Using sObject Collections — REST API Developer Guide
- Get Records Using sObject Collections — REST API Developer Guide
- allOrNone Parameters in Composite and Collections Requests — REST API Developer Guide
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)
Walkthrough
- 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.
- Harmonize. Inside Data Cloud, mapping and identity resolution stitch records into unified profiles and calculate insights. This is platform work, not an API call.
- 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.
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)
- Ingestion API — Data 360 Integration Guide
- Get Started with Ingestion API (auth, scopes, limits) — Data 360 Ingestion API Reference
- Query Data using Query API — Data 360 Query Guide
- Boost Data Cloud Integrations with the New Query Connect API — Salesforce Developers Blog
- Query Customer Profile Information with Profile API — Data 360 Query Guide
- Data 360 Connect API References
- Quick Start — Data 360 Developer Guide
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:
- How much data? One record, a couple hundred, or millions?
- Sync or async? Do you need the answer now, or can you submit a job and collect results later?
- 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?
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)
- Which API Do I Use? — REST API Developer Guide
- REST API Developer Guide (v66.0)
- Bulk API 2.0 and Bulk API Developer Guide
- Pub/Sub API Documentation
- GraphQL API — Salesforce Developers
- Metadata API Developer Guide
- Tooling API
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
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)
- Execution Governors and Limits — Apex Developer Guide
- Configure Salesforce CORS Allowlist — Salesforce Help
- Limits and Allocations Quick Reference
- Shield Platform Encryption — Salesforce Help
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
- 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 theCorsWhitelistOriginmetadata. - CSP Trusted Sites: Setup → CSP Trusted Sites → add the external site and choose the contexts (
connect-src,img-src,script-src, etc.). Managed viaCspTrustedSitemetadata. - 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)
- Configure Salesforce CORS Allowlist — Salesforce Help
- Manage Trusted URLs (CSP) — Salesforce Help
- CspTrustedSite — Metadata API Developer Guide
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
apiscope 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
Walkthrough:
- 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.
- Scope check. The token only works against APIs covered by the scopes it was issued. A token without
apicannot call the REST data API no matter who the user is. - API Enabled check. The running user must have the API Enabled user permission, or the platform rejects all API access with an error.
- 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:
- 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).
- Layer on exactly the object, field, and feature permissions the integration needs via permission sets (assigned with the Salesforce API Integration permission set license).
- 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)
- User Permissions (API Enabled, etc.) - Salesforce Security Guide
- Secure API Access with the New Least-Privilege User Profile - Release Notes
- Give Integration Users API Only Access - Salesforce Help
- Restrict Login IP Addresses in Profiles - Salesforce Help
- Manage OAuth Access Policies for a Connected App - Salesforce Help
- Connected App IP Relaxation and Continuous IP Enforcement - Salesforce Help
- Restrict Access to APIs with Connected Apps - Salesforce Help
- Invoke REST APIs with the Salesforce Integration User and OAuth Client Credentials - Developer Blog
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
Walkthrough:
- 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.
- 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.
- 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 sharingat 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(orinherited sharing) for record-level enforcement. - Query with
WITH USER_MODEand/or sanitize results withstripInaccessible(). - 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)
- Securing Data Access - Salesforce Help
- Use the with sharing, without sharing, and inherited sharing Keywords - Apex Developer Guide
- Set an Access Mode for Database Operations (WITH USER_MODE) - Apex Developer Guide
- Filter SOQL Queries Using WITH SECURITY_ENFORCED - Apex Developer Guide
- Enforce Security with the stripInaccessible Method - Apex Developer Guide
- SObjectAccessDecision Class - Apex Reference Guide
- Exposing Data with Apex REST Web Service Methods (system context) - Apex Developer Guide
- Enforce Object and Field Permissions - Apex Developer Guide
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.
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.
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)
- Set Up a Mutual Authentication Certificate for API Login - Salesforce Help
- Configure Your API Client to Use Mutual Authentication - Salesforce Help
- Certificates in Mutual Authentication for Salesforce - Salesforce Help
- Preferred Alternatives to IP Allowlisting on Hyperforce - Salesforce Help
- Enhance Integration Security with mTLS for Salesforce and MuleSoft - Salesforce Developers Blog
- Strengthen Your Data's Security with Shield Platform Encryption - Salesforce Help
- Filter Encrypted Data with Deterministic Encryption - Salesforce Security Guide
- General Shield Platform Encryption Considerations - Salesforce Help
- Introducing the Salesforce Shield Platform Encryption REST API - Platform Encryption REST API Developer Guide
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.
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/limitsreturns the maximum and remaining allocation for many limits, includingDailyApiRequests. 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.
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)
- Execution Governors and Limits - Apex Developer Guide (v66.0)
- Callout Limits and Limitations - Apex Developer Guide
- API Request Limits and Allocations - Limits and Allocations Quick Reference
- Bulk API and Bulk API 2.0 Limits and Allocations - Limits and Allocations Quick Reference
- Platform Event Allocations - Platform Events Developer Guide
- List Org Limits - REST API Developer Guide
- Scale Your Concurrent Long-Running Apex Requests Limit - Salesforce Release Notes
- API Limits and Monitoring Your API Usage - Salesforce Developers Blog
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)
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?
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)
- Salesforce CLI — Developer Tools
- Data Loader — Developer Tools
- Salesforce Platform APIs — Postman Collection
- MuleSoft Anypoint Platform
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
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
- Install Postman (desktop or web) and sign in.
- Fork the official "Salesforce Platform APIs" collection from the Salesforce Developers workspace on Postman.
- 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.
- 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.
- Postman stores the token (and often the
instance_url) as variables. - 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)
- Salesforce Platform APIs — Postman Collection
- Quick Start: Connected App for the REST API — Salesforce Developers
- REST API Developer Guide (v66.0)
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
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)
- Workbench: About (v66.0.0)
- Workbench Login
- Workbench Documentation Wiki (GitHub)
- Bulk API 2.0 and Bulk API Developer Guide (v66.0)
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(thesfdxexecutable 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
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 withsf 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 jwtat the start of the pipeline, thensf project deploy start --dry-runon 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)
- Salesforce CLI Command Reference (Summer '26)
- data Commands - CLI Command Reference
- project Commands - CLI Command Reference
- Authorize an Org Using the JWT Flow - Salesforce DX Developer Guide
- Execute Anonymous Apex - Salesforce DX Developer Guide
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
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)
- Open Data Loader and click Insert.
- Log in via OAuth and confirm the environment.
- Choose the object (e.g.
Account) and select your CSV. - Map CSV columns to Salesforce fields (save the mapping as an
.sdlfile to reuse). - Choose where to write the success and error CSVs.
- 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:
- Create an encryption key, then encrypt your password with the bundled
encryptutility so no plaintext password sits in a file. - Write a
process-conf.xmldescribing the job: a<bean>per process withsfdc.endpoint,sfdc.username, the encryptedsfdc.password, the operation, the CSV path, and the field-mapping file. - 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)
- Data Loader Guide (v67.0, Summer '26)
- Data Loader Command Line Introduction
- Encrypt from the Command Line - Data Loader Guide
- Data Loader Process Configuration Parameters
- Considerations for Installing Data Loader (Java 17+)
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
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-keyheader 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.xmlby 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)
- Salesforce Extensions for VS Code - Salesforce Developers
- Apex Replay Debugger - Salesforce Developers
- External Services OpenAPI 2.0 and 3.0 Support - Salesforce Help
- ReqRes API docs (x-api-key)
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.
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. |
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)
- What Is API-led Connectivity? - Salesforce
- Enterprise Integration - Anypoint Platform - MuleSoft
- CloudHub Overview - MuleSoft Documentation
- Understanding API-Led Connectivity Essentials - Trailhead
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
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)
- Salesforce Developer Edition signup
- Apex Developer Guide — Integration
- REST API Developer Guide (v66.0)
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
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.
- Setup → Named Credentials → New (External Credential + Named Credential, or a legacy Named Credential).
- Label/Name:
Test_API. - URL:
https://reqres.in(orhttps://httpbin.org). - 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.
- 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
HttpCalloutMocktest 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)
- Invoking Callouts Using Apex — Apex Developer Guide
- Named Credentials as Callout Endpoints — Apex Developer Guide
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
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.
- Setup → Named Credentials → New.
- Label/Name:
Calc_API. - URL: the SOAP service base, for example
https://www.dneonprism.dev(or any public calculator/country-info SOAP host you choose). - Authentication: No Authentication for an open service. For an authenticated SOAP partner, attach an External Credential, see Module 03.
- 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
WebServiceMockimplementation and a test class that assertsadd(2, 3) == 5with 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)
- SOAP Services: Defining a Class from a WSDL Document - Apex Developer Guide
- Understanding the Generated Code - Apex Developer Guide
- Test Web Service Callouts (WebServiceMock) - Apex Developer Guide
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
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.
- Setup → App Manager → New External Client App (or New Connected App).
- 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). - 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.
- Save, then copy the Consumer Key and Consumer Secret.
- In Postman,
POSTtohttps://login.salesforce.com/services/oauth2/token(usetest.salesforce.comfor a sandbox) withx-www-form-urlencodedbody:
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}}andContent-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
- In Postman, run the token request, then the
POST. Confirm 201 and copy the returnedid. - 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'. - Re-send the same
POSTand watch a duplicate appear, then repeat with thePATCHupsert 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)
- Create a Record - REST API Developer Guide
- Upsert Records Using sObject Rows by External ID - REST API Developer Guide
- OAuth 2.0 Client Credentials Flow for Server-to-Server Integration - Salesforce Help
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
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}}andContent-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
- In Postman, run the token request, then the composite
POST. Expect the top-level 200 with acompositeResponsearray of two 201 results, each carrying anid. - Verify the link in Workbench or Developer Console:
SELECT Id, LastName, AccountId, Account.Name FROM Contact WHERE Account.Name = 'Acme Inc'and confirmAccountIdmatches the Account'sid. - Test rollback: set the Contact's
LastNameto an empty string (required field) and resend withallOrNone: true. The whole request rolls back and no Account is created. FlipallOrNonetofalseand 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)
- Composite Request Body - REST API Developer Guide
- Update an Account, Create a Contact, and Link Them - REST API Developer Guide
- Create Nested Records (sObject Tree) - REST API Developer Guide
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
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.
- Setup to App Manager to New Connected App (or New External Client App). Enable OAuth Settings.
- Add the OAuth scopes Manage user data via APIs (api) and Perform requests at any time (refresh_token, offline_access).
- Save, then copy the Consumer Key and Consumer Secret.
- In Postman, POST to
https://MyDomain.my.salesforce.com/services/oauth2/tokenwithgrant_type,client_id,client_secret, and (for a user flow)username+password+securitytoken. Read more in Module 03. - The response gives you
access_tokenandinstance_url. Send the token asAuthorization: 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 OKwith{ "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 toRestContext.request, and invokesCaseService.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
@HttpPatchmethod to update Case Status, and an@HttpDeleteto close it. - Return a custom error body with the right status by setting
RestContext.response.statusCode = 400on bad input. - Accept the raw JSON body via
RestContext.request.requestBodyandJSON.deserializeinto 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)
- RestResource Annotation - Apex Developer Guide
- Apex REST Methods - Apex Developer Guide
- RestContext / RestRequest Class - Apex Reference Guide
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
webservicemethod + 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
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.
- Setup to API to Generate Enterprise WSDL, then Generate and save the file.
- In SOAP UI, File to New SOAP Project, point it at the Enterprise WSDL.
- Run the login operation with your username and
password + security token. Copy the returned sessionId and serverUrl from the response. - The same session id authenticates the custom Apex service call. (You can also use an OAuth
access_tokenas 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
createAccountrequest 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
getAccountwith that Id. - A
INVALID_SESSION_IDfault 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 staticmethod 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)
- Webservice Methods - Apex Developer Guide
- Exposing Apex Classes as SOAP Web Services - Apex Developer Guide
- SessionHeader - SOAP API Developer Guide
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
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.
- CLI route: run
sf org login web --alias myorgonce. The CLI stores and refreshes the token. - Raw REST route: get an
access_tokenandinstance_urlfromPOST /services/oauth2/token(see Module 03). SendAuthorization: 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>/successfulResultsGET .../jobs/ingest/<jobId>/failedResultsreturns each bad row withsf__Idandsf__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 thesf__Errorcolumn 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
Httpclient 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)
- Bulk API 2.0 Ingest Jobs - Bulk API 2.0 and Bulk API Developer Guide
- Bulk API 2.0 Limits and Allocations - Developer Guide
- data Commands (sf data import bulk / upsert bulk) - Salesforce CLI Command Reference
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
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.
- Go to Pipedream (
pipedream.com) and create a new Workflow with an HTTP / Webhook trigger. Copy the generated URL, for examplehttps://eoxxxxxxxx.m.pipedream.net. (Or usewebhook.siteand copy its unique URL.) - 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.
- Setup → quick find Outbound Messages → New Outbound Message.
- Object:
Opportunity. - Name:
Notify_Billing_Closed_Won. - Endpoint URL: paste the Pipedream URL.
- User to send as: pick an integration user (their field-level security governs what is sent).
- Fields to send: select
Id,Name,Amount,StageName,CloseDate.IdandOrganizationIdare always included. - (Optional) tick Send Session ID if the receiver will call back into Salesforce.
- 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.
- Setup → Flows → New Flow → Record-Triggered Flow.
- Object:
Opportunity. Trigger: A record is updated. - Entry conditions:
StageName Equals Closed Won. Set Optimize for: Actions and Related Records. - Add an Action element → search the action type Outbound Message → select
Notify_Billing_Closed_Won. - 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
- Open an Opportunity and set Stage to Closed Won, then save.
- Switch to the Pipedream workflow and watch the inspector: a new event appears with the SOAP XML body within a few seconds.
- Confirm the field values match the record.
- 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)
- Outbound Messaging — Salesforce Help
- Define Outbound Messages — Salesforce Help
- Understanding Outbound Messaging (SOAP Notifications) — SOAP API Developer Guide
- Send an Outbound Message Action — Flow Builder (Salesforce Help)
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
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.
- 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.
- Label/Name:
Numbers_API. - URL: the API base host, for example
https://api.example.com. - Save. The host in your OpenAPI
serversblock 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.
- Setup → quick find External Services → New (or Add an External Service).
- Choose From API Specification.
- Service Name:
NumbersService(this prefixes the generated action API names). - Service Schema - Named Credential: select
Numbers_API. - Schema source: pick Service Schema Complete URL and paste the SwaggerHub URL, or pick Schema and paste the JSON above.
- Click Save & Next. Salesforce parses the spec and lists the operations it found.
- 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.
- Setup → Flows → New Flow → Screen Flow (simplest to test) or an autolaunched flow.
- (Screen Flow) Add a Screen with a Number input variable
inputNumber. - Add an Action element → External Services →
NumbersService→getMathFact. - Map the input number =
{!inputNumber}. - Store the output, then add a second Screen that displays the returned
textfield from the action output. - Save and Activate.
5. Test
- Run the Flow (Flow Builder → Run, or Debug with a sample input).
- Enter a number, for example
42, on the first screen. - The Flow calls the API through the Named Credential and the second screen shows the returned fact text.
- 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
POSToperation 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)
- External Services — Salesforce Help
- OpenAPI 2.0 and 3.0 Support — Salesforce Help
- External Services Considerations and Limitations — Salesforce Help
- Learn MOAR: OpenAPI 3.0 Support for External Services — Salesforce Developers Blog
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) + ApexEventBus.publish()+ an LWC using thelightning/empApimodule. 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
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:
- Permission to create a Platform Event (Customize Application).
- A Lightning page (App, Home, or Record page) where the LWC can be dropped.
- Desktop browser for testing,
empApidoes not run in the Salesforce mobile app (see gotchas).
4. Step-by-step build
Step 1 - Define the Platform Event.
- Setup → quick find Platform Events → New Platform Event.
- Label:
Order Notification. The API name becomesOrder_Notification__e(the__esuffix marks a Platform Event). - 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.
- Add custom fields:
-
Order_Number__c(Text) -Status__c(Text) -Message__c(Text) - 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
- Open the Lightning page with the component, it shows "Waiting for events...".
- In another tab, open Developer Console → Debug → Open Execute Anonymous (or
sf apex run) and run:
OrderNotifier.notifyShipped('ORD-1001');
- Within a second or two, the LWC list updates with ORD-1001 - Shipped with no page refresh.
- 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
-2to 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)
- Subscribe to Platform Event Notifications with the empApi Module — Platform Events Developer Guide
- Emp API (lightning/empApi) — Lightning Web Components Developer Guide
- Publish Event Messages with Apex — Platform Events Developer Guide
- Platform Event Fields and Publish Behavior — Platform Events Developer Guide
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
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:
- Setup → quick-find Change Data Capture.
- Move Account from Available Entities to Selected Entities.
- 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
- In the org, open any Account and change a field (for example, Phone), then Save.
- 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 theChangeEventHeader. - Create a new Account and delete one to see
CREATEandDELETEchange 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_idto a datastore and restart the client withreplay_preset: CUSTOMto 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)
- Pub/Sub API as a gRPC API — Get Started
- Subscribe RPC Method — Pub/Sub API Reference
- GetSchema RPC Method — Pub/Sub API Reference
- Pull Subscription and Flow Control
- Subscribe with Pub/Sub API — Change Data Capture Developer Guide
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
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:
- Setup → App Manager → New Connected App (choose the classic Connected App form, or use an External Client App).
- 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).
- Check Enable Client Credentials Flow and accept the security warning. Save.
- 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.
- 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:
- POST
https://MyDomainName.my.salesforce.com/services/oauth2/token. In Body → x-www-form-urlencoded addgrant_type=client_credentials,client_id,client_secret. Send and confirm a200with anaccess_token. - Alternative: leave the body with onlygrant_type, and put the key/secret under Authorization → Basic Auth. - Copy the
access_token. New request: GET{{instance_url}}/services/data/v66.0/limits, set Authorization → Bearer Token, paste it, send. A200with org limits confirms the token works as the integration user. - 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)
- OAuth 2.0 Client Credentials Flow for Server-to-Server Integration
- Configure a Connected App for the OAuth 2.0 Client Credentials Flow
- OAuth 2.0 JWT Bearer Flow for Server-to-Server Integration
- Invoke REST APIs with the Salesforce Integration User and OAuth Client Credentials — Salesforce Developers Blog
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)
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@futuremethod.- Test classes must use
HttpCalloutMock/WebServiceMockand wrap inTest.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
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/pathskips 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_ROWand 5xx = transient. Retry with exponential backoff.- Always log the
errorCode+messagefrom 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
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.
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)
- Setup → Named Credentials → External Credentials → New → pick protocol.
- Add a Principal (Named, Per-User, or Client Credentials) + enter secrets.
- Map the principal to a permission set (mandatory).
- Named Credentials → New → set URL + select the External Credential.
- Callout options: Generate Authorization Header (usually on); Allow Formulas in HTTP Header/Body only if you need merge fields.
- 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
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
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}}.
Setup steps
- Install Postman (desktop or web) and sign in.
- 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.
- 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(addrefresh_tokenso the token can renew). - Save → copy the Consumer Key and Consumer Secret (wait ~10 min for activation). - 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 |
- Run an OAuth flow on the collection's Authorization tab → Configure New Token:
- Logged-in user → Authorization Code (browser consent via the
pstmn.iocallback). - Server-to-server → Client Credentials (no user; the app's run-as identity). - Click Get New Access Token → Use Token. Postman storesaccess_token(and usuallyinstance_url). - 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 theqparam, 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 theerrorCode, 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
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)
- Spring '26 Release Architect Highlights — Salesforce
- The Salesforce Developer's Guide to the Summer '26 Release
- Platform SOAP API login() Retirement — Salesforce Help
- Event Message Durability — Pub/Sub API
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)
| 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/dataversion 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)
- Platform SOAP API login() Retirement — Salesforce Help
- The Salesforce Developer's Guide to the Summer '26 Release
- Spring '26 Release Architect Highlights
- Retirement of OAuth 2.0 Username-Password Flow — Release Notes
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
@RestResourceendpoints. - AuraEnabled Apex -
@AuraEnabledmethods. - 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)
Walkthrough
- The user asks for something in natural language inside their AI client.
- The client connects to the org's hosted MCP server using OAuth (a dedicated
mcp_apiscope, plus PKCE). Every transaction runs as the authenticated user. - The server is the action router. It advertises only the tools you explicitly exposed, the agent cannot call an API that was not published.
- The chosen tool runs: an Apex action, a Flow, a Named Query, or a MuleSoft-backed endpoint.
- 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)
- Salesforce Hosted MCP Servers Are Now Generally Available - Salesforce Developers Blog
- The Salesforce Developer's Guide to the Summer '26 Release
- Agentforce MCP Support - Salesforce
- MCP Solutions - Agentforce Developer Guide
- Hosted MCP Servers Developer Docs
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)
Walkthrough
- Agents from any platform register in the Agent Registry, which also catalogs MCP and A2A (agent-to-agent) servers.
- A request hits the Agent Broker, which sorts agents and tools into business domains and routes the task to the best-fit agent.
- Agent Governance applies security, compliance, and policy to every hop before anything runs.
- 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.
- 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)
- Salesforce Launches MuleSoft Agent Fabric - Salesforce News
- MuleSoft Agent Fabric - MuleSoft
- Salesforce Expands MuleSoft Agent Fabric with Automated Discovery
- MuleSoft Composer for Salesforce
- Get Started with Agent Fabric - MuleSoft Documentation
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 OPTIONSclause sets the dataspace andNULL-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)
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)
- Named Query APIs for SOQL Queries - REST API Developer Guide
- Named Query API Simplifies Data Access for Agents and Apps - Salesforce Developers Blog
- Event Message Durability (72-hour retention) - Pub/Sub API Guide
- Managed Event Subscriptions (Beta) - Pub/Sub API Guide
- The Salesforce Developer's Guide to the Summer '26 Release
Next: back to the README.md for the full Module 13 map.