跳转到内容

Sessions

此内容尚不支持你的语言。

A Session is one conversation between a caller and an Agent. This page is the exhaustive field-by-field reference for every Session operation, split into the two distinct ways to run one:

  • Dashboard/JWT session flowstartSession, sendMessage, endSession, interruptSession. Full JWT access, meant for a first-party dashboard or any client that already authenticates users directly against the platform.
  • SDK/embed flowsdkStart, refreshAvatarToken, sdkSendMessage, sdkEndSession. API-key + avatarToken auth, meant for embedding a live agent (e.g. the <vai-avatar> web component) into a third-party site or app. This is what most runtime integrators actually use.

For the end-to-end narrative version of this flow (why each call exists, in what order), see Core API Flow. For how the AI’s reply actually arrives, see Events & Subscriptions — every message-sending mutation on this page is fire-and-forget; the reply streams back asynchronously.

Every request in this reference must include the x-huat-platform: customer header in addition to standard GraphQL headers.

Two different things named “avatarToken” — read this first

Section titled “Two different things named “avatarToken” — read this first”

The word avatarToken refers to two related but distinct tokens in this API. Confusing them is the single most common integration mistake:

  1. The top-level avatarToken query — a JWT-authenticated query that mints a short-lived HMAC token scoped to the caller’s tenant, for talking directly to the avatar/TTS/STT service (a separate service from this API, with its own auth scheme). It is not tied to any one session — it authorizes tenant-level avatar/voice operations.
  2. The avatarToken field returned by sdkStart — a session-scoped token, returned once when an SDK session begins. It authenticates the rest of the SDK flow (sdkSendMessage, sdkEndSession, sdkVerifyWebbyxOneIdentity, requestStudyKitExport, interruptSession, sessionEvents) in place of a JWT, via AvatarTokenGuard. It is only valid for that one session.

Do not attempt to use the tenant-scoped token from query 1 in place of the session-scoped token from sdkStart (query 2), or vice versa — they authorize different things and are checked by different guards.

As of 2026-09-08, the sdkStart-issued avatarToken is bound to the sessionId it was returned with — every operation listed above rejects a call whose sessionId argument doesn’t match the session the presented token was minted for, with Forbidden. Always use the sessionId/avatarToken pair together, exactly as returned from a single sdkStart call; a token from one session was never meant to be usable against another, and previously it silently was.


Returned by session, sessions, startSession, sendMessage, and endSession:

FieldTypeNotes
idInt!
agentIdFloat!
agentAgentDtoNested Agent — see Agents.
statusSessionStatus!PENDING | ACTIVE | ENDED | EXPIRED.
clientExternalIdString!Caller-supplied external identifier, echoed back.
metadataJSONCaller-supplied arbitrary metadata, echoed back.
messages[MessageDto!]!Full chat history, oldest first. There is no standalone chat-history query — always fetch history via session(id) { messages { ... } }.
interruptCountInt!Number of times interruptSession has fired for this session.
totalTokensInContextInt!Running LLM context-window token count.
startedAtDateTime
endedAtDateTime
lastEventAtDateTime
createdAtDateTime!
updatedAtDateTime!

MessageDto fields (returned inside messages):

FieldTypeNotes
idInt!
sessionIdFloat!
roleMessageRole!USER | MODEL.
textString!
turnIndexFloat!Ordering index within the session.
tokensInFloat!Estimated input tokens billed to this message — 0 for USER messages (only a MODEL reply has an LLM call behind it). Character-based estimate, not an exact tokenizer count.
tokensOutFloat!Estimated output tokens for this message. Same estimate caveat as tokensIn.
costUsdFloat!Estimated USD cost of the LLM call that produced this message — 0 for USER messages, and for a MODEL message from a workflow’s static/templated response node with no LLM call behind it. Not billing-exact.
structuredContentJSONOptional payload alongside text — closed vocabulary { kind: "card" | "table" | "list", ... }, set only when a workflow’s response node has a structuredContentTemplate configured. null for every other message, and null if that template failed to render/parse/validate for this specific turn — text is always populated regardless, so treat null here as “nothing structured to show,” never as an error. Dashboard-only consumer today; no button/interactive variant yet.
createdAtDateTime!
updatedAtDateTime!
enum SessionStatus {
ACTIVE
ENDED
EXPIRED
PENDING
}
enum MessageRole {
MODEL
USER
}

These four operations require a JWT belonging to a user in the tenant that owns the target Agent/Session. Use this flow for a first-party dashboard, an admin console, or any server-side client that already has full user auth.

Starts a new conversation Session against an Agent. Required before sendMessage or subscribing to session events.

Auth: JWT

startSession(input: StartSessionInput!): SessionDto!

StartSessionInput fields

FieldTypeRequiredNotes
agentIdInt!yesMust be an Agent owned by the caller’s tenant.
clientExternalIdString!yesAn identifier you control (e.g. a visitor or ticket id) — echoed back on the Session, useful for correlating sessions with your own records.
metadataJSONnoArbitrary caller-supplied data, echoed back on the Session.

Request

mutation StartSession($input: StartSessionInput!) {
startSession(input: $input) {
id
status
agentId
clientExternalId
}
}
{
"input": {
"agentId": 42,
"clientExternalId": "visitor-8841",
"metadata": { "source": "dashboard-demo" }
}
}
POST /graphql
Content-Type: application/json
Authorization: Bearer <jwt>
x-huat-platform: customer

Response

{
"data": {
"startSession": {
"id": 501,
"status": "ACTIVE",
"agentId": 42,
"clientExternalId": "visitor-8841"
}
}
}

Sends a user text turn into the Session. Returns immediately — this is an ack, not the AI’s reply. The reply is generated asynchronously: subscribe to sessionEvents (watch for AiResponseEvent) to see it stream live, or re-query session(id) { messages } afterward, since the reply is persisted as a message once generation completes. See Events & Subscriptions.

Auth: JWT

sendMessage(input: SendMessageInput!): SessionDto!

SendMessageInput fields

FieldTypeRequiredNotes
sessionIdInt!yes
textString!yes
clientTurnIdStringnoAn identifier you supply to correlate a specific reply with the message that produced it — echoed back verbatim on the AiResponseEvent for this turn. Optional.
testDraftBooleannoWhen true, this turn runs the agent’s workflow’s live-editable draft instead of its published snapshot — no publish required, and the workflow doesn’t even need to have been published yet. Meant for testing in-progress edits from a dedicated test session; omit (or leave false) for any real session — this is not how a real user’s turn should ever be sent.

Request

mutation SendMessage($input: SendMessageInput!) {
sendMessage(input: $input) {
id
status
}
}
{
"input": {
"sessionId": 501,
"text": "What's your return policy?"
}
}

Response

{
"data": {
"sendMessage": {
"id": 501,
"status": "ACTIVE"
}
}
}

Ends a Session (sets status ENDED). Triggers async Evaluation generation — poll the evaluation(sessionId) query afterward (see Evaluation & Export), it will be null until that job completes.

Auth: JWT

endSession(sessionId: ID!): SessionDto!

Request

mutation EndSession($sessionId: ID!) {
endSession(sessionId: $sessionId) {
id
status
endedAt
}
}
{ "sessionId": "501" }

Response

{
"data": {
"endSession": {
"id": 501,
"status": "ENDED",
"endedAt": "2026-08-09T10:15:00.000Z"
}
}
}

Interrupts (barges in on) an in-flight AI response for this Session. Unlike most mutations on this page, takes a bare sessionId and returns a Boolean, not the Session object itself.

Auth: JWT or avatarToken — this is the one exception to the JWT/avatarToken split described above. It’s callable from a first-party JWT-authenticated dashboard client (the flow every other mutation on this page uses), and also directly by the <vai-avatar> embed SDK’s own barge-in handling, which only has the avatarToken from sdkStart — there’s no separate sdkInterruptSession mutation the way there’s a separate sdkSendMessage/sdkEndSession pair for the send/end cases. Send either Authorization: Bearer <accessToken> (JWT) or Authorization: Bearer <avatarToken> — both work. When authenticating with an avatarToken, sessionId must match the session that token was issued for (see the session-binding note above) — a JWT holder is unaffected, since a JWT is legitimately tenant-wide.

interruptSession(sessionId: ID!): Boolean!

Request

mutation InterruptSession($sessionId: ID!) {
interruptSession(sessionId: $sessionId)
}
{ "sessionId": "501" }

Response

{ "data": { "interruptSession": true } }

These queries work for either flow’s sessions (they’re the same underlying SessionDto regardless of how the session was started), but require a JWT — the SDK/embed flow does not have a query equivalent, since it authenticates by API key/avatarToken instead.

Fetches a single Session by id, scoped to the caller’s tenant. This is also the only way to retrieve chat history — select the nested messages field; there is no standalone getChatHistory/messages(sessionId) query.

Auth: JWT

session(id: ID!): SessionDto

Request

query GetSession($id: ID!) {
session(id: $id) {
id
status
messages {
role
text
turnIndex
}
}
}
{ "id": "501" }

Response

{
"data": {
"session": {
"id": 501,
"status": "ENDED",
"messages": [
{
"role": "USER",
"text": "What's your return policy?",
"turnIndex": 0
},
{
"role": "MODEL",
"text": "You can return any item within 30 days of purchase.",
"turnIndex": 1
}
]
}
}
}

Lists Sessions for the caller’s tenant, optionally filtered by agentId. Returns a plain array, not a Relay connection.

Auth: JWT

sessions(agentId: Int): [SessionDto!]!

Request

query ListSessions($agentId: Int) {
sessions(agentId: $agentId) {
id
status
clientExternalId
}
}
{ "agentId": 42 }

Response

{
"data": {
"sessions": [
{ "id": 501, "status": "ENDED", "clientExternalId": "visitor-8841" }
]
}
}

Total number of Sessions belonging to the caller’s tenant.

Auth: JWT

sessionCount: Int!

Request

query {
sessionCount
}

Response

{ "data": { "sessionCount": 1 } }

Mints a short-lived HMAC token scoped to the caller’s tenant, for authenticating directly against the avatar/TTS/STT service — a separate service from this API, with its own auth scheme, not a JWT. This is not the same token as the avatarToken field returned by sdkStart — see the callout at the top of this page.

Auth: JWT

avatarToken: String!

Request

query {
avatarToken
}

Response

{ "data": { "avatarToken": "eyJhbGciOi...redacted" } }

These two queries are for API-key-tier integrators (e.g. a partner who authenticates with X-Api-Key, not a JWT) that want to re-pull their own end-customer’s data — for example, if they lose local state and need to reconstruct session history or usage without ever having persisted a session id on their own side. Both are scoped by the caller’s own clientExternalId (the same identifier you pass as clientId to sdkStart) within the tenant your API key belongs to — you cannot query another tenant’s or another customer’s data with these.

Relay-paginated (forward pagination only) list of Sessions matching externalCustomerId.

Auth: API key (X-Api-Key header)

sessionsByExternalCustomerId(externalCustomerId: String!, first: Int, after: String): SessionConnection!

Arguments

ArgumentTypeRequiredNotes
externalCustomerIdString!yesMatches clientExternalId/clientId from startSession/sdkStart.
firstIntnoPage size. Defaults to 20.
afterStringnoOpaque cursor from a previous page’s pageInfo.endCursor.

SessionConnection fields

FieldTypeNotes
edges[SessionEdge!]!Each edge is { cursor: String!, node: SessionDto! }.
pageInfoSessionPageInfo!{ hasNextPage: Boolean!, endCursor: String }.
totalCountInt!Total matching Sessions, independent of pagination.

Request

query SessionsByCustomer(
$externalCustomerId: String!
$first: Int
$after: String
) {
sessionsByExternalCustomerId(
externalCustomerId: $externalCustomerId
first: $first
after: $after
) {
totalCount
pageInfo {
hasNextPage
endCursor
}
edges {
cursor
node {
id
status
createdAt
}
}
}
}
{ "externalCustomerId": "omni-cust-1", "first": 20 }
POST /graphql
Content-Type: application/json
X-Api-Key: <your-api-key>
x-huat-platform: customer

Response

{
"data": {
"sessionsByExternalCustomerId": {
"totalCount": 2,
"pageInfo": { "hasNextPage": false, "endCursor": "Mg==" },
"edges": [
{
"cursor": "MQ==",
"node": {
"id": 501,
"status": "ENDED",
"createdAt": "2026-08-01T10:00:00.000Z"
}
},
{
"cursor": "Mg==",
"node": {
"id": 502,
"status": "ACTIVE",
"createdAt": "2026-08-05T10:00:00.000Z"
}
}
]
}
}
}

To fetch the next page, pass after: "Mg==" (the previous page’s endCursor).

Rolled-up token/cost usage across every Session belonging to externalCustomerId — a single aggregate, not a page of rows.

Auth: API key (X-Api-Key header)

customerUsageSummary(externalCustomerId: String!): CustomerUsageSummaryDto!

CustomerUsageSummaryDto fields

FieldTypeNotes
externalCustomerIdString!Echoes the argument back.
sessionCountInt!Number of Sessions matching this customer.
tokensInInt!Sum of every Message’s tokensIn across all matching Sessions.
tokensOutInt!Sum of every Message’s tokensOut across all matching Sessions.
costUsdFloat!Sum of every Message’s costUsd across all matching Sessions. Estimate, not billing-exact — see MessageDto.costUsd above.

Request

query CustomerUsage($externalCustomerId: String!) {
customerUsageSummary(externalCustomerId: $externalCustomerId) {
externalCustomerId
sessionCount
tokensIn
tokensOut
costUsd
}
}
{ "externalCustomerId": "omni-cust-1" }

Response

{
"data": {
"customerUsageSummary": {
"externalCustomerId": "omni-cust-1",
"sessionCount": 2,
"tokensIn": 1200,
"tokensOut": 800,
"costUsd": 0.00042
}
}
}

These operations are the <vai-avatar> embed SDK’s equivalents of startSession/sendMessage/endSession, plus refreshAvatarToken for renewing a session token that’s about to expire. sdkStart authenticates via an X-Api-Key header (see Authentication) — not a JWT — and must be called server-side, since the API key must never reach the browser. It returns a session-scoped avatarToken; sdkSendMessage and sdkEndSession then authenticate using that token instead of a JWT or API key.

This is the flow most runtime integrators use: your backend calls sdkStart with your API key, then hands the returned sessionId/avatarToken/config attributes to the browser-side <vai-avatar> component (or your own client), which uses them directly.

Entry point for the <vai-avatar> embed SDK. Starts a Session and returns everything the SDK web component needs as attributes: sessionId, avatarToken (a separate HMAC token for the avatar service, not this API), and voice/avatar config.

Auth: API key (X-Api-Key header) — see Authentication. Rate-limited (as of 2026-09-08) — repeated calls from the same source IP are throttled.

sdkStart(input: SdkStartInput!): SdkStartResult!

SdkStartInput fields (all optional — supply either agentId or the inline persona fields)

FieldTypeNotes
agentIdIntUse an existing Agent’s configuration.
agentNameStringInline persona name, if not using agentId.
personaPromptStringInline persona prompt, if not using agentId.
positionStringInline persona position label.
useCaseAgentUseCaseInline persona use case.
voiceTierVoiceTierInline persona voice tier.
clientIdStringCaller-supplied external identifier, analogous to clientExternalId in the dashboard flow.

SdkStartResult fields

FieldTypeNotes
sessionIdInt!
agentIdInt!
avatarTokenString!Session-scoped — see the callout above. Pass this to sdkSendMessage/sdkEndSession and to the avatar service.
avatarServiceUrlString!Base URL of the avatar/TTS/STT service to connect the SDK component to.
graphqlEndpointString!GraphQL endpoint to use for the rest of the SDK flow.
avatarGlbString!
avatarGenderString!
ttsVoiceString!
ttsLangString!
lipsyncLangString!
speakingRateFloat!
vadThresholdFloat!

Request

mutation SdkStart($input: SdkStartInput!) {
sdkStart(input: $input) {
sessionId
avatarToken
avatarServiceUrl
graphqlEndpoint
avatarGlb
ttsVoice
}
}
{
"input": {
"agentId": 42,
"clientId": "visitor-8841"
}
}
POST /graphql
Content-Type: application/json
X-Api-Key: <your-api-key>
x-huat-platform: customer

Response

{
"data": {
"sdkStart": {
"sessionId": 502,
"avatarToken": "eyJhbGciOi...redacted",
"avatarServiceUrl": "https://avatar.wetel.dev",
"graphqlEndpoint": "https://api.wetel.dev/graphql",
"avatarGlb": "avatar-01.glb",
"ttsVoice": "en-US-Standard-C"
}
}
}

Exchanges an avatarToken that is about to expire — or expired within the last 10 minutes — for a fresh, full-TTL one on the same session, so a conversation can outlive the ~1 hour avatarToken lifetime without starting over. Added 2026-09-14.

Auth: the presented token itself, passed as input.avatarTokennot as an Authorization: Bearer header, and no API key or JWT. This is the one avatarToken-authenticated operation that must accept an already-expired token, which AvatarTokenGuard (the header-based path every other SDK mutation uses) rejects outright. The signature is still verified in full; only the expiry bound is relaxed, and only within the grace window. Rate-limited to 10 requests per 60 seconds per client IP.

refreshAvatarToken(input: RefreshAvatarTokenInput!): RefreshAvatarTokenResult!

RefreshAvatarTokenInput fields

FieldTypeRequiredNotes
avatarTokenString!yesThe current token — the one whose expiry is imminent or has just passed. Max 4,096 characters.

RefreshAvatarTokenResult fields

FieldTypeNotes
sessionIdInt!Read back from the old token’s own signed session binding, never from client input — this mutation cannot move a token onto a different session.
avatarTokenString!The replacement token. Same session, same tenant, same avatar backend config as the one you presented.
expiresInMsInt!Lifetime of the new token, so you can schedule the next refresh without hardcoding this deployment’s TTL.

Request

mutation RefreshAvatarToken($input: RefreshAvatarTokenInput!) {
refreshAvatarToken(input: $input) {
sessionId
avatarToken
expiresInMs
}
}
{
"input": {
"avatarToken": "eyJhbGciOi...current-or-just-expired"
}
}
POST /graphql
Content-Type: application/json
x-huat-platform: customer

Response

{
"data": {
"refreshAvatarToken": {
"sessionId": 502,
"avatarToken": "eyJhbGciOi...fresh",
"expiresInMs": 3600000
}
}
}

Grace window. You don’t need to refresh pre-emptively — a token that expired within the last 10 minutes is still accepted (AVATAR_TOKEN_REFRESH_GRACE_MS, configurable per deployment), so the reactive pattern works: call, get Invalid or expired avatar token, refresh, retry. Past that window there’s nothing to refresh into and you need a fresh sdkStart.

Errors

ErrorCause
Invalid or expired avatar token (401)Bad signature, malformed token, expired beyond the grace window, or a session that no longer exists. Deliberately one indistinguishable message — a caller never learns which.
Session <id> is not active (status: <...>) (400)The token verified, but its session has already ended. Start a new session.

The <vai-avatar> SDK’s equivalent of sendMessage — takes the avatarToken from sdkStart rather than a JWT. Same async-reply semantics: this returns immediately, the AI’s reply arrives via the sessionEvents subscription.

Auth: avatarToken (session-scoped, from sdkStart, via AvatarTokenGuard) — input.sessionId must match the session the token was minted for.

sdkSendMessage(input: SdkSendMessageInput!): Boolean!

SdkSendMessageInput fields

FieldTypeRequiredNotes
sessionIdInt!yes
textString!yes
clientTurnIdStringnoAn identifier you supply to correlate a specific reply with the message that produced it — echoed back verbatim on the AiResponseEvent for this turn (see Events & Subscriptions). Purely optional passthrough; omit it if you don’t need per-turn correlation (e.g. single-turn-at-a-time usage, where the next sessionEvents reply is unambiguously the answer to the message you just sent).

Request

mutation SdkSendMessage($input: SdkSendMessageInput!) {
sdkSendMessage(input: $input)
}
{
"input": {
"sessionId": 502,
"text": "What's your return policy?",
"clientTurnId": "turn-8841-01"
}
}
POST /graphql
Content-Type: application/json
Authorization: Bearer <avatarToken-from-sdkStart>
x-huat-platform: customer

Response

{ "data": { "sdkSendMessage": true } }

The <vai-avatar> SDK’s equivalent of endSession — takes the avatarToken from sdkStart rather than a JWT. Triggers the same async Evaluation generation as the dashboard’s endSession.

Auth: avatarToken (session-scoped, from sdkStart) — input.sessionId must match the session the token was minted for.

sdkEndSession(input: SdkEndSessionInput!): SdkEndSessionResult!

SdkEndSessionInput fields

FieldTypeRequired
sessionIdInt!yes

SdkEndSessionResult fields

FieldTypeNotes
sessionIdInt!
durationSecondsInt!
evaluationSdkEvaluationResultMay be null if the Evaluation job hasn’t completed yet.

SdkEvaluationResult: { score: Int!, recommendation: String!, summary: String!, strengths: [String!]! }

Request

mutation SdkEndSession($input: SdkEndSessionInput!) {
sdkEndSession(input: $input) {
sessionId
durationSeconds
evaluation {
score
recommendation
summary
}
}
}
{ "input": { "sessionId": 502 } }

Response

{
"data": {
"sdkEndSession": {
"sessionId": 502,
"durationSeconds": 184,
"evaluation": null
}
}
}

Exchanges a single-use SSO ticket for the caller’s verified WebbyX One (IAM One) identity, and stores it on the Session so subsequent workflow turns can branch on it. WebbyX One has no separately-hosted login page for this flow — your own frontend hosts the email/password form and calls WebbyX One’s POST /sso/login directly with those credentials (client-side, safe — this call needs only the public X-Client-Id, no secret). That call returns a redirect_url containing a ticket query parameter; extract the ticket from that URL (there’s no need to actually navigate the browser there unless your own callback route depends on it) and pass it to this mutation. The actual token exchange (which needs a client secret) happens server-side on Wetel’s end — never in your browser code.

Once verified, a workflow’s router/condition node config can reference the identity via {{session.webbyxOneIdentity.companyIds}}, {{session.webbyxOneIdentity.email}}, etc.

Auth: avatarToken (session-scoped, from sdkStart, via AvatarTokenGuard) — input.sessionId must match the session the token was minted for.

sdkVerifyWebbyxOneIdentity(input: SdkVerifyWebbyxOneIdentityInput!): SdkWebbyxOneIdentityResult!

SdkVerifyWebbyxOneIdentityInput fields

FieldTypeRequiredNotes
sessionIdInt!yes
ticketString!yesShort-lived (5 minutes), single-use — request a fresh one from /sso/login if this fails.
productTickets[SdkWebbyxOneProductTicketInput!]noOptional, additive. Extra tickets that let a workflow authenticate against sibling WebbyX-group products as this same signed-in caller, instead of a shared service-account credential. See the note below.

SdkWebbyxOneProductTicketInput fields

FieldTypeRequiredNotes
productSdkSessionIdentityProduct!yesAISCRM | OMNICHAT today — see webbyxOneProductClientIds for the current, backend-served list.
ticketString!yesA SECOND, independently-minted ticket, scoped to that product’s own X-Client-Id — not the same value as the top-level ticket field.

SdkWebbyxOneIdentityResult fields

FieldTypeNotes
emailString!
companyIds[String!]!
scopes[String!]!WebbyX One-defined scopes for this identity.
linkedProducts[SdkLinkedProduct!]!One entry per submitted productTickets item, in request order. Empty if you didn’t send any — fully additive, existing callers see no change.

SdkLinkedProduct fields

FieldTypeNotes
productSdkSessionIdentityProduct!
linkedBoolean!false if the ticket couldn’t be redeemed for a downstream session at all (e.g. the product rejected it). true with an empty organizations means the identity was recognised but has no grant on that product.
organizations[String!]!Human-readable organization names this identity can act through on this product. Two or more means the agent will ask which one to use the first time an action needs it — no extra call needed on your end.

Request

mutation SdkVerifyWebbyxOneIdentity($input: SdkVerifyWebbyxOneIdentityInput!) {
sdkVerifyWebbyxOneIdentity(input: $input) {
email
companyIds
scopes
linkedProducts {
product
linked
organizations
}
}
}
{
"input": {
"sessionId": 502,
"ticket": "tk_a1b2c3...",
"productTickets": [{ "product": "OMNICHAT", "ticket": "tk_d4e5f6..." }]
}
}

Response

{
"data": {
"sdkVerifyWebbyxOneIdentity": {
"email": "[email protected]",
"companyIds": ["13"],
"scopes": ["portal"],
"linkedProducts": [
{
"product": "OMNICHAT",
"linked": true,
"organizations": ["Acme Sdn Bhd"]
}
]
}
}
}

Throws if the ticket is invalid, expired, or already used. An invalid/expired entry in productTickets does not fail the whole call — it just comes back with linked: false for that product.


The public WebbyX One X-Client-Id for every sibling product a signed-in identity can link credentials for via productTickets above. Your sign-in widget calls this first to know which extra /sso/login calls to make in the same submit as the main sign-in — served from the backend rather than hardcoded per-integration, so the product list has exactly one source of truth and can grow without a docs or client update. Returns no secrets — /sso/login takes no client_secret.

Auth: avatarToken (session-scoped, from sdkStart) OR a dashboard JWT — either is accepted, since both a dashboard tool and an embed sign-in widget legitimately need this.

webbyxOneProductClientIds: [SdkWebbyxOneProductClientId!]!

SdkWebbyxOneProductClientId fields

FieldTypeNotes
productSdkSessionIdentityProduct!AISCRM | OMNICHAT.
clientIdString!The public X-Client-Id to mint that product’s ticket under, via WebbyX One’s own /sso/login.

Request

query WebbyxOneProductClientIds {
webbyxOneProductClientIds {
product
clientId
}
}

Response

{
"data": {
"webbyxOneProductClientIds": [
{ "product": "AISCRM", "clientId": "019f..." },
{ "product": "OMNICHAT", "clientId": "019f..." }
]
}
}