Salesforce worked example

A real Lightning Web Component + Apex integration — salesforce-confcall — embeds a live confcall room on a Salesforce record page and writes call outcomes back as Activities. It's a working Dev-org proof of concept, built against the same embed API documented on this site.

The snippets below are taken verbatim from the salesforce-confcall sample (a standalone sfdx project that deploys to a Salesforce org): the Apex broker ConfcallController.cls, the conferenceRoom Lightning Web Component, and the backend smoke.sh. They are trimmed for length but not paraphrased.

Architecture

1 · De-risk the backend first (scripts/smoke.sh)

Before any Salesforce, the sample proves the key + endpoints standalone — create a room, mint a session, print an embed URL you can open in a browser:

KEY="${1:?pass your sk_test_ key}"
BASE="${2:-https://meet.confcall.app}"

# 1) create embed room
ROOM_JSON=$(curl -sS -X POST "$BASE/api/embed/rooms" \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"roomName":"sfdc-smoke"}')
ROOM_ID=$(printf '%s' "$ROOM_JSON" | sed -n 's/.*"roomId":"\([^"]*\)".*/\1/p')

# 2) mint host session token
SESS_JSON=$(curl -sS -X POST "$BASE/api/embed/sessions" \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d "{\"roomId\":\"$ROOM_ID\",\"externalUserId\":\"sfdc-smoke-user\",\"displayName\":\"Smoke Advisor\",\"role\":\"host\",\"ttlSeconds\":3600}")
TOKEN=$(printf '%s' "$SESS_JSON" | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')

echo "$BASE/embed/room/$ROOM_ID?token=$TOKEN&name=Smoke+Advisor"

2 · The Apex broker (ConfcallController.cls)

The sk_ secret is read only server-side (from custom metadata) and never reaches the browser. One Apex round-trip creates an embed room and mints a short-lived host token — exactly the two embed API calls:

public with sharing class ConfcallController {

    @AuraEnabled
    public static EmbedSession startSession(Id recordId) {
        Confcall_Setting__mdt c = cfg();          // holds Secret_Key__c + Base_Url__c
        String base   = c.Base_Url__c;
        String roomId = createRoom(base, c.Secret_Key__c, 'sfdc-' + recordId);
        String token  = mintSession(base, c.Secret_Key__c, roomId,
            UserInfo.getUserId(), UserInfo.getName(), 'host', 3600);

        EmbedSession s = new EmbedSession();
        s.roomId = roomId; s.token = token; s.displayName = UserInfo.getName();
        return s;
    }

    private static HttpResponse call(String endpoint, String key, Map<String, Object> payload) {
        HttpRequest req = new HttpRequest();
        req.setEndpoint(endpoint);
        req.setMethod('POST');
        req.setHeader('Content-Type', 'application/json');
        req.setHeader('X-API-Key', key);          // the sk_ key, server-side only
        req.setBody(JSON.serialize(payload));
        return new Http().send(req);
    }

    /** The value loop: turn a room lifecycle event into a completed Task on the record. */
    @AuraEnabled
    public static void logConferenceEvent(Id recordId, String kind, String payload) {
        insert new Task(WhatId = recordId, Subject = 'confcall: ' + kind,
            Description = payload, Status = 'Completed', ActivityDate = Date.today());
    }
}

3 · The Lightning Web Component (conferenceRoom.js)

The LWC calls startSession, then creates the <conference-room> element with the room id + host token (injection mode → no popup login) and forwards its CustomEvents back to Apex as Activities:

const session = await startSession({ recordId: this.recordId });

const el = document.createElement('conference-room');
el.setAttribute('room-id', session.roomId);
el.setAttribute('base-url', 'https://meet.confcall.app');
el.setAttribute('auth-token', session.token);       // injection mode → no popup login
el.setAttribute('display-name', session.displayName);
el.style.height = '640px';                           // iframe is height:100%, needs a resolved height

el.addEventListener('conference-joined',            (e) => this._log('Call started', e.detail));
el.addEventListener('conference-participant-joined',(e) => this._log('Participant joined', e.detail));
el.addEventListener('conference-meeting-ended',     (e) => this._teardown('Call ended', e.detail, el));

this.template.querySelector('.room').appendChild(el); // lwc:dom="manual" — LWS blocks unknown tags in markup

The widget bundle is loaded from a Static Resource (an external <script> is blocked in Salesforce), and the custom element is created in JS under lwc:dom="manual" because Lightning Web Security blocks unknown tags in template markup.

Production hardening (open in RFC-Conference-Room-Embed Phase 5): move the sk_ key into a Named + External Credential (auto-injected X-API-Key, encrypted at rest) instead of admin-readable Custom Metadata; reuse one persisted room per record rather than a fresh room per "Start"; and verify camera/mic inside the Lightning iframe.

Status

Working Dev-org PoC: the LWC render, the Apex-minted host session, and the Activity/Task write-back are built and verified. Named-Credential secret storage, persisted room reuse, and a buyer demo remain open — see RFC-Conference-Room-Embed Phase 5 for the current acceptance criteria.