跳转到内容

Core API Flow

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

Wetel is a headless agent backend: you speak GraphQL mutations and subscriptions to it, and it speaks events back to you. There is no pre-built chat widget shipped as part of the product — you own 100% of the UI. This page documents the three mutations that drive the runtime session lifecycle: sdkStart, sdkSendMessage, and sdkEndSession.

If you haven’t set up authentication yet, read Authentication first — this page assumes you already have a long-lived API key and know how to obtain a session’s short-lived avatarToken. If you haven’t configured an agent yet, see Agents.

Every mutation and query against the Wetel API — no exceptions — requires the x-huat-platform: customer header, in addition to whatever auth header the specific call needs (X-Api-Key or Authorization: Bearer <token>). Omitting it returns a 403 Forbidden before your auth is even checked. The one place it’s carried differently is the WebSocket connection_init payload, described below, where it goes inside connectionParams instead of a plain HTTP header.

All examples below target the public endpoint:

https://api.wetel.dev/graphql
  1. Your backend starts a session for an end user by calling sdkStart, authenticated with your long-lived API key. This returns a sessionId and a short-lived avatarToken.
  2. Your backend hands the avatarToken and sessionId to your frontend (cookie, sessionStorage, URL param — whatever transport you use).
  3. Your frontend opens a GraphQL subscription (sessionEvents) authenticated with the avatarToken, to receive the agent’s replies and workflow events. See Events & Subscriptions for the full event catalogue and a critical gotcha about union fragments.
  4. Your frontend sends user messages via sdkSendMessage, authenticated with the avatarToken. This call returns an immediate acknowledgement — not the agent’s reply. Message text is capped at 4,000 characters — validated on the field, rejected outright rather than truncated. If you’re sending something larger than a normal chat message (a long document, a large structured payload for classification, an entire transcript), you’ll need to summarize or chunk it client-side before calling sdkSendMessage.
  5. The agent’s reply streams back asynchronously over the subscription opened in step 3, as one or more AiResponseEvent payloads.
  6. When the conversation ends, your frontend (or backend) calls sdkEndSession, which closes out the session and emits a terminal SessionEndedEvent.
Your Backend Your Frontend Wetel
| | |
|--- sdkStart(agentId) ----------------------------------->|
|<---------------- { sessionId, avatarToken } -------------|
|--- hand off sessionId + avatarToken --->| |
| |--- connection_init ------->|
| |<----- connection_ack -------|
| |--- subscribe(sessionId) -->|
| |--- sdkSendMessage(text) -->|
| |<----- true (ack only) ------|
| |<== AiResponseEvent (chunk) =|
| |<== AiResponseEvent (final) =|
| |--- sdkEndSession -------->|
| |<-------- { durationSeconds } |
| |<==== SessionEndedEvent =====|

Call this from your backend, using your long-lived X-Api-Key. Never call sdkStart from a browser — the API key must never reach client-side code.

mutation StartSession {
sdkStart(
input: { agentId: 3, clientId: "end-user-abc123", position: "helpdesk" }
) {
sessionId
avatarToken
graphqlEndpoint
}
}

Headers:

X-Api-Key: sk_your_api_key_here
x-huat-platform: customer

Input fields:

  • agentId (optional) — which agent to use. If omitted, a default agent is selected.
  • clientId (optional) — your own end-user identifier, useful for analytics and audit trails.
  • position, useCase (optional) — metadata for logging.

Response fields:

  • sessionId — an integer. You need this for every subsequent message and the event subscription.
  • avatarToken — a short-lived (~24 hour) bearer token. Hand this to your frontend; it’s the only credential your client-side code should ever hold.
  • graphqlEndpoint — the GraphQL HTTP endpoint to use for this session. Prefer reading this from the response over hardcoding a URL, so your integration doesn’t silently break if the endpoint ever changes.

Step 2: Send a message (frontend) — read this carefully

Section titled “Step 2: Send a message (frontend) — read this carefully”

This is the single most common integration mistake, so it gets its own heading.

mutation SendMessage {
sdkSendMessage(
input: {
sessionId: 42
text: "What books do you have on quantum computing?"
}
)
}

Headers:

Authorization: Bearer <avatarToken>
x-huat-platform: customer

Response: true — immediately, as soon as the message is accepted. This is an acknowledgement, not the reply.

The agent’s actual response is generated asynchronously — an LLM call can take anywhere from a couple of seconds to ten or more — and streams back over the sessionEvents subscription you opened in Step 3, as one or more AiResponseEvent payloads. If you write code that waits on the sdkSendMessage mutation’s response and expects the agent’s text to be inside it, your integration will appear to “not respond” — the reply already arrived over the subscription channel, not the mutation.

The correct pattern:

  1. Open (or already have open) the sessionEvents subscription for this sessionId.
  2. Call sdkSendMessage and discard/ignore its boolean response beyond confirming it’s true.
  3. Render the reply as AiResponseEvent chunks arrive on the subscription, concatenating until you see isFinal: true.

See Events & Subscriptions for the full subscription setup, the complete event list, and a separate gotcha about a race condition between subscribing and sending your first message.

Covered in full detail, including the exact subscription query shape and every event type, in Events & Subscriptions. At minimum, your subscription must be open before (or immediately after) you send your first message, since that’s how the reply reaches your client at all.

mutation EndSession {
sdkEndSession(input: { sessionId: 42 }) {
sessionId
durationSeconds
evaluation {
rating
feedback
}
}
}

Headers:

Authorization: Bearer <avatarToken>
x-huat-platform: customer

Call this when the user closes the chat, or after your own idle timeout. The response includes session metadata (duration, and an evaluation summary if one was generated — evaluation generation is asynchronous and only runs for conversations with two or more turns, so evaluation may be null even on a valid call). The sessionEvents subscription closes shortly after, and a terminal SessionEndedEvent is emitted first so your client can react before the connection drops.

CORS: a valid API key does not get you connectivity

Section titled “CORS: a valid API key does not get you connectivity”

Authentication and CORS are two separate gates. Even with a fully valid X-Api-Key or avatarToken, a browser will refuse to let your frontend talk to the Wetel API at all if your origin isn’t allow-listed server-side — this applies to both the HTTP GraphQL endpoint and the WebSocket subscription upgrade.

Common hosting platforms (Vercel preview URLs, Cloud Run URLs) are typically covered automatically. If you’re deploying from a different origin — your own custom domain, a different hosting provider — that origin needs to be explicitly allow-listed on the Wetel side before your client can connect. If you hit an inexplicable CORS failure on an otherwise-correct integration, this is almost always the cause: reach out to coordinate getting your production/staging origin added to the allowlist before you go live.

An alternative: your backend holds the socket instead

Section titled “An alternative: your backend holds the socket instead”

Everything above describes Wetel’s default recommended architecture — the browser holds the avatarToken and talks to Wetel directly for steps 3 and 4. This is the right choice for most integrations. If you specifically want backend-native message persistence, moderation before delivery, or a single reconnection strategy you control, see Backend-Proxied Relay Architecture for the alternative: your backend holds the sessionEvents subscription itself and relays replies to your frontend over a channel of your choosing — the browser never talks to Wetel at all.