Support Bubble Chat Widget
Overview
The support bubble lets visitors get help directly from your website. You add a small script to your site and a chat bubble appears in the corner. Visitors can chat with an AI assistant or send a message straight to your team. Messages submitted through the bubble become tickets in your There There inbox, just like email.
Each support bubble is linked to an email channel. When a visitor escalates to a human or sends a direct message, a ticket is created on that linked email channel so your team can reply.
Visitor flow
When a visitor opens the bubble, they see a welcome screen with the call-to-action buttons you configure (for example "Ask a question" or "Talk to a human"). Each call-to-action either opens the AI chat or opens a direct message form that turns into a ticket on the linked email channel.
Before a visitor can send their first message, they confirm their email address. They enter their email, receive a 6-digit verification code, and type it back into the bubble. Their session is then trusted for 60 days, so subsequent visits skip verification.
If your website already knows who the visitor is (for example, a logged-in user), you can skip the email and code step entirely by signing the visitor's email with the channel's widget secret key and calling ThereThere.identify({ email, name, signature }) from your page. The bubble will trust the identity and go straight to the welcome screen. See "Identify logged-in visitors" below for the signing recipe, and the Security tab for the secret key.
Plan availability
The AI Chat call-to-action requires a Standard or Pro subscription, because it depends on the AI features. Free workspaces can still expose direct-message and link call-to-actions to their visitors. Free workspaces that try to switch on AI Chat will see an upgrade prompt in the setup UI.
Branding
The bubble shows a small "Powered by There There" line in its footer by default. You can hide this on the Pro plan. Free and Standard workspaces keep the branding visible. The toggle is disabled on those plans with a prompt to upgrade.
Creating a support bubble
Go to Settings > Channels and create a new channel with the Support bubble type. You'll configure:
- Name: An internal label for the channel.
- Linked email channel: The email channel where tickets created by this bubble will appear.
- Position: Bottom right, bottom left, or center. Center opens the bubble as a modal in the middle of the screen and is meant to be triggered by your own button.
- Primary color: The accent color for the bubble, to match your brand.
- Logo: An optional logo shown in the bubble header and welcome screen.
- Title: The heading shown in the bubble header (e.g. "Acme Support").
- Subtitle: Text below the title (e.g. "How can we help?").
- Welcome message: The heading on the welcome screen.
- Welcome subtext: Text below the welcome message.
- Welcome call-to-actions: The buttons visitors see on the welcome screen. Each one is either an AI chat starter or a direct-message form.
- Placeholder text: Hint text in the AI chat input.
Embedding the bubble
After creating the channel, copy the embed code from the Install tab. The exact loader URL is generated for your channel and shown there. It looks like this:
<script src="https://files.there-there.app/loaders/your-widget-id.js" async></script>
Place this snippet before the closing </body> tag on every page where you want the bubble to appear. The script is lightweight and loads asynchronously, so it won't slow down your page.
The bubble does not make any requests to There There until the visitor actually opens it. This means the script has zero performance impact on your page load.
Content Security Policy
If your site sends a Content-Security-Policy header, allow the bubble's origins or the loader script will be blocked silently. The bubble will simply never appear, and the browser console will show messages like "Refused to load the script" or "Refused to frame" mentioning files.there-there.app or there-there.app.
Update your CSP to include the following directives:
Content-Security-Policy:
script-src 'self' https://files.there-there.app;
frame-src https://there-there.app;
connect-src 'self' https://there-there.app wss://there-there.app;
style-src 'self' 'unsafe-inline';
What each directive is for:
script-src: the loader script is served fromfiles.there-there.app.frame-src: the bubble UI renders inside an iframe served fromthere-there.app.connect-src: chat traffic uses XHR and a WebSocket connection tothere-there.app.style-src 'unsafe-inline': the loader injects a tiny inline<style>block for the bubble's entrance and exit animations. If you use a nonce-based CSP, relax this rule for the loader's inline style.
If your stack does not set a CSP, you can skip this section. The bubble works out of the box.
JavaScript API
The embed script exposes a global ThereThere object with the following methods.
ThereThere.open() / ThereThere.close()
Programmatically open or close the bubble. Useful for triggering the bubble from a custom "Contact us" button.
document.querySelector('#help-btn').addEventListener('click', () => {
ThereThere.open();
});
ThereThere.show() / ThereThere.hide()
Show or hide the bubble button entirely. The bubble is visible by default.
ThereThere.identify({ email, name, signature })
Tell the bubble who the current visitor is so they skip the email plus six digit code step. The signature is an HMAC-SHA256 of the lowercased, trimmed email, signed server-side with the channel's widget secret key (found on the Security tab). See "Identify logged-in visitors" below for the server-side signing recipe (PHP, Laravel, and Node.js) and the recommended async load guard.
window.addEventListener('load', () => {
if (window.ThereThere && window.__therethereIdentity) {
window.ThereThere.identify(window.__therethereIdentity);
}
});
ThereThere.signOut()
Call this when the visitor logs out of your application. It clears the locally stored session token and rebuilds the bubble iframe so any in memory identity, visitor data, and conversation history are dropped. The next time the visitor opens the bubble, it starts a fresh anonymous session with no page reload required.
window.ThereThere && window.ThereThere.signOut();
Calling identify(...) with a different visitor's signature switches the active identity but leaves the previous session token in storage. Use signOut() first when the visitor is actually logging out rather than switching accounts mid session.
Identify logged-in visitors
If your site already knows who the visitor is, you can sign their email server side with this channel's secret key (on the Security tab) and call ThereThere.identify(...). The bubble trusts that identity and skips the email and 6-digit code flow.
A note on email casing: our server lowercases and trims the email before checking the signature, so you must compute the HMAC over strtolower(trim($email)). The email you pass to identify(...) can be in any case, because we normalize it on receipt. Mismatched casing between the signed value and the value the server sees is the most common reason verification fails for some visitors.
1. Compute the signature on your server
The signature is an HMAC-SHA256 of the lowercased, trimmed email, signed with the channel's widget secret key. Keep the secret on your server. Never expose it in client-rendered code.
PHP / Laravel:
// config/services.php
'there-there' => [
'widget_secret' => env('THERE_THERE_WIDGET_SECRET'),
],
// in your layout or view composer
$signature = hash_hmac(
'sha256',
strtolower(trim($user->email)),
config('services.there-there.widget_secret'),
);
Node.js:
const crypto = require('crypto');
const signature = crypto
.createHmac('sha256', process.env.THERE_THERE_WIDGET_SECRET)
.update(user.email.toLowerCase().trim())
.digest('hex');
2. Identify the visitor after the loader has executed
The loader is async, so window.ThereThere may not exist when a sibling inline script runs. Stash the identity in a variable and call identify from a window.load handler instead of inline.
<script>
window.__therethereIdentity = {
email: {{ json_encode($user->email) }},
name: {{ json_encode($user->name) }},
signature: {{ json_encode($signature) }},
};
window.addEventListener('load', function () {
if (window.ThereThere && window.__therethereIdentity) {
window.ThereThere.identify(window.__therethereIdentity);
}
});
</script>
<script src="https://files.there-there.app/loaders/your-widget-id.js" async></script>
When the visitor logs out of your application, call ThereThere.signOut() so the next visitor on the same browser starts a fresh anonymous session. See the ThereThere.signOut() method above.
Use your own button
By default the bubble shows a circular launcher button in the corner. If you would rather open the widget from your own UI, turn off "Show the built-in button" on the channel's Setup tab. The launcher disappears and you open the widget by calling ThereThere.open() from your own button.
document.querySelector('#open-support').addEventListener('click', () => {
if (window.ThereThere) {
window.ThereThere.open();
}
});
document.querySelector('#close-support').addEventListener('click', () => {
if (window.ThereThere) {
window.ThereThere.close();
}
});
You can drive the rest of the widget the same way: ThereThere.close() closes the panel, ThereThere.show() and ThereThere.hide() reveal or remove the whole widget, and ThereThere.identify(...) and ThereThere.signOut() manage the visitor's identity. See the JavaScript API section above for the full list.
The "Center" position takes this one step further. It always hides the built-in button and opens the panel as a modal in the middle of the screen, with a dimmed backdrop behind it. Visitors close it with the panel's own close button, by clicking the backdrop, or by pressing Escape. On phones the panel goes full screen regardless of the position you pick.
Domain restrictions
For security, you can restrict which domains are allowed to embed your bubble. Add your allowed domains in the channel settings (for example yourcompany.com). Wildcard patterns like *.yourcompany.com are supported. If no domains are specified, the bubble can be embedded on any site.
Bot protection
The bubble's public endpoints are protected with per IP rate limiting. This runs silently and requires no configuration on your part.
Dark mode
The bubble automatically detects whether your site uses dark mode and adapts its appearance. It checks both the operating system preference (prefers-color-scheme: dark) and class-based dark mode (a dark class on the <html> element, as used by Tailwind CSS). When the visitor toggles dark mode on your site, the bubble updates in real time.
Consent banner
Enable "Require consent" in the channel settings to show a consent banner before the visitor can start chatting. This is useful for GDPR compliance. The visitor's consent is stored in localStorage and remembered across page loads.
How tickets are created
Both AI Chat escalations and direct-message submissions create tickets on the linked email channel. The ticket includes:
- The visitor's message (or AI conversation transcript for escalations).
- The visitor's verified email and name.
- A link back to the widget session, visible in the ticket sidebar under "Support bubble".
Your team can reply to these tickets the same way they reply to email tickets. The visitor sees replies in real time through the bubble while it is open, and receives an email notification with a deep link back into the conversation.
Usage and billing
Every support bubble conversation counts toward your monthly conversation limit, alongside email tickets. This includes AI Chat conversations (whether resolved by AI or escalated) and direct-message submissions. Check your current usage in Settings > Billing.