Docs
Render Inbox, Composer, Scout, and Sequences inside any host app. Your server mints a short-lived signed token; StrykePoint mints a scoped session cookie. No second login.
HMAC-SHA256, max 5 min, single-use jti, origin-locked.
One <script> tag, one mount() call.
Events for ready, unread counts, reply sent, navigate.
In StrykePoint, open Admin → Embed, enable the add-on, then create a key. Name it, paste your host origin(s) (e.g. https://your-app.com), and pick the surfaces you want available (inbox, composer, scout, sequences). You'll see the secret once — store it in your server's secret manager.
Tokens are short-lived (5 min) and single-use. The secret never leaves your server.
// Node / TanStack / any JS edge runtime
import { createHmac } from "node:crypto";
function b64url(s) {
return Buffer.from(s).toString("base64")
.replace(/=+$/g, "").replace(/\+/g, "-").replace(/\//g, "_");
}
export function mintEmbedToken({ publishableKey, secret, user, scopes }) {
const header = { alg: "HS256", typ: "JWT" };
const now = Math.floor(Date.now() / 1000);
const payload = {
pk: publishableKey,
sub: user.id, // your stable user id
email: user.email,
name: user.name,
scope: scopes, // ["inbox","composer","scout","sequences"]
aud: "strykepoint",
iss: "https://your-app.com",
iat: now,
exp: now + 5 * 60, // max 5 minutes
jti: crypto.randomUUID(), // single-use
};
const h = b64url(JSON.stringify(header));
const p = b64url(JSON.stringify(payload));
const sig = createHmac("sha256", secret).update(`${h}.${p}`).digest();
const s = sig.toString("base64")
.replace(/=+$/g, "").replace(/\+/g, "-").replace(/\//g, "_");
return `${h}.${p}.${s}`;
}# Python
import time, uuid, hmac, hashlib, base64, json
def b64url(b: bytes) -> str:
return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
def mint_embed_token(pk: str, secret: str, user: dict, scopes: list[str]) -> str:
header = {"alg":"HS256","typ":"JWT"}
now = int(time.time())
payload = {
"pk": pk, "sub": user["id"], "email": user.get("email"),
"name": user.get("name"), "scope": scopes,
"aud": "strykepoint", "iss": "https://your-app.com",
"iat": now, "exp": now + 300, "jti": str(uuid.uuid4()),
}
h = b64url(json.dumps(header, separators=(",",":")).encode())
p = b64url(json.dumps(payload, separators=(",",":")).encode())
sig = hmac.new(secret.encode(), f"{h}.{p}".encode(), hashlib.sha256).digest()
return f"{h}.{p}.{b64url(sig)}"Pass the token your server minted into Strykepoint.mount(). The SDK builds the iframe and wires up the postMessage bridge.
<div id="strykepoint-panel" style="height:600px"></div>
<script src="https://app.strykepoint.ai/embed/sdk.js"></script>
<script>
// `token` comes from your server (never expose the secret to the browser).
const panel = Strykepoint.mount('#strykepoint-panel', {
publishableKey: 'pk_live_...',
token: window.__SP_TOKEN__,
surface: 'inbox', // inbox | composer | scout | sequences
onEvent: (e) => {
if (e.type === 'ready') console.log('embed ready', e.scopes);
if (e.type === 'unread_count') console.log('unread', e.n);
if (e.type === 'reply_sent') console.log('replied', e.threadId);
},
});
// panel.open('thread_123'); panel.refresh(); panel.destroy();
</script>| Field | Type | Notes |
|---|---|---|
| pk | string | Publishable key from the embed key page. |
| sub | string | Your stable user id for this end-user. |
| string? | Optional. Used to populate the embed identity. | |
| name | string? | Optional display name. |
| scope | string[] | Subset of the key's scopes to grant this token. |
| aud | string | Must equal "strykepoint". |
| iss | string | Your host origin, e.g. https://your-app.com. |
| exp | number | Unix seconds, ≤ now + 300. |
| jti | string | Unique per token (UUID). Replay-protected. |
| ws | string? | Optional workspace override; must match the key's workspace. |
| Field | Type | Notes |
|---|---|---|
| ready | { surface, scopes } | Fires once after the bridge connects. |
| unread_count | { n } | New unread DMs across connected channels. |
| reply_sent | { threadId } | User sent a reply from the embed. |
| navigate | { to } | User asked to open something outside the embed. |
The handle returned from mount() exposes open(id), refresh(), send(msg), and destroy(). Under the hood these postMessage to the iframe with { source: "strykepoint-host", ... }.
For badge counts and lightweight server-to-server reads you don't need to render the iframe. Sign each request with the same secret. Send these headers:
| Field | Type | Notes |
|---|---|---|
| x-sp-key | string | Your publishable key (pk_live_...). |
| x-sp-ts | number | Unix seconds; ±5 min clock skew allowed. |
| x-sp-sig | hex | HMAC-SHA256 of `${ts}\n${METHOD}\n${path}\n${bodyHash}` using the secret. |
bodyHash is HMAC-SHA256("strykepoint.embed.body", rawBody) in hex ("" for GET).
// Sign a read-only API request (Node)
import { createHmac } from "node:crypto";
export function signEmbedRequest({ secret, method, path, body = "" }) {
const ts = Math.floor(Date.now() / 1000).toString();
const bodyHash = createHmac("sha256", "strykepoint.embed.body")
.update(body).digest("hex");
const msg = `${ts}\n${method.toUpperCase()}\n${path}\n${bodyHash}`;
const sig = createHmac("sha256", secret).update(msg).digest("hex");
return { ts, sig };
}
// Usage:
const { ts, sig } = signEmbedRequest({
secret: process.env.SP_EMBED_SECRET,
method: "GET",
path: "/api/public/embed/v1/inbox/unread-count",
});
await fetch("https://app.strykepoint.ai/api/public/embed/v1/inbox/unread-count", {
headers: { "x-sp-key": "pk_live_...", "x-sp-ts": ts, "x-sp-sig": sig },
});Available endpoints:
| Field | Type | Notes |
|---|---|---|
| GET /v1/health | — | Sanity check. Returns workspace id, scopes, server time. |
| GET /v1/inbox/unread-count | scope: inbox | Returns { n, window } — replies received in the last 14 days. |
exp ≤ 5 minutes and a fresh jti per token.