Embed SSO signs visitors into your Delphi embed as people your site, community, or newsletter already knows. Their conversations follow them across devices, and you see who they are in your inbox.
Each embed has its own SSO configuration. In Delphi, open Integrations, pick the embed, and go to the Identity tab. If you don't see that tab, contact support@delphi.ai to enable SSO for your account.
There are three ways to sign visitors in:
| Mode | Use it when | Code on your page |
|---|---|---|
| Custom JWT | Visitors sign in to your own site | Your server signs a token; your page hands it over |
| Mighty Networks | The embed lives on a Mighty Networks space | None |
| Substack | Your audience is your Substack subscribers | None |
In the API these are the jwt_public_key, jwks, and oauth verification
modes.
Copy the snippet from the embed's Configuration tab. It looks like this:
<script
src="https://www.delphi.ai/embed.js"
data-channel="YOUR_EMBED_ID"
data-mode="inline"
data-width="100%"
data-height="600"
async
></script>Use data-mode="bubble" for the floating chat bubble. Older snippets that load
from embed.delphi.ai and configure window.delphi = { ... } keep working and
expose the same window.Delphi API described below.
Your server signs a short-lived RS256 JWT for the signed-in visitor. Your page hands it to the embed. Delphi verifies the signature against the public key you saved for that embed. The private key never leaves your server.
sequenceDiagram
participant Browser as Your page
participant Server as Your server
participant Embed as Delphi embed
Browser->>Server: Visitor is signed in to your site
Server-->>Browser: JWT signed with your private key
Browser->>Embed: window.Delphi.login(jwt)
Embed->>Embed: Verify signature with your public key
Embed-->>Browser: Visitor is signed in to the embed
Option A: generate it in Delphi. On the Identity tab, choose Custom JWT,
turn on Enable SSO, and click Generate Key Pair. Delphi saves the public
key, turns SSO on, and downloads delphi-embed-private-key.pem. The private key
is generated in your browser and is never sent to Delphi, so store the download
somewhere safe right away.
Option B: bring your own.
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out private_key.pem
openssl pkey -in private_key.pem -pubout -out public_key.pemPaste the contents of public_key.pem into Public Key (PEM SPKI), including
the BEGIN PUBLIC KEY and END PUBLIC KEY lines, then click Save
Configuration.
Only sign tokens for visitors you've already authenticated. The token must use RS256.
| Claim | Required | Meaning |
|---|---|---|
sub |
Yes* | Stable, unique ID for the visitor in your system. The same sub is always the same visitor. |
email |
No | The visitor's email. If sub is missing, Delphi uses the email as the visitor ID. |
name |
No | Display name shown in your inbox. |
exp |
Recommended | Expiry. Keep it short; the sample uses one hour. |
iss |
No | Checked only if you set Issuer on the Identity tab. |
aud |
No | Checked only if you set Audience on the Identity tab. |
*A token without sub is accepted only if it has email.
If your tokens already use different claim names for the visitor ID or email,
set Subject Claim and Email Claim on the Identity tab instead of
changing your tokens. Emails with a + alias are rejected.
With jose in Node.js:
import { importPKCS8, SignJWT } from "jose";
const privateKey = await importPKCS8(process.env.DELPHI_PRIVATE_SSO_KEY!, "RS256");
const token = await new SignJWT({ email: user.email, name: user.name })
.setProtectedHeader({ alg: "RS256" })
.setSubject(user.id)
.setIssuedAt()
.setExpirationTime("1h")
.sign(privateKey);With the embed.js snippet, call the script's API:
window.Delphi.login(token); // sign the visitor in
window.Delphi.logout(); // back to an anonymous visitorThe embed posts { type: "delphi:ready" } to your page once it can accept a
sign-in. To sign visitors in as the page loads, wait for that message:
window.addEventListener("message", (event) => {
if (event.origin !== "https://www.delphi.ai") return;
if (event.data?.type === "delphi:ready") window.Delphi.login(token);
});https://www.delphi.ai is the origin of the src in your snippet. delphi:ready can fire again, for example when the visitor returns to the tab.
Calling login again with a token for the same visitor is safe.
With a plain <iframe> (no embed.js), post the token into the frame:
const frame = document.getElementById("delphi-frame");
const delphiOrigin = "https://www.delphi.ai";
window.addEventListener("message", (event) => {
if (event.source !== frame.contentWindow || event.origin !== delphiOrigin) return;
if (event.data?.type === "delphi:ready") {
frame.contentWindow.postMessage({ type: "sso_login", token }, delphiOrigin);
}
});
// To sign out:
frame.contentWindow.postMessage({ type: "sso_logout" }, delphiOrigin);The embed only accepts these messages from the page that embeds it. Always pass
the Delphi origin as the targetOrigin, never "*".
On the Identity tab, paste a token into Test JWT and click Test JWT. It checks the signature against your saved public key and shows the subject, email, and expiry it read. The check runs in your browser.
An expired token fails with Invalid SSO token: token has expired. Mint a fresh
token on each page load rather than caching one. Sending an expired token
doesn't sign out a visitor who is already signed in.
Mighty Networks members who are signed in to your space are recognized automatically. Mighty Networks posts a signed token into the embed, and Delphi verifies it against Mighty Networks' published keys. There's nothing to install beyond the embed snippet.
Delphi sets this mode up for your embed; contact support@delphi.ai. Once it's set up, use Enable SSO on the Identity tab to turn it on or off.
Paid subscribers sign in with Substack from inside the embed. There's no code on your page: the embed opens a Substack sign-in popup, and Delphi signs the visitor in when it closes.
-
In your Substack OAuth app, register this redirect URI exactly:
https://delphi.ai/api/embed/oauth/substack/callback -
On the Identity tab, choose Substack under How visitors sign in.
-
Enter your Publication host (for example
newsletter.substack.comor your custom domain) and your OAuth client ID, then click Save Configuration.
Paid subscribers get subscriber access. Anyone else is sent to your publication's subscribe page.
- Keep the private key on your server, in a secret manager. Never ship it to the browser or commit it. If it leaks, generate a new key pair; tokens signed with the old key stop working once you save the new public key.
- Keep
expshort to limit replay. - Use a
subthat never changes and is never reused for a different person.
sample-sso-app is a Next.js app that installs the embed with
embed.js, signs a Custom JWT in a server action, and signs a demo visitor in
and out with window.Delphi.login and window.Delphi.logout. See its
README to run it.