Troubleshooting & FAQ
此内容尚不支持你的语言。
This page collects the questions and mistakes that come up most often when integrating with Wetel — both things to avoid up front, and how to debug a failing conversation when something isn’t working.
If you haven’t yet, start with Getting Started for the basic integration flow, or Workflows Overview for how the workflow graph itself is structured.
Common Mistakes
Section titled “Common Mistakes”Don’t build your own MCP server just to work around a missing feature
Section titled “Don’t build your own MCP server just to work around a missing feature”If you already have a REST API and no MCP server, register it as a Custom Action instead of wrapping it in an MCP server just to satisfy Wetel. MCP adds a protocol handshake, tool discovery, and schema validation that a plain REST endpoint doesn’t need. Custom Actions are simpler to operate, need zero MCP knowledge, and get the same SSRF validation and credential scoping as MCP connectors.
Only reach for an MCP Connector if you already have a live MCP server running — a third-party service, or your own multi-tenant MCP hub. See MCP Connectors for the registration flow.
Before building anything custom, check whether the capability you need already exists in the platform — it’s easy to over-engineer a workaround for something that’s actually a documented feature.
Don’t wait for a canvas / drag-and-drop workflow-builder protocol — there isn’t one
Section titled “Don’t wait for a canvas / drag-and-drop workflow-builder protocol — there isn’t one”Wetel doesn’t ship a visual canvas or generative-UI protocol that lets an agent dynamically inject buttons, grids, or forms into your page. Workflows are graphs of nodes and edges that you manage directly through the GraphQL API — createWorkflow → updateWorkflow → publishWorkflow (see Workflows Overview). There is no drag-and-drop builder on the integrator side.
If you need live, agent-driven visuals in your product, the pattern is: listen for NodeExecutingEvent on the events subscription, fetch the relevant data from your own backend, and render it with your own UI framework. The agent’s reasoning and tool calls stay on Wetel’s side; your presentation layer is entirely your own responsibility.
Don’t confuse SessionWillEndEvent with SessionEndedEvent
Section titled “Don’t confuse SessionWillEndEvent with SessionEndedEvent”These are not interchangeable, and treating them the same way is one of the most common integration bugs:
SessionWillEndEventis non-terminal. It fires when the agent is about to ask for confirmation before a final action (for example, “Shall I book this room?”). The session is still active — the user can reply, and the workflow continues. Keep your subscription open and render a confirm card, not a “goodbye” message.SessionEndedEventis terminal. The session is genuinely over and the subscription closes after it. This is the correct place to show a summary or closing message.
Don’t put your API key in client-side code
Section titled “Don’t put your API key in client-side code”Your X-Api-Key is a long-lived, tenant-scoped credential — if it leaks into browser storage or network logs, anyone with it can act as your entire tenant: call any agent, read any workflow, manipulate any session. Keep it in your backend’s environment only.
The correct flow: your backend uses the X-Api-Key to call sdkStart once per end-user session, and that mutation returns a short-lived avatarToken. Pass the avatarToken down to your frontend and use it — never the X-Api-Key — for every client-side call and subscription.
Don’t forget the union-fragment selection on the events subscription
Section titled “Don’t forget the union-fragment selection on the events subscription”GraphQL silently drops any event whose type isn’t explicitly selected with an inline fragment in your subscription query. There’s no error — the payload just vanishes, and you’ll see a plain typing indicator where you expected rich process chips.
Always select every event type you actually want to handle:
subscription SessionEvents($sessionId: Int!) { sessionEvents(sessionId: $sessionId) { __typename ... on AiResponseEvent { text mood isFinal } ... on NodeExecutingEvent { workflowRunId nodeId nodeType } ... on NodeFailedEvent { workflowRunId nodeId nodeType error } ... on SessionWillEndEvent { type } ... on SessionEndedEvent { durationSeconds } }}If you don’t care about a given event type, it’s fine to omit its fragment — just do it deliberately, not by accident.
Don’t assume evaluation is populated immediately
Section titled “Don’t assume evaluation is populated immediately”Session evaluation is generated asynchronously (queued in the background, with retries) after the session ends, and only for conversations with at least two turns. The evaluation field on sdkEndSession’s immediate response is frequently null because the background job hasn’t finished yet — this is expected behavior, not a bug.
Don’t build UI that depends on this field being populated synchronously right after sdkEndSession returns. There is currently no way to poll for the result afterward using SDK/API-key credentials.
Don’t treat Invalid or expired avatar token as an unrecoverable session
Section titled “Don’t treat Invalid or expired avatar token as an unrecoverable session”An avatarToken is valid for about an hour, so any session that runs longer than that will eventually start failing every sdkSendMessage/sdkEndSession call with Invalid or expired avatar token. The fix is not a new sdkStart (which would abandon the conversation and start a fresh session): call refreshAvatarToken with the token you have, and retry with the one it hands back. A token that expired within the last 10 minutes is still accepted, so reacting to the error is a perfectly good trigger — you don’t need to refresh pre-emptively.
The part that bites afterwards: a refreshed token cannot be applied to an already-open sessionEvents WebSocket, because graphql-ws reads connectionParams only once, when the connection opens. Close the subscription and reopen it with the new token. If you don’t, the symptom is not an error — your mutations start succeeding again while the stale socket quietly stops delivering replies.
Never forget the x-huat-platform: customer header
Section titled “Never forget the x-huat-platform: customer header”Every HTTP request to the API — GraphQL or otherwise — is rejected before authentication is even checked if it’s missing this header. The failure looks like an auth problem (ForbiddenException: Platform is not specified!) but isn’t. Add -H "x-huat-platform: customer" (or the equivalent header in your HTTP client) to every call, alongside whichever auth header that call needs. See Getting Started for the full header requirements.
Why won’t my workflow publish?
Section titled “Why won’t my workflow publish?”publishWorkflow rejects the graph with a specific error message rather than a generic failure — the message tells you exactly what to fix:
- “Workflow must have a START node before publishing” / “…exactly one START node” — every workflow needs precisely one
startnode. - “Edge … has unknown source/target node” — an edge references a node ID that isn’t in your
nodesarray. Common cause: a node was deleted but an edge pointing to it wasn’t. - “Duplicate node id” — two nodes share the same
id. Only the first one in the array is ever reachable; the second is silently dead. Give it a unique ID. - “Node … is unreachable from START” — the node has no incoming edge path from
start, even indirectly. This is usually leftover scaffolding from editing the graph — either connect it or remove it. - A node-specific config error (e.g.
"LLM node ... is missing required field: promptTemplate") — every node type has required config fields; see that node’s page under Workflow Nodes for the full list.
A workflow with an intentional loop (a router node routing back to an earlier step for a retry pattern) publishes fine — cycles aren’t rejected, only disconnected or duplicate-ID nodes are. If you do build a loop, give the router a circuitBreaker config (see the Router node reference) so a misbehaving loop can’t run indefinitely within a single turn.
My ACTION/WEBHOOK URL worked before and now fails
Section titled “My ACTION/WEBHOOK URL worked before and now fails”As of 2026-09-08, outbound calls from action/webhook nodes (and tool/MCP connector calls, and externalSyncUrl) no longer follow HTTP redirects, and a target hostname that doesn’t resolve is rejected instead of silently allowed through. If a call that used to work now fails:
- Check whether your target 301/302/307/308-redirects. A common cause: an
http://URL your target’s server upgrades tohttps://, or a trailing-slash normalization. The node’s own error names the redirect it hit — fix by pointingurldirectly at the final destination. - Check whether your target’s hostname actually resolves from outside your own network — an internal-DNS-only name, or a partner endpoint mid-provisioning/DNS-cutover, now fails at save time (
updateAgent, workflow publish) with an error naming the unresolved hostname, not just at call time. - Check
nodeTrace’srenderedRequest/errorfields (see Observability & Debugging Your Agent) for the exact failure — the error message names the redirect or the unresolved hostname explicitly, so this shouldn’t require guessing.
This is a deliberate security hardening, not a bug — see the 2026-09-08 changelog entry for the full rationale.
Debugging a Failed Workflow Run
Section titled “Debugging a Failed Workflow Run”Use Apollo Sandbox before writing any client code
Section titled “Use Apollo Sandbox before writing any client code”Every mutation and query in the docs can be run by hand first. Open the GraphQL endpoint directly in a browser (https://api.wetel.dev/graphql) — Apollo Server serves an interactive Sandbox UI at the same URL the API lives at.
To authenticate in Sandbox:
- Add
x-huat-platform: customerin the Headers panel — required on every request. - For dashboard-level queries (
workflowRun,session,agents, etc.), runloginfirst to get anaccessToken, then addAuthorization: Bearer <accessToken>. - For SDK-tier calls (
sdkSendMessage,sdkEndSession), useAuthorization: Bearer <avatarToken>(the tokensdkStartreturned) instead.
Mutations that return a plain Boolean (like sdkSendMessage) fail with a confusing "syntax error: invalid number" if sent as an anonymous operation — always name the operation:
mutation SendIt { sdkSendMessage(input: { sessionId: 42, text: "hello" })}Sandbox doesn’t have a point-and-click panel for watching subscription events stream in — use a small script or your own client under development for that; Sandbox is for one-shot mutation/query testing and schema exploration.
Inspecting workflowRun.nodeTrace
Section titled “Inspecting workflowRun.nodeTrace”For the broader picture of what you can inspect beyond one failed run — real-time events, session-level counters, async evaluation scoring — see Observability & Debugging Your Agent.
workflowRun(id: Int!) is the real debugging surface for “why did my workflow do that.” It requires dashboard (JWT) access — it isn’t reachable with an SDK API key or avatarToken. There’s no list query for past runs, so the only way to get a workflowRunId is to capture it live off the events subscription (NodeExecutingEvent.workflowRunId / NodeFailedEvent.workflowRunId) while the conversation is happening — log it to your own store if you’ll need it later.
query DebugRun { workflowRun(id: 42) { id status context nodeTrace errorMessage totalTokensUsed }}nodeTrace is an array with one entry per node the run actually visited, in order — the step-by-step trace of what the workflow did:
{ nodeId: string nodeType: string startedAt: string finishedAt: string outputVar?: string outputValue?: unknown renderedRequest?: unknown tokensUsed?: number routedToDefault?: boolean retrievedChunkIds?: number[] retrievalError?: string status?: 'COMPLETED' | 'FAILED' error?: string}Optional fields are absent (not false/null) when they don’t apply — for example retrievedChunkIds only appears on LLM nodes with a knowledge base attached. Check for presence rather than assuming a default.
renderedRequest shows up only on tool, action, and webhook nodes — the actual interpolated request body/args sent to the partner endpoint, on both success and failure. It’s usually an object, but comes through as a raw string if the tenant’s argsTemplate/bodyTemplate didn’t render to valid JSON. If a template embeds a secret directly in the request body, it appears here unredacted.
Worked example: “my newly-added MCP tool isn’t showing up”
Section titled “Worked example: “my newly-added MCP tool isn’t showing up””This is the most common support question from MCP-server integrators. listMcpTools is always a live call — it opens a fresh connection to your server every time, with no caching — so the fault is almost never on Wetel’s side. Work through it in this order:
Step 1 — bypass Wetel, hit your MCP server directly. Confirm your own server actually returns the new tool:
curl -s -X POST 'https://<your-mcp-server-url>' \ -H 'Content-Type: application/json' \ -H 'Accept: application/json, text/event-stream' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | jq '.result.tools[].name'If the tool is missing here, the bug is on your server — a deploy that hasn’t landed, or the tool not registered in your tools/list handler.
Step 2 — confirm the connector’s serverUrl actually points at that same server. Tunnels (like ngrok) rotate, and a stale connector can be pointed at an old instance while you’re testing against a new one:
query CheckConnector { mcpConnector(id: 5) { id name serverUrl }}Compare it byte-for-byte against the URL you used in Step 1 — a mismatch here is the single most common cause of “the tool isn’t there” reports.
Step 3 — call listMcpTools and confirm it matches Step 1’s output:
query VerifyLive { listMcpTools(connectorId: 5) { name }}If Step 3 still disagrees with Step 1 after serverUrl checks out, that’s worth escalating — the most likely cause is a credential or auth mismatch reaching your server (check for a 401/403 being silently swallowed), not a caching issue, since there’s no cache in this path at all.
In practice, Step 1 or Step 2 resolves the vast majority of these reports.
See also: Agents, Workflows Overview, MCP Connectors, and the Admin API Overview if you’re building agent-authoring tooling rather than integrating a finished agent.