Hi @Mohit Lakde,
That’s a good question. I’ve asked our internal AI assistant to help me figure this out and here is the proposition it come up with:
On Salesforce (LWC/Experience Cloud) you need to (a) load the Intercom widget correctly under CSP, and (b) boot/update it with your signed user data (user_hash). Here’s a working recipe you can drop in.
1) Allow the domains in CSP Trusted Sites
Setup → Security → CSP Trusted Sites
Add (all as script + connect as applicable):
-
https://widget.intercom.io
-
https://js.intercomcdn.com
-
https://api-iam.intercom.io (or https://api-iam.eu.intercom.io if your Intercom workspace is in EU)
2) Generate user_hash server-side (Apex)
Identity verification for Web requires HMAC-SHA256 over your user_id with your Intercom Identity Verification secret. Do this in Apex and pass it to the LWC (do not compute in the browser).
public with sharing class IntercomCtrl {
@AuraEnabled(cacheable=true)
public static String getUserHash(String userId) {
// Store your Intercom identity secret securely (Named Credential/Protected Custom Metadata)
String secret = System.Label.INTERCOM_IDENTITY_SECRET;
Blob mac = Crypto.generateMac('HmacSHA256', Blob.valueOf(userId), Blob.valueOf(secret));
return EncodingUtil.convertToHex(mac); // Intercom expects hex
}
@AuraEnabled(cacheable=true)
public static User getMe() {
return [SELECT Id, Name, Email, CreatedDate FROM User WHERE Id = :UserInfo.getUserId()];
}
}
3) LWC that boots Intercom with signed user data
This LWC loads the widget, boots once, and sends name, email, user_id, and user_hash.
// intercomMessenger.js
import { LightningElement, track } from 'lwc';
import getUserHash from '@salesforce/apex/IntercomCtrl.getUserHash';
import getMe from '@salesforce/apex/IntercomCtrl.getMe';
import USER_ID from '@salesforce/user/Id';
const APP_ID = 'YOUR_APP_ID'; // <-- replace
const API_BASE = 'https://api-iam.intercom.io'; // or EU endpoint
export default class IntercomMessenger extends LightningElement {
initialized = false;
@track me;
@track hash;
async connectedCallback() {
try {
// fetch user + hash in parallel
const [me, hash] = await Promise.all([
getMe(),
getUserHash({ userId: USER_ID })
]);
this.me = me;
this.hash = hash;
// 1) prepare settings
window.intercomSettings = {
app_id: APP_ID,
api_base: API_BASE,
name: this.me.Name,
email: this.me.Email,
user_id: this.me.Id,
user_hash: this.hash,
created_at: Math.floor(new Date(this.me.CreatedDate).getTime() / 1000) // optional but nice
};
// 2) load the widget script if needed, then boot once
this.loadAndBoot();
} catch (e) {
// If you see "Missing user_hash", double-check step #2 and the secret
// If you see "Intercom not loaded", check CSP Trusted Sites (step #1)
// and that the script actually loads.
// eslint-disable-next-line no-console
console.error(e);
}
}
loadAndBoot() {
if (this.initialized) return;
const doBoot = () => {
if (!window.Intercom) return;
window.Intercom('boot', window.intercomSettings);
this.initialized = true;
};
if (window.Intercom) {
doBoot();
return;
}
const s = document.createElement('script');
s.src = `https://widget.intercom.io/widget/${APP_ID}`;
s.async = true;
s.onload = doBoot;
document.body.appendChild(s);
}
// Optional: shutdown on component unload/logout
disconnectedCallback() {
if (window.Intercom) window.Intercom('shutdown');
}
}
What this fixes vs your snippet
-
Loads the widget script (widget.intercom.io/widget/{APP_ID}); without this, Intercom('boot'| 'update') isn’t available.
-
Sends identity-verified fields (user_id + user_hash) to stop the “Missing user_hash” / “Refusing update for identified user” errors.
-
Uses the correct region via api_base.
-
Boots once (then you can call Intercom('update', {...}) later if profile data changes).
4) SPA/Experience Cloud notes
-
If your site navigates without full page reloads, call window.Intercom('update', {...}) when user attributes change.
-
On logout, call window.Intercom('shutdown').
5) Quick checklist if you still see errors
-
CSP: all three domains allowed (widget, js CDN, api-iam).
-
Region: api_base matches your Intercom data hosting region.
-
user_hash: computed server-side with the same user_id you send to Intercom.
-
Script actually loads: open DevTools → Network, confirm widget/{APP_ID} returns 200.
If you paste this in and replace YOUR_APP_ID (and wire your secret), you’ll get the Messenger booted with name, email, user_id, and a valid user_hash on your authenticated site.
Let me know if that resolved your issue.