Slack Bubble connects each website chat widget to one Slack channel. A Slack workspace can have multiple widgets, and a website can embed several of them.
/invite @Bubble (use your development app's name when testing).</body> tag.<script src="https://slack-bubble.com/bubble.js?widget_id=WIDGET_ID" async></script>
Replace WIDGET_ID with the ID from the dashboard. Each channel can have one widget. You can embed that widget on more than one website.
Include each widget's snippet:
<script src="https://slack-bubble.com/bubble.js?widget_id=SALES_WIDGET_ID" async></script>
<script src="https://slack-bubble.com/bubble.js?widget_id=SUPPORT_WIDGET_ID" async></script>
The launchers stack automatically. Each widget has its own saved draft, conversation, unread count, and position. Opening a chat closes the other open panel. Give widgets distinct names, colors, or icons so visitors can tell them apart.
Anonymous visitors get a session for each widget. To identify a logged-in customer across widgets in the same Slack workspace, set the user before loading the embeds:
<script>
window.bubble = {
user: {
name: "Customer name",
email: "[email protected]",
external_id: "YOUR_CUSTOMER_ID",
signature: "SERVER_GENERATED_SIGNATURE"
}
};
</script>
Generate the signature on your server using the team's signature secret:
import { createHash } from "node:crypto";
const signature = createHash("sha256")
.update(externalId + signatureSecret)
.digest("base64url");
Keep the signature secret on your server. The same signed identity works across that team's widgets, while conversation history and access tokens remain scoped to each widget.
For different identities or widgets belonging to different teams, provide settings per widget:
<script>
window.bubble = {
widgets: {
SALES_WIDGET_ID: { user: { external_id: "123", signature: "SALES_SIGNATURE" } },
SUPPORT_WIDGET_ID: { user: { external_id: "456", signature: "SUPPORT_SIGNATURE" } }
}
};
</script>
Per-widget user settings take precedence over window.bubble.user.
The callback receives both the widget controller and its ID:
window.onSlackBubbleReady = function (widget, widgetId) {
// For example, open Support from your own button.
if (widgetId === "SUPPORT_WIDGET_ID") {
document.getElementById("contact-support").onclick = () => widget.onOpen();
}
};
Controllers are also available in window.slackBubbleWidgets[widgetId] after initialization. Including the same widget twice does not create a second instance.
Select a widget in the dashboard to update its settings or disable it. Disabling prevents new loads and blocks messaging and realtime delivery for existing sessions. Changing its channel affects new conversations; existing conversations stay in their original Slack threads.
Working-hours and offline-message fields are reserved in the widget data model; scheduling is not yet implemented. Domain restrictions are not currently supported.
If a widget does not load, check its ID and enabled setting. If messages do not reach Slack, verify the app has been invited to the selected channel and that the channel is available.
Visitor login takes widget_id instead of team_id. The server resolves the owning team. Use the WebSocket method get_widget for public widget configuration. All authenticated operations use the widget associated with the visitor's token.
This release requires a fresh database and new widget-based embeds. No existing-data migration or old team-based embed alias is provided. To show a widget on the app's own pages, set DEMO_WIDGET_ID to an enabled widget ID.