If your Apex still builds an Authorization header by hand, or keeps an endpoint URL in a custom setting, there’s a cleaner way. Salesforce Named Credentials and External Credentials let Apex and Flow make outbound callouts with no URL, token or secret in code, and the platform handles token caching for you. Salesforce has recommended this extensible model since Winter ‘23. Legacy named credentials still run, but they no longer get updates. Below you’ll find the setup order, every authentication option, the step most first attempts miss, and the limits that start to matter as traffic grows.
A callout is only one way to connect systems, so it’s worth confirming it’s the right one; choosing a Salesforce integration pattern compares it with events and virtualization. The transaction limits around callouts sit alongside the rest in the Apex governor limits guide.
First, check the direction
Ask who is calling whom before you configure anything. Mixing up the two sides is the most common integration setup mistake, and the wrong answer looks plausible in a design document.
| Direction | What you configure |
|---|---|
| Inbound (the external system calls Salesforce) | A Connected App or External Client App, OAuth policies, callback URL and permitted users |
| Outbound (Salesforce calls the external system) | A Named Credential, an External Credential, a certificate where needed, and a permission set |
A Connected App does nothing for an outbound callout.
The seven setup steps
- Certificate. In Setup, open Certificate and Key Management and create or import the certificate that holds the signing key (needed for JWT-based protocols).
- External Credential. Choose the authentication protocol and variant, then fill in the token endpoint, scopes and JWT claims, and select the certificate.
- Principal. Add a principal to the External Credential. Use a Named Principal for server-to-server integrations, where the whole org uses one identity. Use Per User only when a real person is authenticating interactively.
- Named Credential. Give it a developer name and base URL, enable it for callouts, link the External Credential, and turn on Generate Authorization Header.
- Permission set. Grant External Credential Principal Access for your principal and assign the permission set to every user that runs the callout.
- Apex. Point the request at the Named Credential and nothing else.
- Environments. Create the Named Credential with the same developer name in every org and change only the URL. The Apex stays identical from sandbox to production.
HttpRequest req = new HttpRequest();
req.setEndpoint('callout:Partner_API/orders');
req.setMethod('GET');
req.setTimeout(30000); // set it explicitly; the default is 10 seconds
HttpResponse res = new Http().send(req);
Step 5 is the one that gets skipped
When the permission set is missing, every screen in Setup looks correct and the callout still fails. If a new Named Credential throws on its first run, check External Credential Principal Access before you touch anything else.
Setup labels for Named and External Credentials have moved between releases, so confirm the exact screen names in Salesforce Help for your org’s version.
Authentication protocols
The protocol lives on the External Credential.
| Protocol | What to know |
|---|---|
| OAuth 2.0 | Five variants (listed below). An authorization provider issues the token to Salesforce |
| JWT | Salesforce signs the token directly. The subject is a string for a Named Principal and a formula for Per User. Users can’t see or edit the options |
| AWS Signature Version 4 | The identity type must be Named Principal |
| Basic | Static username and password plus permission set assignments. Available only on the extensible model |
| Custom | You define the permission set, sequence number and authentication parameters. Available only on the extensible model |
| No Authentication | Available on both legacy and extensible named credentials |
| Password | Legacy named credentials only. On the extensible model, use Custom with custom headers |
Variants
OAuth 2.0
| Variant | How it works |
|---|---|
| Browser Flow | The user signs in to the remote system in a browser and the callback returns the tokens. Also known as the Authorization Code grant |
| Client Credentials with Client Secret Flow | The client ID and secret are exchanged for a token |
| Client Credentials with Client Secret Flow Managed by External Auth Identity Provider | Same exchange, with the ID and secret stored in an external auth identity provider |
| Client Credentials with JWT Assertion | The client ID and a signed JWT assertion are exchanged for a token |
| JWT Bearer Flow | A signed JWT goes to the authorization server and a token comes back. Called JWT Token Exchange on legacy named credentials |
AWS Signature Version 4
| Variant | How it works |
|---|---|
| Roles Anywhere | Temporary, limited-privilege credentials issued through IAM roles, using a certificate |
| IAM User | Temporary, limited-privilege credentials for an AWS IAM user |
The common server-to-server answer: if the integration uses a private key and a signed assertion, set the protocol to OAuth 2.0 and the variant to JWT Bearer Flow.
Packaging note: signing certificates don’t travel in packages. For JWT or JWT Bearer Flow, recreate the certificate in the subscriber org before installing.
Token caching and the authorization request limit
Salesforce caps OAuth authorization requests at roughly 3,600 per user per hour. Two things follow from that:
- The limit is per user. An integration user that carries all your traffic concentrates the risk in one place.
- Requesting a fresh token for every callout ties your token count to your call count. Once any hour carries more than about 3,600 callouts from that user, the integration starts failing on authentication instead of on the callout itself.
The fix is to cache the token and reuse it until it is close to expiry. Named Credentials do this for you: the platform creates the assertion, gets the token, caches it and refreshes it. If you hand-roll the callout, you also have to hand-roll the cache, and that is where these limit breaches usually come from.
Callout limits
| Limit | Value |
|---|---|
| Callouts per transaction | 100 |
| Default timeout when none is set | 10 seconds |
| Maximum timeout per callout | 120,000 ms |
| Cumulative callout time per transaction | 120 seconds |
| Async jobs enqueued per synchronous transaction | 50 |
Rules that trip people up:
- No callout after uncommitted DML. You get
CalloutException: You have uncommitted work pending. Make the callout first, or move it to async Apex. - Triggers can’t make callouts. Hand the work to a Queueable that implements
Database.AllowsCallouts. - Prefer Queueable over
@futurefor callouts. Queueable accepts complex types, returns a job ID, supports chaining and can attach a Finalizer. The async Apex guide covers the trade-offs. - Don’t rely on the 10-second default. Set a timeout that matches the endpoint’s real latency at the 99th percentile.
Walking someone through the setup
When someone describes an outbound integration and asks what to configure, start by confirming the direction, then walk the seven steps in order and call out the permission set. Mentioning that Named Credentials cache tokens, and why that matters for the per-user authorization limit, shows you have run one in production.