Embedded components

Show the Swarmhit inbox, tasks and campaigns inside your own product, each user reaching only the LinkedIn accounts and actions you give them.

Embedded components put live Swarmhit screens inside your product: the inbox, the tasks and the campaigns. Each of your users reaches only the LinkedIn accounts you assign them, and only what you allow them to do. It takes one endpoint on your backend and a few lines on your page.

ElementWhat it shows
<swarmhit-inbox>The conversations of the user's LinkedIn accounts, with the lead panel.
<swarmhit-tasks>Calls to make, and AI comment and voice-note drafts waiting for approval.
<swarmhit-campaigns>The campaigns that send only from the user's accounts, with the Analytics, Leads, Sequence, Senders and Config tabs.

New messages, replies and campaign progress appear live, and every improvement Swarmhit ships reaches your users with nothing to update on your side.

How it works

  1. Your backend creates a session for one of your users with Create an embedded session, using your API key. It gets back a one-time client secret.
  2. Your page hands that secret to the element, which signs the user in.
  3. The element shows the Swarmhit screen for that user.

Your API key never reaches the browser: the page only ever sees a one-time secret. What each user reaches is enforced by Swarmhit's servers, not by hiding buttons, so a user can only ever reach the accounts and actions your backend named for them. Embedded users have no access to your workspace settings, members, billing or LinkedIn account management.

Before you start

  • Embedded components turned on for your workspace. Ask us. Until then, creating a session answers 403 with code: feature_disabled.
  • An API key, created on the Access tokens page in Swarmhit (the API keys card) and stored as a secret on your backend. See Authentication.
  • Your LinkedIn account ids, from GET /accounts. Map each of your users to the accounts they work: a recruiter to their own account, an assistant to their own and the CEO's.

Quickstart

A complete example: an Express server with the session endpoint, and a page that shows the inbox. You need Node 18 or later.

1. Add the session endpoint

The endpoint must know who is signed in from your own session, never from the request body or a query parameter, and decide that user's accounts and permissions on the server. It asks Swarmhit for a fresh secret on every call and never caches it.

server.mjs
import express from 'express';

const app = express();
app.use(express.static('public'));

// Replace with your own login. The user comes from your session, never from the request.
const requireLogin = (req, res, next) => {
  req.user = { id: 'recruiter_481', name: 'Jane Doe', swarmhitAccountIds: [process.env.SWARMHIT_ACCOUNT_ID] };
  next();
};

app.post('/api/swarmhit-session', requireLogin, async (req, res) => {
  const r = await fetch('https://app.swarmhit.com/api/v1/embed/sessions', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.SWARMHIT_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      user: { id: String(req.user.id), name: req.user.name },
      accountIds: req.user.swarmhitAccountIds,
      permissions: ['inbox', 'tasks', 'campaigns.analytics', 'campaigns.leads.manage', 'campaigns.status'],
    }),
  });
  if (!r.ok) {
    console.error('Swarmhit session failed', r.status, await r.text());
    return res.status(502).send('Could not start the Swarmhit session');
  }
  const { data } = await r.json();
  res.set('Cache-Control', 'no-store').type('text/plain').send(data.clientSecret);
});

app.listen(3000, () => console.log('Open http://localhost:3000'));

The request body:

FieldRequiredMeaning
user.idyesYour id for the user, up to 200 characters. The same id always maps to the same Swarmhit user.
user.namenoUp to 120 characters, shown as the author of what they do in Swarmhit.
accountIdsyes1 to 50 LinkedIn account ids of the workspace that owns the key.
permissionsyesWhat the user may do: see Permissions.

The response is { "data": { "clientSecret", "expiresAt", "user", "accountIds", "permissions" } }, where permissions lists every permission the session holds, with screens and implied reads spelled out.

2. Add the element to your page

Load embed.js once, place the elements where you want them, and call Swarmhit.init once with a function that asks your backend for a secret.

public/index.html
<!doctype html>
<html>
  <body style="margin: 0">
    <swarmhit-inbox theme="auto" style="height: 100vh"></swarmhit-inbox>

    <script src="https://app.swarmhit.com/embed.js"></script>
    <script>
      Swarmhit.init({
        fetchClientSecret: async () => {
          const res = await fetch('/api/swarmhit-session', { method: 'POST', credentials: 'include' });
          if (!res.ok) throw new Error('Could not start the Swarmhit session');
          return res.text();
        },
      });
    </script>
  </body>
</html>
  • Each element calls fetchClientSecret when it loads, and again by itself when its session ends, so the user is never asked to sign in.
  • The elements can be in the page before or after Swarmhit.init runs.
  • fetchClientSecret may return the secret as text, or an object holding it: the API's parsed JSON (data.clientSecret) or { clientSecret }.

3. Open the page

npm install express    # and @swarmhit/swarmhit-sdk for the SDK version
SWARMHIT_API_KEY=swh_live_... SWARMHIT_ACCOUNT_ID=<an id from GET /accounts> node server.mjs

Open http://localhost:3000. The inbox appears with the conversations of that LinkedIn account, and the element fires swarmhit:ready. Swap the element for <swarmhit-tasks> or <swarmhit-campaigns> to see the other screens. If it shows "Could not connect" instead, see Troubleshooting.

Permissions

permissions is the boundary of what a user can do, not a display setting. Swarmhit's servers check every action and every piece of data an embedded user requests against it, and refuse anything outside it. Screens and tabs the user lacks are not shown.

A screen's name (inbox, tasks, campaigns) grants everything in that screen. An edit or manage permission includes its read.

PermissionAllows
inboxEverything below in the inbox
inbox.readRead conversations and the lead panel, mark them read, star them
inbox.replyReply (with attachments and voice notes), mark leads interested, pause or stop their sequence
tasksEverything below in tasks
tasks.readSee calls to make and drafts waiting for approval
tasks.manageLog, skip and stop calls; approve, reject and re-draft comments and voice notes
campaignsEverything below in campaigns
campaigns.analyticsThe Analytics tab
campaigns.leadsThe Leads tab
campaigns.leads.managePause, resume, stop and retry leads, mark them interested
campaigns.sequenceThe Sequence tab, read only
campaigns.sequence.editEdit the sequence (messages, voice notes, delays, steps, templates)
campaigns.sendersThe Senders tab, read only
campaigns.senders.editChoose the campaign's senders, among the session's accountIds
campaigns.settingsThe Config tab, read only
campaigns.settings.editEdit the schedule and options, rename the campaign
campaigns.statusLaunch, pause and resume campaigns
campaigns.createCreate campaigns (blank or from a template) and duplicate them
campaigns.deleteDelete campaigns and their leads

Ready-made roles:

Rolepermissions
Recruiter who runs campaigns["inbox", "tasks", "campaigns.analytics", "campaigns.leads.manage", "campaigns.status"]
Manager who builds campaigns["inbox", "tasks", "campaigns"]
Read-only viewer["inbox.read", "tasks.read", "campaigns.analytics", "campaigns.sequence"]

What a user sees:

  • Conversations: those of their accountIds.
  • Campaigns: a campaign appears only when every sender it uses is one of their accounts. A campaign that also sends from someone else's account stays hidden, so that account's conversations never show.
  • New campaigns: a campaign the user creates sends from their accounts from the start.
  • Without campaigns.sequence: the user still sees each lead's stage, but never the messages.

Look and size

AttributeValuesDefault
themelight, dark, or auto (follows the visitor's system setting)light
accentA six-digit hex colour such as #0ea5e9, for buttons, links and highlightsSwarmhit indigo
campaignOn <swarmhit-campaigns>: the id of a campaign to openthe list
  • Live changes: all three attributes can change at any time without a reload. Switch the theme when your app does: el.setAttribute('theme', 'dark').
  • Sizing: an element is a block that fills the width it is given; you set the height. Give it a fixed height (height: 720px) or let it fill a flex or grid area (height: 100%, flex: 1). Without one it is 600px tall, and never less than 320px.
  • Narrow spaces: the screens adapt to the element's own width, not the window's, so they work in a side panel or on a phone.
  • No flash: the element stays transparent until its screen has painted, so a dark page never flashes white.

Events and opening a campaign

Events are dispatched on the element and bubble, so you can listen on the element or on document:

EventdetailWhen
swarmhit:ready{}The user is signed in and the screen is up.
swarmhit:error{ message }The session could not start, or fetchClientSecret failed.
swarmhit:campaign-created{ campaignId }The user created a campaign in <swarmhit-campaigns>.

The elements and the API work on the same data, so you can combine them:

  • Open a campaign from your own UI: set campaign="<id>" on <swarmhit-campaigns>, for example from a link on your job page.
  • Add leads from your platform: call Add leads to a campaign from your backend. For example, when swarmhit:campaign-created fires, attach the job's candidates to the new campaign. The Leads tab and the analytics update live.
  • React on your backend: subscribe to webhooks such as message.received or campaign.finished.
document.addEventListener('swarmhit:campaign-created', (e) => {
  // Your backend then calls POST /campaigns/{id}/leads with the job's candidates.
  fetch(`/api/jobs/${jobId}/swarmhit-campaign`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ campaignId: e.detail.campaignId }),
  });
});

Sessions

  • The secret works once, within 5 minutes. The session it starts lasts 12 hours, then the element asks for a new secret by itself.
  • Changing access: your latest session for a user sets what all of their open screens reach. Creating a session with fewer accounts or permissions signs their open screens out, and they sign back in by themselves with the narrower access.
  • Signing a user out everywhere: call End a user's embedded sessions when they sign out of your platform, leave your company or lose access. Secrets not used yet stop working and their open screens are signed out within moments. revoked is false when that user never had a session.
curl -X DELETE https://app.swarmhit.com/api/v1/embed/users/recruiter_481/sessions \
  -H "Authorization: Bearer swh_live_..."

With the SDK: await swarmhit.embed.users.revokeSessions('recruiter_481').

Security checklist

  • Keep the API key on your backend. Never put it in a page, an app bundle or a log.
  • Authenticate your own user before creating a session: the session endpoint sits behind your login.
  • Never trust a user id, account ids or permissions sent by the browser. Derive them on your backend from your own data.
  • If your site sets a Content-Security-Policy, allow script-src https://app.swarmhit.com and frame-src https://app.swarmhit.com.

Troubleshooting

SymptomCause and fix
403 with code: feature_disabledEmbedded components are not on for your workspace yet: contact us.
400 with code: invalid-request naming accountIdsAn id is not a LinkedIn account of the workspace that owns the key. List them with GET /accounts.
400 naming permissionspermissions is required and must use the names in Permissions.
The element shows "Could not connect" after about 20 secondsNo secret arrived: check that Swarmhit.init runs on the page and that fetchClientSecret resolves. swarmhit:error says why.
"This session is not valid any more"The secret was used already or is older than 5 minutes. Return a fresh one on every call and never cache it.
"This screen is not part of your access"The session has no permission for that element's screen.
A campaign is missing from the listIt sends from an account the user is not assigned. Assign that account too, or change the campaign's senders.
Nothing renders and the console shows a CSP errorAllow https://app.swarmhit.com in script-src and frame-src.

Go-live checklist

  • Embedded components are on for your production workspace.
  • The API key is a backend secret, and the session endpoint requires your login.
  • accountIds and permissions come from your own user data, on the server.
  • fetchClientSecret returns a fresh secret every time (Cache-Control: no-store).
  • Your CSP allows https://app.swarmhit.com for scripts and frames.
  • Each element has a height, fixed or filling its container.
  • Sessions are revoked when a user loses access.
  • You tested with your smallest role: what that user should not see is not there.

On this page