multica-ai/multica · warning · ErrInvalidBotToken

slack: bot token must start with xoxb-

Error message

slack: bot token must start with xoxb-

What it means

Slack BYO (bring-your-own-app) validation error: the pasted bot token does not start with the required xoxb- prefix. RegisterBYO returns it before any network call; the handler maps it to 400 so the admin's dialog can show a precise hint.

Source

Thrown at server/internal/integrations/slack/byo_install.go:23

	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"strings"

	"github.com/jackc/pgx/v5/pgtype"
	"github.com/slack-go/slack"

	db "github.com/multica-ai/multica/server/pkg/db/generated"
)

// ErrInvalidBotToken / ErrInvalidAppToken are returned by RegisterBYO when a
// pasted token is malformed (wrong prefix, or an app token whose app id cannot
// be parsed). The handler maps them to 400 so the dialog can show a precise hint
// instead of a generic failure.
var (
	ErrInvalidBotToken = errors.New("slack: bot token must start with xoxb-")
	ErrInvalidAppToken = errors.New("slack: app-level token must start with xapp- and embed an app id")
	// ErrTokenAppMismatch is returned when the pasted bot token and app-level
	// token belong to DIFFERENT Slack apps. Persisting that pair would "connect"
	// but be broken: inbound arrives on the app token's socket (routed by its
	// app id) while mention detection + outbound use the bot token's identity.
	ErrTokenAppMismatch = errors.New("slack: the bot token and app-level token are from different Slack apps")
)

// RegisterBYOParams are the inputs for a bring-your-own-app install: the agent
// this bot represents, who is installing, and the two tokens the user pasted
// from their own Slack app.
type RegisterBYOParams struct {
	WorkspaceID pgtype.UUID
	AgentID     pgtype.UUID
	InitiatorID pgtype.UUID
	BotToken    string // xoxb-… — outbound Web API (chat.postMessage)
	AppToken    string // xapp-… — this app's OWN Socket Mode connection (inbound)
}

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Copy the Bot User OAuth Token (starts with xoxb-) from the Slack app's OAuth & Permissions page and re-paste.
  2. Trim whitespace/newlines from the pasted value before submitting.
  3. If you only have an xapp- value in hand, you copied the App-Level Token — keep looking for the bot token; do not paste it into this field.

Example fix

// before
err := svc.RegisterBYO(ctx, params) // params.BotToken = "xapp-..."
// -> "slack: bot token must start with xoxb-"

// after: validate at the edge, give a field-level hint
params.BotToken = strings.TrimSpace(params.BotToken)
if !strings.HasPrefix(params.BotToken, "xoxb-") {
	return respondFieldError(w, "botToken", "must be a bot token starting with xoxb-")
}
err := svc.RegisterBYO(ctx, params)
Defensive patterns

Strategy: validation

Validate before calling

bot := strings.TrimSpace(params.BotToken)
if !strings.HasPrefix(bot, "xoxb-") {
	return respondFieldError(w, "botToken", "must start with xoxb-")
}
_ = svc.RegisterBYO(ctx, params)

Type guard

func isSlackBotToken(s string) bool {
	s = strings.TrimSpace(s)
	return strings.HasPrefix(s, "xoxb-") && len(s) > len("xoxb-")
}

Try / catch

if err := svc.RegisterBYO(ctx, params); err != nil {
	if errors.Is(err, slack.ErrInvalidBotToken) {
		return respondFieldError(w, "botToken", "copy the Bot User OAuth Token (xoxb-...)")
	}
	return err
}

Prevention

When it happens

Trigger: Calling RegisterBYO with a BotToken that is not a bot token — typically a user token (xoxp-), an app-level token (xapp-), an old workspace token (xoxa/xoxs), or a copied-with-whitespace/garbled string.

Common situations: Admin copies the wrong token field from the Slack app config page (app-level token instead of bot token); token pasted with leading/trailing spaces or a newline; token truncated by a password manager.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/ad3fdc916d54848e. Report an issue: GitHub.