{"record":{"id":"77ce86efeb928154","repo":"multica-ai/multica","slug":"slack-the-bot-token-and-app-level-token-are-from","errorCode":null,"errorMessage":"slack: the bot token and app-level token are from different Slack apps","messagePattern":"slack: the bot token and app-level token are from different Slack apps","errorType":"http","errorClass":"ErrTokenAppMismatch","httpStatus":400,"severity":"error","filePath":"server/internal/integrations/slack/byo_install.go","lineNumber":29,"sourceCode":"\n\t\"github.com/jackc/pgx/v5/pgtype\"\n\t\"github.com/slack-go/slack\"\n\n\tdb \"github.com/multica-ai/multica/server/pkg/db/generated\"\n)\n\n// ErrInvalidBotToken / ErrInvalidAppToken are returned by RegisterBYO when a\n// pasted token is malformed (wrong prefix, or an app token whose app id cannot\n// be parsed). The handler maps them to 400 so the dialog can show a precise hint\n// instead of a generic failure.\nvar (\n\tErrInvalidBotToken = errors.New(\"slack: bot token must start with xoxb-\")\n\tErrInvalidAppToken = errors.New(\"slack: app-level token must start with xapp- and embed an app id\")\n\t// ErrTokenAppMismatch is returned when the pasted bot token and app-level\n\t// token belong to DIFFERENT Slack apps. Persisting that pair would \"connect\"\n\t// but be broken: inbound arrives on the app token's socket (routed by its\n\t// app id) while mention detection + outbound use the bot token's identity.\n\tErrTokenAppMismatch = errors.New(\"slack: the bot token and app-level token are from different Slack apps\")\n)\n\n// RegisterBYOParams are the inputs for a bring-your-own-app install: the agent\n// this bot represents, who is installing, and the two tokens the user pasted\n// from their own Slack app.\ntype RegisterBYOParams struct {\n\tWorkspaceID pgtype.UUID\n\tAgentID     pgtype.UUID\n\tInitiatorID pgtype.UUID\n\tBotToken    string // xoxb-… — outbound Web API (chat.postMessage)\n\tAppToken    string // xapp-… — this app's OWN Socket Mode connection (inbound)\n}\n\n// RegisterBYO installs a user-supplied (\"bring your own\") Slack app for an agent.\n// The user creates their own Slack app, installs it to their workspace, and\n// pastes its bot token (xoxb-) + app-level token (xapp-). There is NO OAuth code\n// exchange: we validate the bot token live via auth.test (which also yields the\n// team id + bot user id), prove the bot + app tokens belong to the SAME app,","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/integrations/slack/byo_install.go#L11-L47","documentation":"Slack BYO validation error: the pasted xoxb- bot token and xapp- app-level token belong to DIFFERENT Slack apps. Persisting such a pair would appear connected but be broken: inbound events arrive over the app token's Socket Mode connection (routed by its app id) while mention detection and outbound posting use the bot token's identity.","triggerScenarios":"Calling RegisterBYO where the app id embedded in the xapp- token does not match the app id of the xoxb- bot token — e.g. the admin created two Slack apps and copied the bot token from one and the app-level token from the other.","commonSituations":"Two similarly named apps in the Slack dashboard; regenerated tokens in a new app after following an outdated tutorial; copy/paste mix-up between browser tabs each open to a different app's credentials page.","solutions":["Open ONE Slack app's configuration and copy both tokens from that same app: Bot User OAuth Token (xoxb-) from OAuth & Permissions and App-Level Token (xapp-) from Basic Information.","Delete the stale/duplicate app if it exists, so only one candidate remains to copy from.","After re-copying, re-run RegisterBYO; the mismatch check passes only when both tokens embed the same app id."],"exampleFix":"// before\nparams.BotToken = \"xoxb-...\" // app A\nparams.AppToken = \"xapp-...\" // app B\nerr := svc.RegisterBYO(ctx, params)\n// -> \"slack: the bot token and app-level token are from different Slack apps\"\n\n// after: pre-validate app id alignment in the handler\nbotAppID, appAppID := parseAppID(params.BotToken), parseAppID(params.AppToken)\nif botAppID != \"\" && appAppID != \"\" && botAppID != appAppID {\n\treturn respondError(w, 400, \"both tokens must come from the same Slack app\")\n}\nerr := svc.RegisterBYO(ctx, params)","handlingStrategy":"validation","validationCode":"botApp := appIDFromToken(params.BotToken)  // parses app id from xoxb- token\nappApp := appIDFromToken(params.AppToken) // parses app id from xapp- token\nif botApp != \"\" && appApp != \"\" && botApp != appApp {\n\treturn respondError(w, 400, \"both tokens must come from the same Slack app\")\n}\n_ = svc.RegisterBYO(ctx, params)","typeGuard":null,"tryCatchPattern":"if err := svc.RegisterBYO(ctx, params); err != nil {\n\tif errors.Is(err, slack.ErrTokenAppMismatch) {\n\t\treturn respondError(w, 400, \"bot token and app token are from different apps — copy both from ONE app\")\n\t}\n\treturn err\n}","preventionTips":["Copy both tokens in one sitting from one app's pages, in one browser tab.","Delete abandoned half-configured Slack apps so only one candidate exists to copy from.","Remember the failure mode this prevents: a mismatched pair 'connects' but inbound and outbound use different identities."],"tags":["slack","byo","token-mismatch","configuration","go"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}