Headless Identity APIs: Headless Guest Flow for Public Clients
Some users interact with your off-platform app but don’t log in or register. You can issue identifiers in the form of unique visitor ID (UVIDs) for these unknown visitors with the Headless Guest Flow. If the user decides to log in or register, you can pass the identifier into a named user flow, like a headless login flow. Use guest user identity as a tool for carrying context from guest user sessions to named user sessions. This flow is a variation of the Authorization Code and Credentials Flow.
Required Editions
| Available in: both Salesforce Classic (not available in all orgs) and Lightning Experience |
| Available in: Enterprise, Unlimited, and Developer Editions |
Before setting up this flow, complete these steps.
- Complete Prerequisites for Headless Identity
- Integrate your off-platform app with Salesforce using one of these options. Make sure that you configure additional policies for the guest flow.
For example, you host an ecommerce app outside of the Salesforce platform. You want users to be able to save items to a cart without logging in. You can use the guest user flow to generate a UVID for the user, and you can store the user’s cart information and tie it to the UVID. When the user eventually logs in or registers, you pass the UVID into a headless login or registration flow. Because you have the cart context from the UVID, you can save the user’s items, giving your users a better experience.
The shopping cart example is just one of many possibilities when it comes to the UVID and guest flow. Other possibilities include understanding what makes your users want to sign up for your app and remembering their preferences.
The guest flow and UVID are supported only for JSON Web Token (JWT)-based access tokens. To connect the UVID to a named user, you must configure the named user flow to issue JWT-based access tokens too.
These instructions tell you how to implement the flow for a public client, like a single-page app, that can’t keep confidential information private. Public clients have some extra security considerations. Compared to a private client like a client-server app, they don’t have a private backend to store the consumer secret, which acts as a password to secure the code exchange. With nowhere to store a consumer secret, the app can’t risk leaking it. To replace the role of the consumer secret, you can use the Proof Key for Code Exchange (PKCE) extension instead. This extension protects your app with parameters that only you and Salesforce can verify. We always recommend implementing PKCE for a public client.
Here’s an overview of the guest flow. These steps cover the flow up to the point when the guest user is identified, but not logged in.
- The unknown visitor arrives at the app or performs an action like clicking a button (1).
- The app looks for a UVID, and if it can’t find one, it generates a value.
- If you’re using the Proof Key for Code Exchange (PKCE) extension, which we strongly recommend, the app generates PKCE parameters.
- To get an authorization code, your app headlessly sends the UVID to the Salesforce
authorization endpoint (
services/oauth2/authorize) in a GET or POST request. - Salesforce validates the UVID and returns a 302 redirect to a preconfigured URL containing the authorization code. The redirect is processed within the browser, and the response is delivered to the callback endpoint.
- The callback endpoint extracts the authorization code and returns it to your app.
- Your app receives the authorization code and initiates a code exchange by sending the code
and other parameters in a POST request to the Salesforce token endpoint (
services/oauth2/token). - Salesforce validates the request and returns a guest JWT-based access token with the UVID in the subject claim.
- Your app processes the response and creates a guest session, persisting the UVID value.
- The end user now has a guest session keyed to the UVID value returned in the access token.
Like other variations of the Authorization Code and Credentials Flow, for a public client, this
flow requires a callback endpoint that can handle the 302 redirect and return the authorization
code and other parameters to your app. If you do the code exchange in your browser, you can use
the Salesforce /services/oauth2/echo endpoint. It
automatically parses the 302 redirect, extracts the parameters, and returns them to your app in
JSON format. We use the echo endpoint in the code examples for this flow.
Unknown End User Arrives at Your App
The flow starts when an end user visits your app. You can also configure it to start when the user performs an action, such as saving an item to a cart.
App Generates a UVID
Your app tries to find a UVID associated with this user. For example, it looks for browser cookies from any previous visits to the app. If the app can’t find a UVID, it generates one. Salesforce doesn’t play a role in generating the UVID. Its generation, storage, and maintenance are entirely up to you. The only requirement is that the UVID must be a Version 4 universally unique identifier (v4 UUID).
App Generates PKCE Parameters
If you’re using PKCE, your app generates code_verifier
and code_challenge parameters.
The PKCE specification defined in RFC 7636 also includes an optional
code_challenge_method parameter that you can send in the
authorization request. Salesforce ignores any value that you send in this parameter and defaults
to SHA256.
App Exchanges the UVID for an Authorization Code
Your app headlessly sends a GET or POST request to the Salesforce authorization endpoint on your Experience Cloud site. The request contains the UVID and other parameters to identify the app and specify the type of request.
You have two options for how you send the UVID in this request. You can either send a plain UVID value, or send a JWT-based access token with the UVID minted into it. For example, if you have a JWT-based access token from a previous session, sending the token to the authorization endpoint is an easy way to reuse it without specifically parsing it for the UVID.
Include these headers in your request.
| Header | Required? | Description |
|---|---|---|
Uvid-Hint
|
Required if you don’t send the UVID in the request body. You must always include a UVID—whether it’s sent in the header or in the request body. | Contains either the UVID as a plain value or a JWT-based access token with a UVID minted into it. If you send the UVID as a plain value, include a If you send
a JWT-based access token with a UVID, include a |
Auth-Request-Type
|
Yes. | Specifies the type of request you want to make to Salesforce. For headless
passwordless login, this value must be set to guest. |
Include these parameters in the request body.
| Parameter | Required? | Description |
|---|---|---|
uvid_hint
|
Required if you don’t include the UVID in the header. | Contains either the UVID as a plain value or a JWT-based access token with a UVID minted into it. If you send the UVID as a plain value, include a If you send a
JWT-based access token with a UVID, include a |
client_id
|
Yes. | The consumer key of the external client app or connected app. |
response_type
|
Yes. | The OAuth 2.0 grant type that your app requests. Because this flow is a variation of
the Authorization Code and Credentials Flow, this value must be code_credentials. |
redirect_uri
|
Yes. | The URL where users are redirected after successful authentication. The For this flow, you can use the echo endpoint, |
code_challenge
|
Only if you’re using PKCE, which we always recommend, especially for a public client. | Specifies the SHA256 hash value of the If a If the |
scope
|
Yes. | Permissions that define the type of protected resources an external client app or connected app can access. The values that you send in this request must match or be a subset of the scopes assigned to your app. For more information about each scope and its purpose, see OAuth Tokens and Scopes. |
Check out these example authorization requests, which all implement PKCE.
Here’s an example request where the UVID is passed as a plain value in the request header.
POST /services/oauth2/authorize? HTTP 1.1
Host: MyExperienceCloudSite.my.site.com
Uvid-Hint: UVID abcd-1234-efgh
Auth-Request-Type: guest
response_type=code_credentials&
client_id=***********&
redirect_uri=https://www.MyExperienceCloudSite.my.site.com/services/oauth2/echo&
code_challenge=********&
scope=openid
In this example, the UVID is passed in a JWT-based access token in the header.
POST /services/oauth2/authorize? HTTP 1.1
Host: MyExperienceCloudSite.my.site.com
Uvid-Hint: JWT **************
Auth-Request-Type: guest
response_type=code_credentials&
client_id=***********&
redirect_uri=https://www.MyExperienceCloudSite.my.site.com/services/oauth2/echo&
code_challenge=********&
scope=openid
Here’s an example where the UVID is passed as a plain value in the request body.
POST /services/oauth2/authorize? HTTP 1.1
Host: MyExperienceCloudSite.my.site.com
Auth-Request-Type: guest
uvid_hint=UVID abcd-1234-efgh&
response_type=code_credentials&
client_id=***********&
redirect_uri=https://www.MyExperienceCloudSite.my.site.com/services/oauth2/echo&
code_challenge=********&
scope=openid
Salesforce Returns a 302 Redirect
Salesforce validates the UVID. If the UVID was passed as a plain value, Salesforce validates its format. If it was passed as a JWT-based access token, Salesforce checks the validity of the token.
Salesforce then returns an HTTP 302 redirect to a preconfigured URL containing the authorization code. If the flow is happening in the browser, the 302 redirect is processed in the browser and Salesforce automatically sends the redirect response to the redirect URL, which is the callback endpoint. Here’s an example preconfigured URL.
https://www.MyDomainName.my.site.com/services/apexrest/code/exchange?code=aPrxC1*******
&sfdc_community_url=https%3A%2F%2FMyDomainName.my.site.com&sfdc_community_id=0DBxxxxxxxxxxxxCallback Endpoint Sends the Authorization Code to Your App
The callback endpoint extracts the authorization code and returns it to your app. In these
code examples, the redirect URL points to the /services/oauth2/echo callback endpoint on the Experience Cloud site. This endpoint
automatically parses the 302 redirect, extracts the authorization code and other parameters, and
returns them to your app in JSON format.
App Initiates Code Exchange
Your app receives the code response with the authorization and other parameters. It exchanges
the code for an access token by sending a headless POST request to the /services/oauth2/token endpoint.
Include one header in the request.
| Parameter | Required? | Description |
|---|---|---|
Auth-Request-Type
|
Yes. | Specifies the type of request you want to make to Salesforce. For headless
passwordless login, this value must be set to guest. |
Uvid-Hint
|
Yes. | Contains either the UVID as a plain value, or a JWT-based access token with a UVID minted into it. For the code exchange, don’t include a prefix for the UVID. For example,
if you pass the UVID as a plain value, the header is just |
Include these parameters in the request body.
| Parameter | Required? | Description |
|---|---|---|
code
|
Yes. | The authorization server creates an authorization code, which is a short-lived token, and passes it to the client after successful authentication. The client sends the authorization code to the authorization server to obtain an access token and, optionally, a refresh token. |
client_id
|
Yes. | The consumer key of the external client app or connected app. |
redirect_uri
|
Yes. | The URL where users are redirected after a successful authentication. The redirect URI must match one of the values in the external client app or connected app Callback URL field. Otherwise, the approval fails. This value must be URL encoded. For this flow, you can use
the echo endpoint, |
grant_type
|
Yes. | The type of validation that the external client app or connected app can provide to
prove it’s a safe visitor. Because this flow is a variation of the Authorization Code and
Credentials Flow, the value must be authorization_code. |
code_verifier
|
Only if you’re using PKCE. | Specifies 128 bytes of random data with high entropy to make guessing the code value difficult. Set this parameter to help prevent authorization code interception attacks. The value must be base64url-encoded as defined in https://datatracker.ietf.org/doc/html/rfc4648#section-5. If there’s a If the |
Here’s an example token request that implements PKCE. In this example, the UVID is sent as a plain value.
POST services/oauth2/token? HTTP 1.1
Host: MyExperienceCloudSite.my.site.com
Uvid-Hint: abcd-1234-efgh
Auth-Request-Type: guest
code=********&
client_id=**********&
redirect_uri=https://MyExperienceCloudSite.my.site.com/services/oauth2/echo&
grant_type=authorization_code&
code_verifier=*******Salesforce Grants a JWT-Based Access Token
After validating the app’s credentials. Salesforce returns a guest JWT-based access token and
a state to the browser. The guest access token contains the UVID in the subject (sub) claim. Here’s an example snippet of a JWT-based access token.
As a reminder, these tokens have three components: a header, payload, and signature. This
example shows you the payload with the UVID in the sub
claim. As you can see, the value is prefixed with uvid.
{
"tnk": "example/00XXXXXX",
"ver": "1.0",
"kid": "CORE_ATJWT************",
"tty": "sfdc-core-token",
"typ": "JWT",
"alg": "RS256"
}
{
"scp": "open_id",
"aud": [
"https://example.com"
],
"sub": "uvid:abcd-1234-efgh",
"nbf": "1675197036",
"iss": "https://MyExperienceCloudSite.my.site.com",
"exp": "1675198836",
"iat": "1675197036",
jti”: xRb**********”
"client_id": "**********"
}For a full explanation of each claim in a JWT-based access token, see JWT-Based Access Tokens in Salesforce Help.
App Creates a Guest Session
Your app processes the access token response and creates a guest session, persisting the UVID value.
Unknown User Is Now Identified
The unknown end user now has a guest session tied to the UVID value returned in the JWT-based access token. At this point, it’s up to you what you want to do with the UVID. To learn how to pass it into a named user authorization flow, see Headless Identity APIs: Extending the Headless Guest Flow into a Named User Flow.

