Customers can resume their own support context without exposing a signing secret or support API token to a browser or app bundle.
Keep signing on your backend
Your backend authenticates the current customer, signs a short-lived DobroDesk customer identity JWT for the selected integration and returns it from an endpoint protected by the host session. The endpoint response used by the mobile wrappers is JSON with an authorizationToken string.
The SDK does not mint this JWT and loadDobroDeskToken is not an SDK function. It is a host-provided function in the examples below. Never include the JWT signing secret, a DobroDesk support API token or a long-lived service credential in browser JavaScript, a mobile bundle or a public repository.
The web widget also supports an anonymous session. Native SDK integrations require a signed customer identity before network operations and widget presentation. Native requests send the identity as Authorization: Bearer ... with x-dobrodesk-client: native-v1; they do not send a browser Origin. After session start, the native engine owns the returned session proof and sends it as x-dobrodesk-session-token; the host must not construct or replace that header.
// This function belongs to your application. The route is your backend route.
async function loadDobroDeskToken(): Promise<string> {
const response = await fetch("/your-backend/dobrodesk-token", {
method: "POST",
credentials: "include",
headers: { Accept: "application/json" },
});
if (!response.ok) throw new Error("Customer token request failed");
const body = (await response.json()) as { authorizationToken?: unknown };
if (typeof body.authorizationToken !== "string" || !body.authorizationToken)
throw new Error("Customer token response is invalid");
return body.authorizationToken;
}
// Web can remain anonymous or apply the token after host login.
widget.setIdentityToken(await loadDobroDeskToken());
// React Native, NativeScript and Capacitor use the signed native shape.
const config = {
apiOrigin: "https://dobrodesk.com",
integrationId: "wgt_0123456789abcdef0123456789abcdef",
accountScope: currentCustomer.id,
credentialProvider: async () => ({
authorizationToken: await loadDobroDeskToken(),
}),
};Configure the signing secret and claims
In Admin > Channels > Website widgets, open the widget integration, set identity mode to Require a signed logged-in user and use Copy identity secret. Store the copied per-integration secret in your backend secret manager with the public Integration ID. The secret never belongs in a browser, mobile app bundle or public repository. Use it as the HS256 signing key.
Sign a three-part HS256 JWT with typ JWT. Required claims are iss = dobrodesk-widget:{integrationId}, aud = the public Integration ID, sub = a stable non-email customer ID, iat = current Unix seconds and exp no more than 15 minutes after iat. The verifier allows 60 seconds of clock skew. Optional claims are name, email, email_verified: true and jti. Set email_verified only after the host has verified the address.
const textEncoder = new TextEncoder();
const base64url = (bytes: Uint8Array) => {
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
};
// secretStore is your backend secret manager. Never run this in browser code.
export async function createDobroDeskIdentityToken(user: {
id: string;
email?: string;
emailVerified?: boolean;
name?: string;
}) {
const integrationId = await secretStore.get("DOBRODESK_WIDGET_ID");
const identitySecret = await secretStore.get("DOBRODESK_WIDGET_IDENTITY_SECRET");
const now = Math.floor(Date.now() / 1000);
const encode = (value: unknown) =>
base64url(textEncoder.encode(JSON.stringify(value)));
const header = encode({ alg: "HS256", typ: "JWT" });
const payload = encode({
iss: `dobrodesk-widget:${integrationId}`,
aud: integrationId,
sub: user.id,
iat: now,
exp: now + 5 * 60,
...(user.email
? {
email: user.email,
...(user.emailVerified === true ? { email_verified: true } : {}),
}
: {}),
...(user.name ? { name: user.name } : {}),
});
const key = await crypto.subtle.importKey(
"raw",
textEncoder.encode(identitySecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
);
const signature = base64url(
new Uint8Array(
await crypto.subtle.sign("HMAC", key, textEncoder.encode(`${header}.${payload}`)),
),
);
return `${header}.${payload}.${signature}`;
}Bind and clear identity deliberately
Set accountScope to a stable host customer or account identifier that does not contain secrets. It isolates durable native session and outbox state. Do not reuse one scope for multiple signed-in customers.
Call logout before the host session ends or accountScope changes when using Flutter, React Native, NativeScript or Capacitor. Direct iOS and Android clients expose resetSession instead: reset and discard that client, then create a new client for the next account. Call resetSession when the customer intentionally discards the current conversation but remains signed in. A token refresh is not an account switch and should use the same account scope.
Reject unsafe token states
- Reject an empty, malformed, expired or wrong-customer token in the host endpoint instead of returning a support credential.
- Treat a 401 or session-expired SDK error as a request for a fresh token for the same account. Do not silently switch accountScope or create a new session.
- If wrapper logout or direct native reset is requested while a token request is pending, ignore the late token and keep the next account isolated from the previous session.