Group Brain
Status: live in production since 2026-10-24, and on staging. Shadow-only: every decision is recorded and answered SILENT until Wetel staff switch your profile live.
The Group Brain API is for platforms that run their own chat network (their own WhatsApp, Telegram or web chat integration) and want a Wetel assistant in group chats. In a group, most messages are not for the assistant. You send each new batch of group messages to groupDecide; Wetel decides whether the assistant should speak, stay silent or wait, and keeps the per-group state (cooldown, daily cap, mute, talkativeness, who it is talking to). You keep the chat history and post the replies. If you only need an assistant that answers when mentioned, the Answer When Mentioned recipe works in production today with the standard SDK operations and no group-specific API.
Before you start
Section titled “Before you start”- A profile. A channel connector of type
APIin your workspace, linked to the agent that should answer. Its id is theprofileIdin every call. Its group settings (talkativeness, aliases, cooldown, daily cap, shadow) are set withupdateChannelConnectorGroupMode, owner-only. - A named API key with the
groups:decidescope, granted by the workspace owner or Wetel staff. Send it asX-Api-Key, withx-huat-platform: customer. A missing, unknown, revoked or expired key answersUNAUTHENTICATED; a valid key without the scope (or the legacy tenant key) answersFORBIDDEN; aprofileIdthat is not anAPIconnector in the key’s workspace answersNOT_FOUND. - Rate limits: 120 calls per minute per API key for
groupDecide, and 120 per minute per key for each of the other three operations. Over the limit, the error carriesextensions.status429: back off and retry.
The flow
Section titled “The flow”- Debounce new group messages on your side, then call
groupDecidewith the new messages (events, 1 to 20) and, for context, up to 20 earlier ones (recent, with your bot’s own messages markedfromBot: true). - On
SPEAK: sendturnTextas one user turn withsdkSendMessageon a session you keep for that group, post the reply threaded toreplyToMessageId, then callgroupReportOutcome. Ifnoticeis set, post it once in the group (before or with the first reply) and reportNOTICE_POSTED. - On
SILENT: do nothing. - On
WAIT: if no new message arrives withinretryAfterMs, callgroupDecideonce more with the same events, the samerequestKeyandwaited: true. A second wait becomesSILENT.
Send one call per group at a time; Wetel also serialises calls per group.
groupDecide
Section titled “groupDecide”mutation Decide($input: GroupDecideInput!) { groupDecide(input: $input) { decisionId action stage reasonCode turnText replyToMessageId retryAfterMs notice shadow shadowAction shadowReasonCode replayed }}GroupDecideInput: profileId, groupId (up to 189 letters, digits and @ . _ + -, no :), optional threadId (each thread is its own conversation), optional groupTitle and channel (for example whatsapp; defaults to api), requestKey (your id of the newest event), botSenderId, events, optional recent, optional waited.
Each event: kind (TEXT, or MEDIA_OTHER for a sticker, GIF, poll or attachment with no usable words), messageId, senderId (a stable id, never a display name), optional senderLabel (shown to the model only), sentAt, text (up to 4000 characters; put a voice-note transcript or photo caption here), mentionsBot, quotedFromBot, optional quotedSenderId and mentionedOthers. You detect mentions and replies; Wetel does not read your network.
Behaviour to rely on:
- Synchronous. Usually under a second when rules decide (
stage: RULES), a few seconds when a small judge model is asked (stage: JUDGE). The judge is given up after 6 seconds and the answer isSILENT. - Fail-closed. A kill switch, a profile that is off or paused, a judge failure, timeout or cost cap all answer
SILENTwith areasonCode, never an error. With the kill switch on,decisionIdisnull. - Shadow. While
shadowistrue,actionisSILENTwithreasonCode: "shadow", andshadowAction/shadowReasonCodesay what would have happened. - Idempotent. The same
requestKeyfor the same group returns the stored decision withreplayed: trueand counts nothing twice. A retry that arrives while the first call is still being judged answersWAITwithreasonCode: "in_flight": ask again afterretryAfterMswith the samerequestKeyand the samewaitedvalue. - Follow-up window. After the agent answers a person, that person’s next message within
continuityMs(default 120000 ms, 0 to 3600000) is a follow-up candidate: the rules do not decide it, the judge does, with the hint that it may continue the conversation (features.followUpCandidate). The judge answers only if the message continues that conversation or is clearly meant for the agent; unrelated small talk getsSILENT(judge_silent). A follow-up still skips the rapid-exchange, cooldown and daily-cap rules, but inmention_onlymode the window does not apply (mention_only). The reason codefollow_upis kept for compatibility. With more than one agent in a group, give each distinctaliases: every agent that just answered the same person gets the follow-up candidate and decides for itself. - No message text stored. Wetel builds
turnTextfrom your input and does not store it; the decision log holds ids, codes and numbers only.
reasonCode values: speaking mention, quote_reply_to_bot, alias_match, follow_up, judge_reply; silent muted, own_message, addressed_other_human, rapid_human_exchange, media_only, cooldown, daily_cap, mention_only, mention_flood, stale_event, judge_silent, judge_below_threshold, judge_react, judge_error, judge_budget, judge_daily_cap, shadow, disabled, profile_off; wait judge_wait, in_flight.
groupReportOutcome
Section titled “groupReportOutcome”groupReportOutcome(input: { decisionId, status, outboundMessageId }) returns true. status is SENT (you posted the reply), DROPPED_STALE (newer messages arrived, you did not post), FAILED, or NOTICE_POSTED (stops notice being returned for that group). It is accepted only for a live SPEAK decision: for a shadow or silent decision it answers BAD_REQUEST. Calling it again overwrites the status. It does not change cooldowns.
groupSetState
Section titled “groupSetState”Applies a group admin command and returns the group’s state (muted, talkativeness, noticePosted, unpromptedToday, lastUnpromptedAt). Input: profileId, groupId, optional threadId, and any of muted, talkativeness (MENTION_ONLY, QUIET, NORMAL, CHATTY), clearTalkativeness, reset, plus actorId. Fields you omit are kept. You check that actorId is an admin of the group; Wetel records it. Muted means silent even when mentioned. Never forward admin commands to the agent as messages.
groupDecisions
Section titled “groupDecisions”groupDecisions(filter: { profileId, since, groupId, threadId, first, after }) lists a profile’s recorded decisions since since, oldest first (Relay paging, first 1 to 200, default 100). Each record has ids (conversationId, requestKey, triggerMessageId, batchMessageIds), stage, action (in shadow, what it would have been), reasonCode, confidence, threshold, shadow, wouldSend, sent, languageBucket, latencyMs and outcomeStatus, never message text. Join them to your own history to label and evaluate shadow decisions before asking for go-live.