Your application owns the login. Summer accepts a short-lived statement signed by your server, scoped to one installation and the widget's current challenge. You can use your existing account IDs; they do not need to be Rebody IDs or UUIDs.
Keep the signing key on your server
Summer shows a new installation's signing key once. Store it in your server's secret manager alongside its installation ID and key ID. Never return the key to the browser or add it to a public environment variable. Key rotation invalidates existing widget sessions.
You supply this Summer signing key to your own backend. You do not need to supply clinical-system API keys to Summer for this integration.
Add the browser callback
Replace YOUR_INSTALLATION_UUID with your installation ID. See Find your installation ID.
<script>
window.summerMessengerConfig = {
installationId: "YOUR_INSTALLATION_UUID",
getIdentityAssertion: async ({ nonce }) => {
const response = await fetch("/api/summer/identity", {
method: "POST",
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ frame_nonce: nonce })
});
if (!response.ok) throw new Error("Identity unavailable");
const result = await response.json();
if (result.kind === "guest") return null;
if (result.kind !== "authenticated" || typeof result.assertion !== "string") {
throw new Error("Invalid identity response");
}
return result.assertion;
}
};
</script>
<script src="https://gosummer.ai/messenger/v1/loader.js" async></script>Implement the server endpoint
Check same-origin requests and verify your application's current login using your authentication library. Accept the frame nonce, not a browser-supplied user ID. Derive the account ID, session ID and session expiry from the verified login.
Return { "kind": "guest" } only when no login is present. An invalid login, failed refresh or authentication-service error must return an error, not silently become a visitor.
Send Cache-Control: private, no-store on guest and authenticated responses, and exclude this endpoint from CDN or application response caching. Each authenticated response contains a fresh assertion for that request's nonce and login.
Create an HS256 JWT with this header and payload:
{
"alg": "HS256",
"typ": "JWT",
"kid": "YOUR_KEY_UUID"
}{
"iss": "summer-installation:YOUR_INSTALLATION_UUID",
"aud": "summer-messenger:customer-session:v1",
"installation_id": "YOUR_INSTALLATION_UUID",
"identity_namespace": "host-account",
"sub": "YOUR_STABLE_ACCOUNT_ID",
"host_session_ref": "SHA256_OF_INSTALLATION_AND_SESSION_ID",
"host_session_expires_at": 1792458000,
"parent_origin": "https://www.example.com",
"frame_nonce": "THE_NONCE_FROM_THE_WIDGET",
"iat": 1792454400,
"exp": 1792454460,
"jti": "A_FRESH_RANDOM_UUID"
}The timestamps above show the format only. Generate current Unix timestamps in seconds: iat is now, exp is now plus 60 seconds, and host_session_expires_at comes from your actual login. Use a cryptographic random UUID for each jti.
sub is a stable opaque account ID, 1 to 256 characters. Avoid email addresses and medical information. host_session_ref is a lowercase SHA-256 hex digest of your installation ID and actual session ID. parent_origin comes from trusted server configuration and must exactly match an allowed website origin. Validate the browser's nonce against ^[A-Za-z0-9_-]{32,128}$ before signing.
Node.js signing example
After you have verified the login and validated the nonce, construct the payload above and sign it using Node's built-in crypto library:
import { createHmac, createHash, randomUUID } from "node:crypto";
const now = Math.floor(Date.now() / 1000);
const payload = {
iss: `summer-installation:${installationId}`,
aud: "summer-messenger:customer-session:v1",
installation_id: installationId,
identity_namespace: "host-account",
sub: verifiedUser.id,
host_session_ref: createHash("sha256")
.update(`${installationId}:${verifiedSession.id}`).digest("hex"),
host_session_expires_at: verifiedSession.expiresAtSeconds,
parent_origin: configuredWebsiteOrigin,
frame_nonce: validatedNonce,
iat: now,
exp: now + 60,
jti: randomUUID()
};
const encode = value => Buffer.from(JSON.stringify(value)).toString("base64url");
const data = `${encode({ alg: "HS256", typ: "JWT", kid: keyId })}.${encode(payload)}`;
const signature = createHmac("sha256", signingKey).update(data).digest("base64url");
const assertion = `${data}.${signature}`;
// Return { kind: "authenticated", assertion } with Cache-Control: private, no-store.The variables beginning with verified must come from your server's authenticated session. They are not fields to accept from the request body. For patient-specific links, add the optional care context before signing.
Login changes and renewal
After login or an account switch, call await window.SummerMessenger.refreshIdentity(). This method shuts down the previous widget session, clears its visible conversation, and boots the same configuration against the current host login. Handle a rejected refresh in your website UI.
For logout, call await window.SummerMessenger.shutdown() before clearing the host session. The result is { revoked: true } when Summer confirmed revocation, or { revoked: false } when it could not confirm it. In either case, the old frame and its local state are removed. Your logout flow should complete its own session invalidation and handle the unconfirmed widget revocation without claiming it succeeded.
shutdown() also clears the widget configuration. To show a visitor widget after logout, explicitly call window.SummerMessenger.boot(window.summerMessengerConfig) after the host logout completes. Do this only if your installation allows visitors. Calling refreshIdentity() after shutdown() fails because there is no active configuration to refresh.
The widget automatically requests fresh identity while open as its short session expires. Your endpoint must check the current login each time.
Rebody uses a reserved built-in adapter. Other companies use host-account; do not copy Rebody's namespace or endpoints.