I still find API keys in custom settings, endpoints pasted into Apex, and tokens stored in custom metadata "temporarily" since 2021. Every one of those is a security finding waiting to happen, and every one of them breaks the moment the other system rotates a secret. Salesforce solved this problem properly with the Named Credential and External Credential pair, but the split model confuses people who learned the old single-object version. Here is how it actually fits together.

The mental model: two objects, two jobs

The legacy Named Credential bundled everything into one record: URL, auth protocol, credentials. The current model splits it:

  • An External Credential answers "how do we authenticate?" It holds the protocol (OAuth 2.0, JWT Bearer, AWS Signature v4, Basic, or Custom), the parameters for that protocol, and the principals.
  • A Named Credential answers "where are we calling?" It holds the endpoint URL and points at an External Credential for the auth part.

Why the split? Reuse and governance. One External Credential (say, your OAuth connection to an API gateway) can back several Named Credentials pointing at different base paths. And because credentials live in their own object, you can control who sees or uses them with normal permission sets instead of profile spaghetti.

Principals: the part everyone gets wrong

An External Credential has one or more principals, and each principal is a set of actual credentials:

  • A Named Principal is one shared identity for everyone. The org calls the API as a single service account. This is what you want for system integrations, scheduled jobs, and platform automation.
  • Per-User principals store separate credentials for each user. Use this when the external system needs to know which human is acting, for example a document service where each user connects their own account.

Here is the trap: creating the principal is not enough. Users (including your integration user, and yes, including the Automated Process user in some flows) need access to the External Credential principal through a permission set mapping. If you skip this, callouts fail with a vague error and you will spend an hour convinced your OAuth setup is broken. It is not. It is the permission set.

My convention: one permission set per integration, named after it (for example Integration_PaymentGateway), containing the External Credential principal access plus any object permissions the integration needs. Assign it to the integration user and you can answer "what can this integration touch?" in one screen.

Setting one up, end to end

Say we integrate with a payment API that uses OAuth 2.0 client credentials. The steps:

  1. Setup, then Named Credentials, then the External Credentials tab. New External Credential with the OAuth 2.0 protocol and Client Credentials with Client Secret flow, plus your identity provider URL.
  2. Create a Named Principal on it and enter the client ID and secret. Salesforce encrypts and stores them; after saving, nobody reads the secret back out.
  3. Create the permission set, add the External Credential principal access under External Credential Principal Access, and assign it.
  4. Create the Named Credential with the base URL, for example https://api.payments.example.com, and link the External Credential.

Apex then never touches a secret:

HttpRequest req = new HttpRequest();
req.setEndpoint('callout:Payment_API/v1/charges');
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setBody(JSON.serialize(chargeRequest));

HttpResponse res = new Http().send(req);

The callout: scheme resolves the URL, runs the OAuth dance, caches the token, refreshes it when it expires, and injects the Authorization header. Token refresh logic that used to be 80 lines of brittle Apex is now zero lines.

Custom headers and formulas

Some APIs want extra headers, an API key alongside OAuth, a tenant ID, a static subscription header. You can define custom headers on the Named Credential itself, and reference credential fields with formula syntax so the value stays out of code:

{!$Credential.Payment_API.SubscriptionKey}

Combine that with Custom authentication protocol on the External Credential when the API does something nonstandard. Between OAuth, JWT, AWS Sig v4, Basic and Custom with formula headers, I have not met an API in years that forced me back to hardcoded auth.

Beyond Apex: Flow, External Services and Agentforce

The same Named Credential works everywhere. External Services registers an OpenAPI schema against it and generates invocable actions, so a Flow can call your API with no code at all. Agentforce agent actions that make callouts ride on the same rails. This is the real payoff of doing it right once: every consumer in the org shares one secure, rotatable connection definition.

Migrating from legacy Named Credentials

Legacy Named Credentials still function, but new features target the new model, and I migrate them whenever I touch an org. Practical notes from those migrations:

  • The callout:Name reference stays identical, so Apex usually needs no changes. The work is all in Setup.
  • Permission set mappings do not exist in the legacy model, so plan for the access step. This is the number one post-migration breakage.
  • Per-User legacy credentials require users to re-authenticate after migration. Communicate that or your support channel will light up.
  • Deployments: External Credentials deploy via metadata, but secrets do not (by design). You re-enter secrets per environment, so document which principal needs which value in which org.

What good looks like

When I audit an org's callout security, this is the bar:

  • Zero endpoints or secrets in Apex, custom settings, custom metadata, or labels.
  • Every callout goes through a Named Credential, referenced with the callout: scheme.
  • One permission set per integration, mapped to the External Credential principal.
  • Remote Site Settings only where a Named Credential genuinely cannot work, which is rare.
  • Secrets rotate without a deployment, because rotation is just editing the principal.

If your org is far from that list, the good news is the migration is mostly configuration, and you can do it one integration at a time without downtime.