Sitemap

Securely Manage User Context in Agentforce Using Pre-Chat Variables

A deep dive into the set up of user context variables using “Messaging for In-App and Web” with Agentforce

14 min readMay 27, 2025

--

There are a lot of examples of Agentforce implementations that require the agent to know who they are talking to. You want your agent to give accurate responses that apply to the user.
Whether the response changes based on a user’s location, their purchase history or any other type of relevant data, we will have to pass the user and context data to the agent securely.

The way this is done in the Messaging for In-App and Web (MIAW) is to pass context variables through the Pre-Chat API. Just before the chat opens you can pass data like: The page the user was on when the chat was opened, the breadcrumb path the user followed or other customer behaviour on the site or app you require.

Note: I’ll be focussing on the Web Part, but the same principle applies if you use the iOS or Android SDKs. The focus is also purely on the hidden pre-chat variables as these are the ones that are used for system type data that do not require manual user input.

IMHO security is an underrated subject here. In examples I have seen user email addresses or worse plain text user ids are sent through JavaScript. So time to create some security awareness.

The “Journey” Explained

This article is not a manual that explains where to click in setup to create the entire journey start to finish. We assume you have some knowledge about pre-chat variables and OMNI flows and Salesforce in general. This might be an article for a different time. Nonetheless I want at a high level explain the journey once more as a reminder of all the moving parts we have to deal with. I higly recommend watching this Salesforce Code Live Video if you’re after a step by step guide.

Press enter or click to view image in full size
All the moving parts involved when moving variables from the pre-chat api to an agent action

The journey begins in a custom web portal (Or Experience Cloud) where the Messaging for Web Api is implemented. The portal includes the JavaScripts generated by the Embedded Service Deployment related to your Agent.

The portal can popualte pre-chat variables using the Javascript Pre-Chat API and once that is completed the magic starts when a user launches the chat window using the launchChatin the Utilities API script.

Each ESD has a related Messaging Channel. In the messaging channel settings you can set custom parameters for pre-chat. There also is a parameter flow mapping that allows you to map these parameters in to flow variables that are in the OMNI flow.

When you start a chat the messaging channel creates a MessagingSession record. This is the important record that can be directly referenced from flows and Agents. A related MessagingEndUser child record for messaging users is created to keep track of the related end users. I leave that out of scope for now.

Each messaging channel has a related OMNI flow that kicks off as soon as a chat is initiated. All mapped parameter values from the messaging channel are available in this flow as flow variables (you have to create them manually as flow input variables with matching names). So it is up to this OMNI flow to route the pre-chat values and physically save them to the MessagingSession record using an update record action. You can create matching custom fields on the Messaging Session object and have the OMNI flow put the values in these custom fields. This is the recommeded way of storing / passing chat related data.

Lastly the OMNI flow routes to the Agent. The pre-chat variables are stored on the related messaging object record in custom fields. This means our pre-chat values are now available in Agent Actions as Context variables.

This brings us (finally) to the secure context of it all. We want to make sure the values in these variables are send securely.

Secure Context Variables

In the Web API we are sending variables through client side JavaScript. An attacker can put a breakpoint in the JavaScript just before the pre-chat data is sent and update these values to something else. This allows for potentially changing the user context or any other context data or scope .

For example: If you have deterministic External Ids like USR001, USR002etc. An attacker can perform an iteration attack and change the context to USR003 to find out what users exist in the system and try to impersonate them to extract data for different users than currently logged in.

This might seem silly, but when you look at the example of email addresses and names for large customers, a check based purely on email and name is simply not secure enough. It’s really not hard to guess a first and last name when the email address is harry.potter@gmail.com. With enough time, social engineering and liked pages on Facebook, it’s not very hard at all to find customers that might do business with certain companies.
It’s pretty scary how much people publicly share about their interests on social media through innocent likes and how easily it can be used to create a profile. So be careful who you share your details with!

So what can we share? This fully depends on the type of data. If you are a gym and somebody clicks on the contact for a certain location, it can be totally safe to share the location context. Worst case an attacker can get the info from a different gym location but no personal info is exposed.
If you want to get some more context or even define who your user is, you might ask for a membership number in combination with a postcode, signup location and last name. Something that is hard to guess unless you really know something that is not public knowledge like the membership number. Find the right level of security that is fit for your use case.

What I like to focus on are the scenarios where we are in a portal or app that already has the user context and we need to pass that context to our agent. This is often done by sending a user id together with some additional context on what the user is asking for.

As a security best practice record ids and external ids should be treated as secret data and never be exposed through a URL or as plain text. Even Salesforce Ids, as they might seen random, they are fully deterministic (in most cases). The amount of time I have seen this being ignored is quite worrying. Keep in mind that you want to prevent exposing the Ids in a portal or system where external users have access to them and they are used in the web-api through JavaScript.

Long story short: we want to send as little information as possible and get the additional required user data at the Salesforce backend. We don’t trust any data that can be modified on the client side by modifying scripts.

A basic solution

Note: the solution is purely meant as an illustration, an idea, not a full blown implementation but a principle. It’s all about creating the awareness and give you enough to create you own solution that you can validate with your security team.

In order to securely send sensitive data we need a couple of things:

  • A shared secret that is known by both the external portal and Salesforce
  • A shared External Id that is known in the external portal and Salesforce to identify the user / contact record.
  • Some sort of shared salt to add an extra layer of protection in the hash, for simplicity we use the Salesforce Record Id here in combination with the external Id but it can be anything as long as it is shared and secret in both systems.
  • A hash function to make sure the context data is not tampered with: We do not want users to update values like discount, shopping cart value etc. This hash needs to be re-constructed on the server to validate

Let’s pick a few variables:

  • UserId :: The External Id that is encrypted with the shared shared represented in Base64 encoding
  • ContextData :: A JSON string with the key value pairs of our data, encrypted. Note we have a limit of 255 characters, so potentially multiple data fields are required. Ideally this is encrypted with the shared secret represented in Base64 encoding as well.
  • ContextHash :: A SHA256 hash of the ContextData variable value and the Shared Secret Salt joined by a period. In our example we have the format: JSON.ContactId I also like to encrypt the hash, it’s not really required, but we are encrypting everything else, so why not.

Important: The generation of the context data and hash should be done on the server. No secrets or salts should ever be be exposed on the client. Prevent any methods to generate hashes by calling JavaScript functions. Ask your favourite GPT about “Secure Server-Side Hash Signing Patterns” for more info.

Let’s put the whole process in a sequence flow and run through it

Press enter or click to view image in full size
Sequence flow from starting a chat to running the OMNI flow
  1. A user logs in into to a web portal. The portal backend stores the shared User Id of the logged in user. For example the value stored in the External_Id__c field on the contact object. The server also stores the salt. In our example this is the contact Id but can be a random value. As long is it is kept secret and know to both server and Agentforce.
  2. During the user’s browsing actions the portal collect the required context data to start a chat. For example, the user selects a case, navigates to the case details page etc.
  3. The context data is ideally stored in the backend during browsing, but it could be in the front-end as well, depending on the sensitivity.
  4. The user clicks the start chat button.
  5. A javascript is called that makes a callout made to the portal server. The server encrypts the UserIdand the ContextDatadata with the shared secret value and generates the hash using the shared salt.
  6. The server sends the encrypted data back to the portal
  7. When the data is received back from the server, the pre-chat Api is called and sets the encrypted values.
  8. The chat is now starting
  9. The configured message channel has an OMNI flow associated with it. The OMNI flow calls out to an Invocable Apex method that will decrypt the variable data, recalculate / validate the hash and perform some other check to validate that the

The unencrypted data we send looks something like this:

UserId      : 'USR0019',
ContextData : '{"CaseId" : "00001140", "CaseType" : "Technical Issue"}'
ContextHash : '{"CaseId" : "00001140", "etc..." . 003J9000008WWduIAG'

With the encrypted values the data we send looks like below.

UserId      : "9Rp4EdxS1RHu9uvfHpwSydRkfF/QUV375Zf39nWxXH8="
ContextData : "K5azHhJKsw76cet [SHORTEND FOR BREVITY] J11z3OABieJHPUoQ=="
ContextHash : "/3MorA+ts/yXoQh [SHORTEND FOR BREVITY] a6TM69l5lwkMjWnA=="

As you can see: It’s now encrypted and cannot be tampered with on the client before the data is send to Agentforce unless you know the shared secret, external user Id and the salt.

The pre-chat Api code looks something like below. It’s just an illustration, not tested code… The principle is that you send whatever data you need to your own server. The server signs and encrypts all the values. Next the encrypted values are picked up from the response and send to the pre-chat API. Once the values are set the chat is launched.

function startChat(){
// Call a custom signing API
fetch("/api/signAndEncrypt", {
method: "POST",
body: JSON.stringify(contextData),
})

// When the signing is complete parse the JSON
.then(res => res.json())

// Set the the encrypted pre-chat API hidden values
embeddedservice_bootstrap.prechatAPI.setHiddenPrechatFields({
UserId : res.UserId,
ContextData : res.ContextData,
ContextHash : res.ContextHash
});

// Open the chat window
embeddedservice_bootstrap.utilAPI.launchChat();
})
}

The next step is to create an Invocable Apex Method that can decrypt the initial pre-chat values, validate the hash do some additional checks like: does the case exist and does the case belong to the customer.

Press enter or click to view image in full size

It basically does the same process but in reverse: All variables are read and decrypted. A hash is generated and validated against the received hash, additional check can be done as required and the method returns true or false.

Some example Apex code as an illustration.

/**
* @description Example class that can be used to securely send
* and handle encrypted data to Agentforce Agents
*/
public class EncryptedPreChatVariableHandler {

/**
* @description Class required because of multiple input parameters
*/
public class InputParams {

@InvocableVariable
public String userId;

@InvocableVariable
public String contextData;

@InvocableVariable
public String contextHash;
}


/**
* @description Class with the output variables we can map in the
* in the OMNI flow
*/
public class OutputResult {
@InvocableVariable
public String userId;

@InvocableVariable
public String contextData;

@InvocableVariable
public String contextHash;

// Constructor
public OutputResult(String userId, String contextData,
String contextHash){
this.userId = userId;
this.contextData = contextData;
this.contextHash = contextHash;
}
}


/**
* @description The main Invocable Method we call from the OMNI flow
*/
@InvocableMethod(
label = 'Encrypted Pre-Chat Variable Handler'
description= 'Handles encrypted pre-chat OMNI Flow variables'
)
public static OutputResult[] processInputs(InputParams[] inputs){

// Start by decrypting the encrypted input
String decryptedUserId = PreChatVariableUtil.decrypt(
inputs[0].userId
);

String decryptedContextData = PreChatVariableUtil.decrypt(
inputs[0].contextData
);

String decryptedContextHash = PreChatVariableUtil.decrypt(
inputs[0].contextHash
);

// Validate the hash
Boolean hashIsValid = PreChatVariableUtil.validateHash(
decryptedUserId,
decryptedContextData,
decryptedContextHash
);

// Throw an exception if the hash if not valid
if(!hashIsValid){
throw new StringException(
'Invalid Pre-Chat variable values detected'
);
}

// Create the results
return new OutputResult[]{new OutputResult(
decryptedUserId,
decryptedContextData,
decryptedContextHash
)};
}
}

OMNI Flow

In the related OMNI flow we start by getting the MessagingSession record.

Next we call the invocable Apex Method where we pass the encrypted parameters that were mapped from the Pre-Chat API in the matching invocable parameters.

Press enter or click to view image in full size

If there is any error we can handle it accordingly, you can choose to re-route to a different agent or the same agent or simply do nothing. This is not very user friendly though, so routing with a custom message is the right thing to do.

If there are no errors this means the data is decrypted properly and the hash is valid. You can now handle the output data from the Apex Method however you require. In this example I copy the decrypted values to fields on the MessagingSession record and set the User Validation Passed field to true. An Agent action can reference that checkbox as a starting point.

Press enter or click to view image in full size

After this step the only thing to do is to update the MessagingSession record so the newly assigned variables are saved to the database, ready to be read by Agent Instructions or Agent Actions.

Reference the MessagingSession Data in the Agent

The Agent has access to all data on the related MessagingSession record. However, the first thing you need to do is expose the required fields to your Agent by going to the Context Menu on the left in the Agent Builder. Here you can include fields from the MessagingSession object into your Agent context. If you forget this step, the Agent will not have access to these fields and you will spend a lot of time debugging :-)

Press enter or click to view image in full size
Include Context Variables in your Agent

You can use the fields from the MessagingSession in the Agent instructions as well as in actions. In our case we want to use the UserId__c value to determine the input parameter value for the “Identify Contact” Agent Action. This action does always run first before anything else because we don't want to run anything for unidentified users.

If there is no User Id set or the User Validation Passedfield is set to false we can assume that the OMNI flow has rejected the pre-chat variables and we do not want to continue. Remember that it is the OMNI flow’s responsibility to validate everything is OK before the chat is routed to the Agent. The more potential malicious behaviour we can prevent before we reach the agent, the better.

In the below image you can see some example instructions. These are extremely high level to give you an idea. Getting it to work 100% the way you want it to, will require some trial and error. Make them as specific as you possibly can.

Press enter or click to view image in full size
Example instructions that use the messaging session variables

Full Recap

We ran through the principle flow of how pre-chat variables move from the pre-chat API all the way to the MessagingSession record. The logic for populating the record is managed by the related OMNI flow. Agent Actions can reference the pre-chat values in the related MessagingSession record.

From a security perspective we added an additional layer by requiring the values that are sent through the pre-chat api to be encrypted and hashed so we can prevent client side tampering and accidentally exposing any unauthorised data.

Important Considerations

  • Coding is the simple part. You need to have a robust secret management process in both Salesforce and the custom Portal and a strategy on how both teams work together. Think of secret rotation, sharing of secrets lower environments etc. This process should not be underestimated.
  • Secure Secret Storage on Salesforce is a whole chapter by itself. Make sure to speak to a certified implementation partner to properly implement key management. The same applies to the custom portal software. Secrets management is the weakest link.
  • Think reusability: Have multiple portals? Create reusable methods for encrypting, hashing etc.
  • Fields are limited to 255 characters. This means that encrypted values in a Base64 encoding limit how much (JSON) data you can store. You might want to split your values.
  • Do not under estimate the security impact of sending values from a client you do not have control over… Hackers are incredibly smart and security should be always top of mind.

Conclusion

The whole Messaging model might seem a bit complex at first, but once you understand how it works it not that bad. Implementing a server side encrypting and hashing pattern might seem like a lot of work and perhaps a bit overkill, but I assure it is not. It’s relatively little work and you seriously reduce your client side script modification attack vector.

Resources

Final note

At the time of writing I am a Salesforce employee. The above article describes my personal views and techniques only. The content is in no way, shape or form official advice of any kind. It’s purely meant to be informative.

Anything from the article is not per definition the view of Salesforce as an Organization. Always speak to a certified implementation partner before implementing anything your read on the internet… Especially when it is security related!

Press enter or click to view image in full size
Image generated by Microsft AI Image generator

--

--

Justus van den Berg
Justus van den Berg

Written by Justus van den Berg

Technical Architect at Salesforce UKI