At the protocol level, Client Credentials grant is designed to not require a user identity. It involves an application exchanging its application credentials, such as client ID and client secret, for an access token. This flow is best suited for Service-to-Service integrations where a backend component (your Service) talks to another backend component (the REST API => the other Service).
Starting with the Winter '24 release, oAuth Client Credentials flow (grant_type=client_credentials) is supported out of the box by a combination of External and Named Credential. The authorization step of obtaining the access token is done by the External Credential. The callout to the REST API and injection of the access token is handled by Named Credential.
For our implementation example we'll use Marketing Cloud (MC) as the target. MC meets our criteria: it has REST APIs which require authorization via oAuth, Client Credentials is a supported flow. One of the REST APIs available in Marketing Cloud is Email Validation: given an email address, the API tells you if the email is valid. (As an aside, this API alone is not sufficient for real-world email validation, see more on this below).
- REST API:
https://<api_host:port>/address/v1/validateEmail - oAuth Authorization Service - Token:
https://<token_host:port>/v2/token
Implementation
1. External Credential (EC)
a. Credential
- Protocol: oAuth 2.0
- Flow Type: Client Credentials with Client Secret Flow
- Identity Provider URL: URL of oAuth Authorization Service - Token endpoint
- Pass client credentials in request body: depends

The Pass client credentials.. checkbox is a nuance of oAuth client credentials grant implementation. Regardless of whether this box is checked, the body of the request will be encoded via application/x-www-form-urlencoded scheme. The body contains the grant type (set by EC/NC to client_credentials) and optionally the scope (the Scope field).
The oAuth spec allows the credentials to be stuffed into the request body or by encoding them into the Authorization header according to HTTP Basic authentication scheme. The spec recommends the Authorization header as an allegedly safer choice. When the spec was published in 2012, there may have been relevant considerations for this preference. Since then lots of e-ink has been spilled on various threats to oAuth protocol. There's no universal "safe(r)" option nor does this one choice significantly harm or improve your overall security posture.
This checkbox is important because some older oAuth authorization services will accept only one out of these two credential transmission formats: body or Authorization header. Modern authorization services accept both. Based on reading the docs or by trial and error you'll need to find out what mode(s) your authorization service supports and make an appropriate selection.
As it so happens, Marketing Cloud's oAuth authorization service is an older implementation. It only allows credentials to be sent in the body of the request, requiring the Pass credentials... option to be checked.
If you leave the option unchecked and your target authorization service doesn't support this mode, you'll get a CalloutException along the lines of:
> System.CalloutException: Unable to fetch the OAuth token. Error: > invalid_request. Error description: Make sure that the client ID and > client secret are valid and that the following parameters are not > empty or null: client ID, client secret, and grant type..
b. Principal
The principal represents your application acting as oAuth client. Within the EC definition page, scroll down to the Principal panel and create a new Principal:
- Parameter Name: name of your application (e.g. Salesforce - Production org)
- Client Id: oAuth client id
- Client Secret: oAuth client secret
If you have configured the Principal and later on decide to edit the EC (say, to change the Pass client credentials... option), you will need to reapply the credentials to the Principal. As a safety precaution, EC removes the credentials from the Principal on edit of EC. If you are not aware of this and you attempt to make the callout, you'll receive a CalloutException: The external credential isn't fully configured. Contact your Salesforce admin
2. Named Credential (NC)
- URL: https://<api_host:port>
- Enabled for Callouts: select
- External Credential: select the EC from step 1
- Callout options > Generate Authorization Header: select

3. Permissions
The user who will be making the callout via Named Cred needs to be granted permission to the underlying External Cred. (Analogy: Named Cred = pipe, External Cred = identity). The user in question is the user attached to the runtime context of your callout.
Permissions can be granted via Profile or Permission Set:
- Navigate to External Credential Principal Access section, click Edit and select the EC/Principal combination that will be listed. Save.
Prior to Winter '25 release, after you enabled external credential principal access, you need to have assigned object permissions for User External Credentials object manually on each permission set or profile. Starting with Winter '25, most standard permission sets and profiles have access to the User External Credentials by default. For the guest user profile, and for existing custom permission sets and profiles, you must still grant access to the User External Credentials object manually.
Warning: as of this writing, External Credential Principal Access has a bug: if more than one set of EC/Principal is available, you'll only see one EC/Principal combo in External Credential Principal Access view/panel until you hit Edit. The other EC/Principal combos will then show up on the Edit page.
4. Callout (Apex)
String body = '{"email": "[email protected]","validators": [ "SyntaxValidator", "MXValidator", "ListDetectiveValidator"]}'; HttpRequest req = new HttpRequest(); req.setEndpoint('callout:mc/address/v1/validateEmail'); // base URL (NC) + relative API path req.setMethod('POST'); req.setBody(body); Http http = new Http(); HttpResponse res = http.send(req); System.debug(res.getBody());
Result:
00:66:66:224 USER_DEBUG [8]|DEBUG|{"email":"[email protected]","valid":true}
5. Callout (Flow)
As an alternative to calling the API in Apex, you can perform the same operation via a Flow.
a. HTTP Callout action
Create the HTTP Callout action and link it to the Named Credential created in step 2:

Provide the relative API path (/address/v1/validateEmail) and sample JSON request/response, thus completing the build of the action:

If you squint at this screen, you'll see that a ValidateEmail External Service was auto-generated and linked to the action. This is so because underneath the hood the HTTP Callout action is a Flow wrapper (Adapter design pattern) around External Service.
b. Wire request/response
For this example, we're using the Screen Flow to prompt the user for the email address, compose the API request, call the API and display the response

Result:

Debug log:
VALIDATEEMAIL.VALIDATE EMAIL (EXTERNAL SERVICES): Validate Email (API) Inputs: body = {!ValidateEmailPayload} (ValidateEmail_Validatex20Email_IN_body : { "validators_set" : true, "validators" : [ "ListDetectiveValidator", "MXValidator", "SyntaxValidator", "ListDetectiveValidator", "MXValidator", "SyntaxValidator" ], "email_set" : true, "email" : "[email protected]" }) Outputs: 2XX (ValidateEmail_Validatex20Email_OUT_2XX : { "valid_set" : true, "valid" : false, "email_set" : true, "email" : "[email protected]" }) responseCode (200)
Bonus: you might have noticed that both request and response generated by the External Service have xxx_set keys (email_set,validators_set, valid_set) not present in the payload built in Apex. These keys are not part of MC API schema, they're junk artifacts of External Service. If the target API used strict JSON parsing that rejected unknown keys, this request would have blown up. Thankfully Marketing Cloud's JSON parser is as relaxed as its email validation approach.
Double Bonus if you've read this far: out of three validators available when calling MC's Email Validation API, the syntax validator is the only one that's mildly useful. As you can see, [email protected] is syntactically good according to the RFC but it's certainly not a valid real-world email address.