This repository contains code and configuration for capturing full agent conversation data from Salesforce Agentforce and sending it to Pendo for Agent Analytics. It interacts with Pendo solely through Pendo's authenticated Conversation API.
This example code is not a Pendo product and is not part of any Pendo commercial agreement.
Copyright 2026 Pendo
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This code is provided "AS IS," without warranty of any kind, as described in the license above. It is example code rather than a Pendo product, so it is not covered by a warranty or SLA, and Pendo does not commit to maintaining or fixing it.
Pendo Support can assist with standard Pendo functionality, documented in the Pendo Help Center. Implementing and troubleshooting this code, however, involves your Salesforce environment, which falls outside the scope of what the support team can advise on. For hands-on implementation help, Professional Services is the appropriate path.
This code runs within your own Salesforce environment, and deploying and operating it there is your responsibility. We recommend reviewing the code, validating it against your own policies and requirements, and testing it in a non-production sandbox before deploying to production.
Because Pendo has no visibility into or control over your Salesforce org, Pendo accepts no responsibility for its behavior once deployed, including any effect on your Salesforce configuration, data, users, performance, API or event limits, security, or compliance posture. Testing in a non-production environment first is the best way to identify anything specific to your setup.
- How It Works
- Deploy with an AI Coding Agent
- Choose Your Approach
- What Gets Sent to Pendo
- Repository Structure
- Prerequisites
- Clone and Authenticate
- Data Cloud Approach — Deployment
- Custom Action Approach — Deployment
- Verifying It Works
- Troubleshooting
- Security
Every time a user talks to a tracked Agentforce agent, both the user's message and the agent's reply are captured and forwarded to Pendo Agent Analytics.
For each conversation turn, two events are posted to the Pendo agentic API (POST https://data.pendo.io/data/agentic) — one for the user's message (prompt) and one for the agent's reply (agentResponse) — grouped by a conversation/session ID.
Authentication is a request header: the shipped Named Credential supplies only the base URL (https://data.pendo.io), and the Apex code adds the x-pendo-integration-key header using the API key you store in the Pendo_Config__c custom setting. The Named Credential itself performs no authentication.
This repo ships agent skills so an AI coding assistant can run the deployment for you. Open the repo in your tool of choice and ask it to deploy — it reads the bundled guidance, automates the deployment and verification steps, and pauses to ask for your Pendo credentials and to walk you through the manual Salesforce steps.
- Claude Code / Claude — the
pendo-agent-analytics-agentforceskill (in.claude/skills/) is auto-discovered when you open the repo. Just ask: "Deploy Pendo Agent Analytics." - Cursor (2.4+) — the same skill (in
.cursor/skills/) appears in the/menu as/pendo-agent-analytics-agentforce. - Codex and other
AGENTS.md-aware agents — the repo's rootAGENTS.mdis read automatically; open the repo and ask it to deploy. Enable network access for the agent's shell — the Salesforce CLI needs it to authenticate and deploy, and some sandboxes (e.g. Codex) disable network by default.
You still need the prerequisites (Salesforce CLI access, org capabilities, and your Pendo API Key + Agent ID). The agent handles the automatable deployment and verification; it guides you through the parts that require the Salesforce or Pendo UI.
This repo supports two independent approaches. Both send the same core data — the difference is how they trigger. Pick one.
| Data Cloud Approach (Recommended) | Custom Action Approach | |
|---|---|---|
| Best for | Orgs with Data Cloud enabled | Orgs without Data Cloud |
| How it triggers | A Data Cloud-triggered Flow fires when Agentforce writes interaction records to Data Cloud | Two Apex actions added to each agent topic in Agent Builder; fire during the turn |
| Latency | Batched via Data Cloud ingestion (typically ~30–40 minutes) | Synchronous during the turn (near real-time) |
| Agent targeting | Explicit — a Pendo_Target_Agent__mdt allowlist selects which agents are tracked |
Implicit — only agents you wire the actions into are tracked |
| Per-agent setup | Deploy once; add one metadata record per agent | Configure each agent in Agent Builder |
| Runs as | The Automated Process system user | The interacting user |
| Data captured | Prompt, response, session, visitor plus agentType, agentTraceId, agentResponseDurationMs, agentSubagentsUsed |
Prompt, response, session, visitor |
Recommended: the Data Cloud approach — it captures richer telemetry with no per-turn Agent Builder wiring. Use it when Data Cloud is available. If Data Cloud does not appear in your Salesforce Setup, use the Custom Action approach.
For every conversation turn, two events are posted to Pendo — one for the user's message and one for the agent's reply.
| Field | Description | Approach |
|---|---|---|
conversationId |
Groups all turns of a session together | Both |
messageId |
Unique ID per event | Both |
visitorId |
Salesforce user email address | Both |
account_id |
Salesforce Org ID | Both |
content |
The actual message text | Both |
agentId |
Pendo Agent ID from the custom setting | Both |
modelUsed |
Defaults to "Agentforce" — customizable |
Both |
agentType |
e.g. Employee |
Data Cloud only |
agentTraceId |
Salesforce telemetry trace reference | Data Cloud only |
agentResponseDurationMs |
How long the agent took to respond | Data Cloud only |
agentSubagentsUsed |
Which topic or subagent handled the request | Data Cloud only |
Customizing the payload:
modelUsedis set to"Agentforce"in code — you can change it to another value, and you can send additional properties to Pendo, by customizing the payload the integration builds inPendoConversationService.buildPayload.
The repo is split into three directories inside salesforce-pendo-agent-analytics/main/. Deploy base first — both approaches depend on it.
salesforce-pendo-agent-analytics/
├── README.md
├── sfdx-project.json # sourceApiVersion 66.0
└── salesforce-pendo-agent-analytics/
└── main/
├── base/ # Required by BOTH approaches — deploy first
│ ├── classes/
│ │ ├── model/ ConversationMessage.cls (+ test)
│ │ └── service/ PendoConversationService.cls (+ BaseTestDataFactory, tests)
│ ├── namedCredentials/ Pendo_API.namedCredential-meta.xml
│ └── objects/ Pendo_Config__c/ # Custom Setting: API_Key__c, Agent_Id__c
│
├── agentforce-custom-action/ # Custom Action approach
│ ├── classes/ AgentforceConversationAdapter, GenerateConversationId (+ tests)
│ ├── genAiFunctions/ Generate_Conversation_ID/, Log_Conversation_Turn_to_Pendo/
│ ├── bots/ Agentforce_Employee_Agent/ # EXAMPLE agent — reference only
│ └── genAiPlannerBundles/ # EXAMPLE topics — reference only
│
└── data-cloud/ # Data Cloud approach
├── classes/ DataCloudConversationAdapter.cls (+ test)
├── objects/ Pendo_Target_Agent__mdt/ # Agent allowlist (custom metadata type)
├── flows/ Agentforce_to_Pendo_Conversation_Sync_New_V1.flow-meta.xml
├── flowDefinitions/ Agentforce_to_Pendo_Conversation_Sync_New_V1.flowDefinition-meta.xml
├── bots/ IT_Support_Test_Agent/, Session_Trace_Test_Agent/ # EXAMPLE agents — reference only
└── genAiPlannerBundles/ Session_Trace_Test_Agent/ # EXAMPLE bundle — reference only
Example agents are reference only. The
bots/andgenAiPlannerBundles/folders contain example Agentforce agents included to illustrate how tracking is wired up. Do not deploy them for a real integration — the deployment steps below deploy only the code, objects, and (for Data Cloud) the Flow.
The lists below state what must be true before you start; resolving anything org-specific (licensing, permissions, enablement, data model upgrades) is your responsibility. Get these in place, then follow the deployment steps.
- Ability to deploy metadata to the org — via the Salesforce CLI (
sfv2+) or any deployment method you prefer. - Agentforce enabled, with at least one active agent.
- Named Credentials available (standard in Enterprise editions and above).
- Outbound callouts permitted to
https://data.pendo.io. This is handled by the shipped Named Credential — you do not need a Remote Site Setting. - The deploying user can create metadata (e.g.
Customize Application) and the running context is allowed to make outbound callouts.
- The agent is configured in Pendo Agent Analytics and associated with a Pendo Application. This is a prerequisite, not an afterthought: it is the source of your Agent ID, and it determines which Application's Raw Events the conversation data lands under. Configure this in Pendo first.
- Pendo API Key — the Track Event / agentic Shared Secret for the target subscription (Pendo → Settings → Integrations → Track Event Shared Secret).
- Pendo Agent ID — from the Agent Analytics configuration for the agent above.
- Confirm your Pendo region/endpoint. The Named Credential ships pointing at
https://data.pendo.io(US). EU or other data residencies use a different host — update the Named Credential endpoint accordingly, and verify events in the same subscription the API key belongs to.
- Data Cloud provisioned in the org.
- Salesforce Standard Data Model ≥ v1.130. Session Tracing depends on it. Check Setup → Installed Packages → Salesforce Standard Data Model and upgrade if below 1.130.
- Einstein Generative AI and Agentforce turned on.
- Agentforce Session Tracing available to enable (you enable it during deployment, below). See Salesforce's Session Tracing setup guide.
Start by cloning this repository and authenticating to the Salesforce org you are deploying into, using your preferred method.
The deployment commands below use the Salesforce CLI and assume an org alias of pendo-target — substitute your own alias, or apply the same steps in the same order if you deploy by another method.
Skip to Custom Action Approach if you are not using Data Cloud.
Order matters. The Flow triggers on a Data Cloud object (ssot__AiAgentInteractionMessage__dlm) that does not exist until Session Tracing is enabled. Deploy the code and objects first, enable Session Tracing, and deploy the Flow last. Deploying the whole data-cloud/ directory in one shot will fail (the Flow can't compile before the object exists) and would also pull in the reference-only example agents.
Deploy base, the Data Cloud Apex classes, and the objects — not the Flow yet, and not the example bots//genAiPlannerBundles/:
sf project deploy start \
--source-dir salesforce-pendo-agent-analytics/main/base \
--source-dir salesforce-pendo-agent-analytics/main/data-cloud/classes \
--source-dir salesforce-pendo-agent-analytics/main/data-cloud/objects \
--target-org pendo-targetThen run the tests:
sf apex run test --target-org pendo-target --test-level RunLocalTests --wait 10 --result-format human✅ Checkpoint: the deploy succeeds with no errors and local tests pass.
Create the org-default record of the Pendo_Config__c custom setting:
-
Setup → Custom Settings → Pendo Config → Manage → New
-
Enter:
Field Value API_Key__cYour Pendo Track Event / agentic Shared Secret Agent_Id__cYour Pendo Agent ID -
Save.
Never commit API key values to the repository.
- Setup → Named Credentials → Pendo API
- Confirm the URL is
https://data.pendo.io(change only for non-US Pendo regions). - Confirm the Identity Type is Anonymous.
Why Anonymous: the Data Cloud Flow runs as the Automated Process system user, which has no per-user (Named User) principal. With
Anonymous, the callout works in that context. Authentication to Pendo is thex-pendo-integration-keyheader set in Apex — the Named Credential provides only the URL, so no External Credential is required.
This is what writes Agentforce conversations into Data Cloud and creates the object the Flow triggers on. Without it, the Flow never fires.
- Ensure Standard Data Model ≥ v1.130 (see Prerequisites).
- Setup → Einstein Generative AI → Einstein Audit, Analytics, and Monitoring Setup.
- Scroll to Agentforce Session Tracing and toggle it on.
- Wait 15–30 minutes for the Data Cloud objects to become available and to populate.
✅ Checkpoint: after the wait, the object ssot__AiAgentInteractionMessage__dlm exists. If you use the CLI:
sf sobject describe --sobject ssot__AiAgentInteractionMessage__dlm --target-org pendo-targetThe allowlist must match the agent name as Data Cloud recorded it, which can differ from the API name shown in Setup. After at least one conversation with the agent has been traced, read the recorded value:
sf data query \
--query "SELECT ssot__AiAgentApiName__c FROM ssot__AiAgentSessionParticipant__dlm" \
--target-org pendo-targetUse the exact ssot__AiAgentApiName__c value in the next step. (No rows yet? The conversation hasn't finished ingesting — wait for the Data Cloud batch and try again.)
The Pendo_Target_Agent__mdt custom metadata type is an allowlist. Only agents with an active record are tracked. At least one active record is required — with none, the integration raises a configuration error.
Via Setup:
- Setup → Custom Metadata Types → Pendo Target Agent → Manage Records → New
- Set Agent API Name to the value from Step 5.
- Check Is Active.
- Save.
Or deploy a metadata record — create salesforce-pendo-agent-analytics/main/data-cloud/customMetadata/Pendo_Target_Agent.<Name>.md-meta.xml and deploy it. (If your Salesforce CLI version rejects the custom-metadata deploy, create the record via the Setup UI above, or via the Apex Metadata API — Metadata.Operations.enqueueDeployment.)
Now that the trigger object exists, deploy the Flow and its definition:
sf project deploy start \
--source-dir salesforce-pendo-agent-analytics/main/data-cloud/flows \
--source-dir salesforce-pendo-agent-analytics/main/data-cloud/flowDefinitions \
--target-org pendo-targetThe Flow deploys as Draft.
- Setup → Flows
- Open Agentforce to Pendo Conversation Sync.
- Click Activate.
The Flow will not capture anything until it is active. It begins firing on new conversations immediately after activation.
See Verifying It Works. Expect a batch delay of roughly 30–40 minutes for the first conversations to appear via the Data Cloud path.
Skip this section if you are using the Data Cloud approach.
sf project deploy start \
--source-dir salesforce-pendo-agent-analytics/main/base \
--source-dir salesforce-pendo-agent-analytics/main/agentforce-custom-action \
--target-org pendo-targetThen run the tests:
sf apex run test --target-org pendo-target --test-level RunLocalTests --wait 10 --result-format humanSame as the Data Cloud approach:
- Setup → Custom Settings → Pendo Config → Manage → New
- Enter
API_Key__c(Track Event Shared Secret) andAgent_Id__c(Pendo Agent ID). - Save.
- Setup → Named Credentials → Pendo API
- Confirm the URL is
https://data.pendo.io.
The Agentforce_Employee_Agent deploys with everything already configured:
- The
Conversation IDcontext variable is included in the bot metadata - Both
Generate Conversation IDandLog Conversation Turn to Pendoactions are wired into theGeneralFAQtopic - Simply activate the agent in Setup → Agentforce Agents after deployment
Follow these steps for each agent you want to track.
Create the Conversation ID context variable
-
Go to Setup → Agentforce Agents → [your agent] → Open in Builder
-
Click the Context tab on the left
-
Under Custom Variables, click New Variable
-
Set the following:
Field Value Label Conversation IDAPI Name Conversation_IDData Type Text Description Stores the unique conversation ID for the current session. Persists across all turns and is used as conversationId in Pendo. -
Click Save
Add the two actions to every topic on your agent
Important: You must add both actions to every topic (subagent) on your agent. If a topic handles a conversation turn without these actions, that turn will not be captured in Pendo.
For each topic on your agent:
-
Open the topic in Agent Builder
-
Add Generate Conversation ID action and map:
Parameter Map to Current Conversation ID (input) Conversation_IDvariableConversation ID (output) Conversation_IDvariable -
Add Log Conversation Turn to Pendo action and map:
Parameter Map to User Prompt The user's original message Agent Response The agent's reply Session ID Conversation_IDvariable
Confirm the action order within each topic
Turn starts
↓
Generate Conversation ID ← always first, before reading user message
↓
Agent processes and responds
↓
Log Conversation Turn to Pendo ← always last, after response is ready
- In Pendo, go to Subscription Settings → Applications.
- Select the Application associated with your configured Agent.
- Open the Raw Events tab.
- Filter to your visitor — the Salesforce user email of the person who chatted.
- Confirm both a
promptand anagentResponseevent per turn.
Read
receivedTime, not the conversation timestamp. Each event carries the time the conversation happened as a property — that is not when Pendo received it. Use the event'sreceivedTime(epoch milliseconds) to judge arrival. On the Data Cloud path the two can differ by the full ingestion delay.
- Custom Action approach: synchronous — events arrive in near real-time.
- Data Cloud approach: batched via Data Cloud ingestion — typically ~30–40 minutes, delivered in batches rather than per turn. A delay here is normal and does not indicate a failure.
Add a debug trace for the running user (Custom Action) or check the Automated Process user's logs (Data Cloud) and look for:
Success: both messages sent to Pendo. SessionId: ...
The fastest possible signal, useful before waiting on the Data Cloud batch (and handy for automated deployments). It invokes the adapter directly against a real interaction row and asserts success. isSuccess = true means both callouts returned HTTP 200 — the service throws on any non-2xx response.
List<SObject> rows = Database.query(
'SELECT ssot__AiAgentSessionId__c, ssot__ContentText__c, ssot__AiAgentInteractionId__c, ' +
'ssot__AiAgentSessionParticipantId__c, ssot__Id__c, ssot__MessageSentTimestamp__c ' +
'FROM ssot__AiAgentInteractionMessage__dlm ' +
'WHERE ssot__AiAgentInteractionMessageType__c = \'Output\' LIMIT 1');
if (rows.isEmpty()) { System.debug('No Output rows yet — wait for ingestion.'); return; }
SObject r = rows[0];
DataCloudConversationAdapter.FlowInput fi = new DataCloudConversationAdapter.FlowInput();
fi.sessionId = (String) r.get('ssot__AiAgentSessionId__c');
fi.messageType = 'Output';
fi.content = (String) r.get('ssot__ContentText__c');
fi.interactionId = (String) r.get('ssot__AiAgentInteractionId__c');
fi.participantId = (String) r.get('ssot__AiAgentSessionParticipantId__c');
fi.messageId = (String) r.get('ssot__Id__c');
fi.messageTimestamp = (Datetime) r.get('ssot__MessageSentTimestamp__c');
for (DataCloudConversationAdapter.FlowOutput o :
DataCloudConversationAdapter.handleMessage(new List<DataCloudConversationAdapter.FlowInput>{ fi })) {
System.debug('isSuccess=' + o.isSuccess + ' | ' + o.status);
}Run it with sf apex run --file <script>.apex --target-org pendo-target and look for isSuccess=true. This sends real events to Pendo, so you can confirm them in Raw Events immediately afterward.
| Symptom | Likely cause | Fix |
|---|---|---|
Pendo_Config__c record not found |
Custom Setting org-default not created | Add the org-default record in Setup → Custom Settings → Pendo Config |
Pendo API Key is blank |
Record saved without a value | Edit the Custom Setting and add your key |
No active Pendo_Target_Agent__mdt records |
No active allowlist record (Data Cloud) | Add or activate a record in Custom Metadata Types |
| Agent's turns aren't captured (Data Cloud) | Allowlist value doesn't match Data Cloud's recorded name | Set Agent_API_Name__c to the exact ssot__AiAgentApiName__c value (Step 5) |
| Flow deploy: "We can't find the object specified in the Start element" | Deployed the Flow before Session Tracing created the object | Enable Session Tracing, wait 15–30 min, then deploy the Flow (Steps 4 → 7) |
| Flow not firing | Flow still in Draft | Activate it in Setup → Flows |
| Flow active but no events | Session Tracing not enabled, or too soon after enabling | Enable Session Tracing and allow 15–30 min |
| "Events look ~40 min late" / timestamps confusing | Normal Data Cloud batch latency; you're reading the conversation timestamp | Judge arrival by the event's receivedTime, not the embedded conversation time |
Error [Callout] |
Network or URL issue | Confirm the Named Credential URL is https://data.pendo.io (or your region's host) |
Error [RateLimit] |
Pendo returned 429 | Reduce conversation volume or contact Pendo support |
| No data in Pendo | Wrong Agent ID, or wrong subscription/region | Confirm the Agent ID in the Custom Setting and that you're viewing the same Pendo subscription as the API key |
| Events not visible under a browser filter in Pendo | Server-side callouts aren't browser events | Remove browser filters — these events originate from Salesforce, not a browser |
| Conversations not grouped (Custom Action) | Conversation_ID variable not mapped consistently |
Map both input and output of Generate Conversation ID to the same variable |
- API keys live in the
Pendo_Config__ccustom setting — never in code or the repo. - The Pendo endpoint URL is in a Named Credential — Apex never builds raw URLs.
- Data Cloud queries use
WITH USER_MODE— Salesforce sharing rules are enforced. - Messages containing Salesforce-masked sensitive data are skipped.
- No secrets of any kind are committed to this repository.