multica-ai/multica · error
wecom: redeemer is not a workspace member
Error message
wecom: redeemer is not a workspace member
What it means
WeCom binding sentinel error: the redeemer is not a member of the token's workspace. Enforced by an explicit membership check during redemption (the member FK no longer exists); the HTTP boundary translates it to 403.
Source
Thrown at server/internal/integrations/wecom/binding.go:78
// user who keeps going, against rows that expire in fifteen. The price of
// being wrong is one more message, not ten minutes of a bot insisting it
// already answered.
//
// It must stay comfortably inside BindingTokenTTL so a link a throttled user
// is pointed back at still has real time left on it.
const BindingTokenMintInterval = time.Minute
var (
// ErrBindingTokenInvalid: token unknown / already consumed / expired.
// One opaque error for all three avoids a replay timing oracle.
ErrBindingTokenInvalid = errors.New("wecom: binding token invalid or expired")
// ErrBindingAlreadyAssigned: this WeCom userid is already bound to a
// different Multica user (account transfer must go through explicit
// unbind, not implemented in iter 1 — an admin can DELETE the row).
ErrBindingAlreadyAssigned = errors.New("wecom: user id is already bound to a different user")
// ErrBindingNotWorkspaceMember: the redeemer is not a member of the
// token's workspace. Translated to 403 at the HTTP boundary.
ErrBindingNotWorkspaceMember = errors.New("wecom: redeemer is not a workspace member")
)
// BindingToken is a freshly minted token. The raw value is returned exactly
// once (embedded in the binding URL); only its hash is persisted.
type BindingToken struct {
Raw string
ExpiresAt time.Time
// Reused says the throttle suppressed the mint because a live link is
// already sitting in the user's chat. Raw is empty in that case and there
// is no way to recover it — the table only ever held the hash — so the
// caller must point the user back at the earlier message rather than
// building a URL. ExpiresAt carries the live token's expiry, not a fresh
// one's.
Reused bool
}
// RedeemedBindingToken is returned after a successful redemption.View on GitHub (pinned to 2c0912b6ec)
Solutions
- Have an admin add the redeeming user to the workspace, then request a new binding link (old one may have expired).
- Confirm the browser session is the intended member account before redeeming.
- If membership is believed valid, inspect the membership rows for staleness.
Example fix
// before
_, err := svc.Redeem(ctx, rawToken)
if err != nil {
http.Error(w, err.Error(), 500)
}
// after
_, err := svc.Redeem(ctx, rawToken)
if errors.Is(err, wecom.ErrBindingNotWorkspaceMember) {
http.Error(w, "you must be a member of this workspace", http.StatusForbidden)
return
} Defensive patterns
Strategy: validation
Validate before calling
if !members.IsMember(ctx, token.WorkspaceID, user.ID) {
return respondForbidden(w, "join the workspace before linking WeCom")
}
_ = wecomSvc.Redeem(ctx, rawToken) Try / catch
if err := wecomSvc.Redeem(ctx, rawToken); err != nil {
if errors.Is(err, wecom.ErrBindingNotWorkspaceMember) {
return respondStatus(w, http.StatusForbidden)
}
return err
} Prevention
- Verify the active browser account matches a workspace member before showing the redeem page.
- Re-check membership at redeem time; the 15-minute TTL window is long enough for removals.
- Return 403 (not 400) for this case so clients can offer a workspace-switch prompt.
When it happens
Trigger: Redeeming a WeCom binding token by a user with no membership in the workspace the token was minted for — removed mid-flow, or signed in under an account that never joined.
Common situations: User removed from the workspace between the bot message and the redeem click; wrong account active in the browser session when clicking the link.
Related errors
- redeemer is not a workspace member
- slack: redeemer is not a workspace member
- wecom: binding token invalid or expired
- wecom: user id is already bound to a different user
- Invalid desktop runtime config JSON: ${err instanceof Error
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/a5bebee4be4274e9.
Report an issue: GitHub.