コンテンツにスキップ

WebChat

The WebChat and WebChat GUI messaging extensions let you embed a chat experience directly in your website. messaging-webchat exposes the Direct Line-compatible messaging surface, and messaging-webchat-gui adds the hosted fullpage UI and embeddable chat bubble. They support:

  • Real-time messaging
  • Adaptive Cards
  • File uploads
  • Custom theming
  • Mobile responsive
  • OAuth/OIDC authentication (Google, Microsoft Entra ID, GitHub, custom OIDC)
  • Built-in locale picker for language selection
User's Browser
▼ HTTP / WebSocket polling stream
┌─────────────────────────────────────────┐
│ WebChat Widget (React) │
│ (greentic-webchat component) │
└─────────────────────────────────────────┘
▼ Direct Line Protocol
┌─────────────────────────────────────────┐
│ Direct Line-compatible surface │
│ (messaging-webchat pack) │
└─────────────────────────────────────────┘
▼ runtime routing
┌─────────────────────────────────────────┐
│ Greentic Runner │
└─────────────────────────────────────────┘
  1. Configure Provider

    answers.json
    {
    "messaging-webchat-gui": {
    "enabled": true,
    "public_base_url": "https://your-domain.com",
    "base_url": "https://your-domain.com",
    "route": "webchat",
    "mode": "local_queue",
    "tenant_channel_id": "demo:webchat",
    "jwt_signing_key": "replace-with-a-long-random-secret"
    }
    }
  2. Run Setup

    Terminal window
    gtc setup --answers answers.json ./my-bundle
  3. Start Runtime

    Terminal window
    gtc start ./my-bundle
  4. Embed in Website

    There are three ways to embed WebChat. Choose the one that fits your use case:

    Link directly to the hosted fullpage chat UI:

    <a href="https://your-domain.com/webchat/gui/demo/default">
    Open Support Chat
    </a>
OptionRequiredDescription
enabledYesEnable/disable provider
public_base_urlYesPublic runtime URL used in generated links and webhook/client configuration
base_urlYesBase URL used by the GUI assets and generated embed snippets
routeNoWeb route, usually webchat
modeNoWebChat mode, for example local_queue in local demos
tenant_channel_idNoTenant/channel binding used by the generated GUI
jwt_signing_keyYes for GUI auth/token modeSigning key for WebChat-issued tokens
oauth_enabledNoEnable OAuth/OIDC login for WebChat (default: false)
oauth_enable_googleNoEnable Google login in the GUI
oauth_enable_microsoftNoEnable Microsoft login in the GUI
oauth_enable_githubNoEnable GitHub login in the GUI
oauth_enable_customNoEnable custom OIDC login in the GUI
<script>
window.greenticChatConfig = {
tenant: 'demo',
baseUrl: 'https://your-domain.com'
};
</script>
<script src="https://your-domain.com/v1/web/webchat/demo/embed.js" defer></script>
<script>
window.greenticChatConfig = {
tenant: 'demo',
baseUrl: 'https://your-domain.com',
theme: {
primaryColor: '#0078D4',
backgroundColor: '#f5f5f5',
bubbleBackground: '#ffffff',
bubbleFromUserBackground: '#0078D4',
bubbleTextColor: '#333333',
bubbleFromUserTextColor: '#ffffff',
sendBoxBackground: '#ffffff',
sendBoxTextColor: '#333333'
}
};
</script>
<script src="https://your-domain.com/v1/web/webchat/demo/embed.js" defer></script>
<script>
window.greenticChatConfig = {
tenant: 'demo',
baseUrl: 'https://your-domain.com',
user: {
id: 'user-123',
name: 'John Doe',
email: 'john@example.com'
}
};
</script>
<script src="https://your-domain.com/v1/web/webchat/demo/embed.js" defer></script>
<script>
window.greenticChatConfig = {
tenant: 'demo',
baseUrl: 'https://your-domain.com',
openOnLoad: false
};
</script>
<script src="https://your-domain.com/v1/web/webchat/demo/embed.js" defer></script>
- id: reply
type: reply
config:
message: "Hello! How can I help you today?"

WebChat has full Adaptive Card support:

- id: welcome_card
type: adaptive-card
config:
card:
type: AdaptiveCard
version: "1.4"
body:
- type: TextBlock
text: "Welcome!"
size: Large
weight: Bolder
- type: TextBlock
text: "I'm your virtual assistant. How can I help?"
- type: ActionSet
actions:
- type: Action.Submit
title: "Get Help"
data:
action: "help"
- type: Action.Submit
title: "Contact Support"
data:
action: "support"
- id: suggestions
type: reply
config:
message: "What would you like to do?"
suggested_actions:
- "Check Order Status"
- "Talk to Support"
- "View FAQs"

Enable file uploads:

window.greenticChatConfig = {
// ... other config
uploadEnabled: true,
uploadAccept: '.pdf,.doc,.docx,.png,.jpg',
uploadMaxSize: 10485760 // 10MB
};

Handle uploads in flow:

- id: handle_upload
type: branch
config:
conditions:
- expression: "attachments.length > 0"
to: process_file
default: normal_message
- id: process_file
type: reply
config:
message: "Thanks! I've received your file: {{attachments[0].name}}"
- id: thinking
type: reply
config:
typing: true
typing_duration: 2000 # milliseconds
to: actual_reply
- id: actual_reply
type: reply
config:
message: "Here's what I found..."

WASM components emit outputs as JSON (serialized to CBOR). The runtime wraps each output into a ChannelMessageEnvelope before handing it to the WebChat provider, which in turn serializes the envelope to a DirectLine activity on the wire.

Structured, provider-aware data travels in the envelope’s extensions map — not at the top level of the component output. extensions is the canonical carrier: it is provider-agnostic, round-trips end-to-end, and the WebChat provider passes known keys through to DirectLine verbatim (no stripping, no normalization).

The keys below are declared in greentic-types (src/messaging/extensions.rs) and are the source of truth. Downstream consumers must tolerate unknown keys and MUST round-trip them — stripping unrecognized keys is a bug.

KeyShapePurpose
adaptive_cardObjectAdaptive Card JSON. Replaces the legacy metadata["adaptive_card"] string convention.
channel_dataObjectBot Framework / DirectLine channelData — free-form per-channel metadata.
entitiesArrayBot Framework entities array (mentions, structured annotations, …).
attachmentsArrayProvider-native attachments with inline content. Preferred over the URL-only envelope.attachments when inline content is required (e.g. Adaptive Cards).

Other well-known keys — rag, suggested_actions, speak, input_hint — follow the same pass-through rule.

The WebChat provider accepts attachments in either of two shapes. Both round-trip to the DirectLine activity identically:

  1. Top-level attachments array on the component output — convenient when you are already writing DirectLine-native JSON.
  2. extensions["attachments"] array on the envelope — the canonical carrier, useful when other extension keys are set alongside (channel_data, entities, …).

Each attachment entry uses the Bot Framework {contentType, content} shape. For an Adaptive Card:

{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{ "type": "TextBlock", "text": "Welcome!" }
]
}
}

The following component output emits an Adaptive Card attachment plus channelData and entities. All three round-trip to the DirectLine activity reaching the WebChat client.

Schematic: the snippets below illustrate the JSON output shape, not the component entrypoint. Real Greentic components expose operations through their WIT world (via wit-bindgen-generated trait impls) — see the component authoring guide for the actual entrypoint.

use serde_json::json;
fn build_output() -> serde_json::Value {
let card = json!({
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{
"type": "TextBlock",
"text": "Welcome!",
"weight": "Bolder",
"size": "Medium"
}
]
});
json!({
"text": "Here is your card:",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": card
}
],
"channelData": { "source": "my-component" },
"entities": [ { "type": "mention", "id": "user-123" } ]
})
}

Equivalent output using the canonical extensions carrier:

// card is the same AdaptiveCard JSON as above
json!({
"text": "Here is your card:",
"extensions": {
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": card
}
],
"channel_data": { "source": "my-component" },
"entities": [ { "type": "mention", "id": "user-123" } ]
}
})
// Get chat instance
greenticChat.open();
greenticChat.close();
greenticChat.toggle();
greenticChat.isOpen();
greenticChat.hide();
greenticChat.show();
greenticChat.resetBadge();

Direct Line REST API endpoints:

Terminal window
# Start conversation
POST /v1/messaging/webchat/{tenant}/v3/directline/conversations
Authorization: Bearer <token>
# Send message
POST /v1/messaging/webchat/{tenant}/v3/directline/conversations/{conversationId}/activities
Content-Type: application/json
{
"type": "message",
"text": "Hello!"
}
# Get messages
GET /v1/messaging/webchat/{tenant}/v3/directline/conversations/{conversationId}/activities
<style>
.greentic-webchat {
--primary-color: #0078D4;
--background-color: #ffffff;
--text-color: #333333;
--border-radius: 8px;
--font-family: 'Segoe UI', sans-serif;
}
.greentic-webchat .message-bubble {
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.greentic-webchat .send-button {
background-color: var(--primary-color);
}
</style>
window.greenticChatConfig = {
// ... other config
botAvatar: 'https://example.com/bot-avatar.png',
userAvatar: 'https://example.com/user-avatar.png',
showAvatars: true
};

The header area can display a title, subtitle, and close button. When the locale picker is enabled, it appears in the header. When OAuth is enabled, a logout button is also rendered in the header next to the locale picker.

window.greenticChatConfig = {
// ... other config
header: {
title: 'Support Chat',
subtitle: 'We usually reply within minutes',
showCloseButton: true
}
};

When the greentic.cap.bundle_assets.read.v1 capability is enabled, the wizard scaffolds a per-tenant skin directory under ./assets/ in your bundle. You can edit these files to customize the WebChat appearance without rebuilding the provider pack.

The runtime resolves asset files using an overlay strategy: bundle files override pack defaults on a per-file basis. If you customize skin.json in your bundle, the runtime uses your version. If you leave page.css untouched, the pack’s built-in default is used.

This means you can selectively override only the files you need to change.

FileLocationPurpose
skin.jsonassets/webchat-gui/skins/{tenant}/skin.jsonTheme config: colors, title, logo, feature flags
index.htmlassets/webchat-gui/skins/{tenant}/fullpage/index.htmlFullpage chat HTML template
page.cssassets/webchat-gui/skins/{tenant}/fullpage/page.cssFullpage custom styles
bubble.cssassets/webchat-gui/skins/{tenant}/embed/bubble.cssChat bubble custom styles
en.jsonassets/webchat-gui/skins/{tenant}/i18n/en.jsonEnglish locale strings
id.jsonassets/webchat-gui/skins/{tenant}/i18n/id.jsonIndonesian locale strings
{tenant}.htmlassets/webchat-gui/embed/{tenant}.htmlAuto-generated embed snippets

Edit skin.json to update colors and branding:

assets/webchat-gui/skins/demo/skin.json
{
"title": "Acme Support",
"subtitle": "We are here to help",
"logo": "/assets/webchat-gui/skins/demo/logo.png",
"primaryColor": "#E63946",
"backgroundColor": "#F1FAEE",
"showLocalePicker": true,
"showOAuthLogin": true,
"embedEnabled": true
}

No rebuild or restart is needed — the runtime picks up the changes on the next request.

The WebChat GUI includes a built-in locale picker that lets users switch languages at runtime. The locale picker appears in the chat header, allowing users to select their preferred language without reloading the page.

window.greenticChatConfig = {
// ... other config
locale: 'es-ES',
showLocalePicker: true,
availableLocales: ['en-US', 'es-ES', 'fr-FR', 'de-DE', 'ja-JP'],
strings: {
'placeholder': 'Escribe un mensaje...',
'send': 'Enviar',
'connecting': 'Conectando...',
'reconnecting': 'Reconectando...'
}
};

When showLocalePicker is enabled, a language selector appears in the header next to the close button (and logout button, if OAuth is enabled). Changing the locale updates all UI strings and notifies the flow so responses can adapt to the selected language.

WebChat supports optional OAuth/OIDC authentication to require users to log in before accessing the chat. When enabled, a login overlay is displayed before the chat interface becomes available. The implementation uses a client-side PKCE (Proof Key for Code Exchange) flow for security.

ProviderWizard fieldsNotes
Googleoauth_enable_google, oauth_google_client_id, oauth_google_client_secretGoogle OAuth 2.0
Microsoft (Entra ID)oauth_enable_microsoft, oauth_microsoft_client_id, oauth_microsoft_client_secretMicrosoft identity platform
GitHuboauth_enable_github, oauth_github_client_id, oauth_github_client_secretGitHub OAuth Apps
Custom OIDCoauth_enable_custom, oauth_custom_auth_url, oauth_custom_token_url, oauth_custom_client_id, oauth_custom_scopesAny OpenID Connect-compatible provider exposed by the installed pack
  1. User opens the WebChat widget
  2. A login overlay is shown with a sign-in button for the configured provider
  3. The user authenticates through the provider’s login flow (client-side PKCE)
  4. On successful authentication, the overlay is dismissed and the chat becomes accessible
  5. The authenticated user’s identity is passed to the flow as user context
  6. A logout button appears in the header (next to the locale picker) for signing out

Each provider is configured through the answers.json file or interactively via gtc setup. The wizard-generated schema is the source of truth for the exact fields supported by the installed pack.

  1. Create OAuth credentials in the Google Cloud Console

    • Create a new OAuth 2.0 Client ID
    • Set application type to “Web application”
    • Add your WebChat domain to “Authorized JavaScript origins”
    • Add https://your-domain.com/webchat/auth/callback to “Authorized redirect URIs”
  2. Configure the provider

    answers.json
    {
    "messaging-webchat-gui": {
    "enabled": true,
    "public_base_url": "https://your-domain.com",
    "oauth_enabled": true,
    "oauth_enable_google": "true",
    "oauth_google_client_id": "123456789.apps.googleusercontent.com",
    "oauth_google_client_secret": "your-google-client-secret"
    }
    }
  3. Run setup and start

    Terminal window
    gtc setup --answers answers.json ./my-bundle
    gtc start ./my-bundle
  1. Register an application in the Azure Portal

    • Register a new application under “App registrations”
    • Set the redirect URI to https://your-domain.com/webchat/auth/callback (type: SPA)
    • Under “Authentication”, enable “Access tokens” and “ID tokens” for implicit grant
    • Note your Application (client) ID and Directory (tenant) ID
  2. Configure the provider

    answers.json
    {
    "messaging-webchat-gui": {
    "enabled": true,
    "public_base_url": "https://your-domain.com",
    "oauth_enabled": true,
    "oauth_enable_microsoft": "true",
    "oauth_microsoft_client_id": "your-application-client-id",
    "oauth_microsoft_client_secret": "your-microsoft-client-secret"
    }
    }
  3. Run setup and start

    Terminal window
    gtc setup --answers answers.json ./my-bundle
    gtc start ./my-bundle
  1. Create an OAuth App in GitHub Developer Settings

    • Go to “OAuth Apps” and create a new application
    • Set the “Authorization callback URL” to https://your-domain.com/webchat/auth/callback
    • Note the Client ID
  2. Configure the provider

    answers.json
    {
    "messaging-webchat-gui": {
    "enabled": true,
    "public_base_url": "https://your-domain.com",
    "oauth_enabled": true,
    "oauth_enable_github": "true",
    "oauth_github_client_id": "your-github-client-id",
    "oauth_github_client_secret": "your-github-client-secret"
    }
    }
  3. Run setup and start

    Terminal window
    gtc setup --answers answers.json ./my-bundle
    gtc start ./my-bundle
  1. Register a client with your OIDC provider

    • Set the redirect URI to https://your-domain.com/webchat/auth/callback
    • Ensure your provider supports PKCE
    • Note the client ID and the issuer/authority URL
  2. Configure the provider

    answers.json
    {
    "messaging-webchat-gui": {
    "enabled": true,
    "public_base_url": "https://your-domain.com",
    "oauth_enabled": true,
    "oauth_enable_custom": "true",
    "oauth_custom_auth_url": "https://your-oidc-provider.com/oauth/authorize",
    "oauth_custom_token_url": "https://your-oidc-provider.com/oauth/token",
    "oauth_custom_client_id": "your-client-id",
    "oauth_custom_scopes": "openid profile email"
    }
    }

    Use gtc setup to confirm whether your pack expects explicit authorization/token URLs or an issuer URL for custom OIDC.

  3. Run setup and start

    Terminal window
    gtc setup --answers answers.json ./my-bundle
    gtc start ./my-bundle

When OAuth is enabled on the server side, the widget automatically shows the login overlay. No additional client-side configuration is needed. The authenticated user’s identity is available in flows via the user context:

window.greenticChatConfig = {
tenant: 'demo',
baseUrl: 'https://your-domain.com'
// OAuth login overlay is shown automatically when oauth_enabled is true
};

After authentication, the header displays a logout button next to the locale picker. Clicking it clears the session and returns the user to the login overlay.

Instead of manually writing answers.json, you can use the interactive setup wizard which guides you through provider-specific configuration with conditional logic:

Terminal window
gtc setup ./my-bundle

The wizard detects OAuth-related fields and asks only the follow-up questions exposed by the installed pack version.

The messaging-webchat-gui provider includes a full web UI served at /v1/web/webchat/{tenant}/ and a chat bubble widget that can be embedded on any website.

Add a floating chat bubble to any page with two script tags:

<script>
window.greenticChatConfig = {
tenant: 'demo',
baseUrl: 'https://your-domain.com',
bubble: { color: '#10B981', label: 'Chat with us' },
window: { title: 'Support Assistant' }
};
</script>
<script src="https://your-domain.com/v1/web/webchat/demo/embed.js" defer></script>

The baseUrl is auto-detected from the script src if not provided.

window.greenticChatConfig = {
// ── Required ──
tenant: 'demo', // tenant ID — determines skin and routes
// ── Base ──
baseUrl: 'https://your-domain.com', // auto-detected from script src
fontFamily: 'Inter,system-ui,sans-serif', // font for all widget UI elements
locale: '', // language passed to iframe (?lang=id)
// ── Bubble ──
bubble: {
color: '#10B981', // bubble background color (skin brand.primary)
hoverColor: '#059669', // color on hover
position: 'bottom-right', // bottom-right | bottom-left
size: 56, // diameter in px
offset: 20, // distance from edge in px
offsetBottom: 20, // distance from bottom in px
icon: null, // custom icon URL (replaces default chat SVG)
iconSize: 28, // icon size in px
label: 'Chat with us', // tooltip text on hover
borderRadius: '50%', // '50%' = circle, '12px' = rounded square
badge: true, // show unread message counter
badgeColor: '#ef4444', // badge background color
greeting: '', // proactive message above bubble
greetingDelay: 3000, // ms before greeting appears (0 = immediate)
},
// ── Chat Window ──
window: {
width: 400, // window width in px
height: 620, // window height in px
borderRadius: 12, // corner radius in px
header: true, // show header bar
headerColor: '#064e3b', // header background color
headerTextColor: '#ffffff', // header text color
title: 'Greentic Assistant', // header title (skin brand.name)
logo: null, // header logo URL (skin brand.logo)
logoSize: 24, // logo size in px
shadow: '0 8px 40px rgba(0,0,0,0.2)',
},
// ── Behavior ──
openOnLoad: false, // auto-open on page load
openDelay: 0, // delay in ms before auto-open
closeOnEscape: true, // close with Escape key
mobileFullscreen: true, // fullscreen on viewports < 480px
persist: false, // remember open/close state across pages
zIndex: 2147483646, // z-index for all widget elements
sound: false, // play sound on new bot message
soundUrl: '', // custom sound URL (default: built-in beep)
animation: true, // pop-in entrance animation on mount
// ── Event Callbacks ──
onOpen: function () {}, // chat window opened
onClose: function () {}, // chat window closed
onMessage: function (data) {}, // bot message received
onUserMessage: function (data) {}, // user sent a message
};

Priority: explicit config > skin.json defaults > hardcoded defaults. For example, if you don’t set bubble.color, it falls back to skin.jsonbrand.primary#10B981.

After the widget mounts, a global greenticChat object is available:

greenticChat.open() // open the chat window
greenticChat.close() // close the chat window
greenticChat.toggle() // toggle open/close
greenticChat.isOpen() // returns boolean
greenticChat.hide() // completely hide bubble + window
greenticChat.show() // show bubble again
greenticChat.resetBadge() // clear unread message counter

When badge: true (default), a red counter appears on the bubble when the bot sends messages while the widget is closed. The badge automatically resets when the user opens the chat.

Show a proactive message above the bubble to increase engagement:

bubble: {
greeting: 'Hi! Need help? Ask me anything.',
greetingDelay: 2000, // show after 2 seconds
}

The greeting dismisses when the user clicks it (opens chat) or clicks the close button. It only appears once per page load.

Play a notification sound when a bot message arrives while the widget is closed:

{
sound: true, // enable with built-in beep
// or
sound: true,
soundUrl: '/assets/notify.mp3', // custom sound file
}

The built-in sound is a short sine-wave beep generated via the Web Audio API — no external files needed.

Pass a language to the webchat iframe:

{
locale: 'id', // loads iframe with ?lang=id
}

The webchat GUI picks up the lang query parameter and loads the corresponding i18n strings.

Keep the widget open across page navigation within the same session:

{
persist: true, // uses sessionStorage
}

By default, the bubble appears with a pop-in animation. Disable it with:

{
animation: false,
}

React to widget events on the host page:

{
onOpen: function () {
analytics.track('chat_opened');
},
onClose: function () {
analytics.track('chat_closed');
},
onMessage: function (data) {
console.log('Bot said:', data);
},
onUserMessage: function (data) {
console.log('User said:', data);
},
}
<!DOCTYPE html>
<html>
<head><title>My Website</title></head>
<body>
<h1>Welcome to My Website</h1>
<script>
window.greenticChatConfig = {
tenant: 'demo',
fontFamily: "'Segoe UI', Roboto, sans-serif",
locale: 'en',
bubble: {
color: '#059669',
hoverColor: '#047857',
label: 'Need help?',
badge: true,
greeting: 'Hi! I can help you get started.',
greetingDelay: 2000,
},
window: {
width: 420,
height: 640,
headerColor: '#064e3b',
title: 'Support Assistant',
},
persist: true,
sound: true,
animation: true,
zIndex: 9999,
onOpen: function () { console.log('Chat opened'); },
onMessage: function (data) { console.log('Bot:', data); },
});
</script>
<script src="https://your-domain.com/v1/web/webchat/demo/embed.js" defer></script>
</body>
</html>

Skins control the fullpage layout and branding. Each skin is a directory under assets/webchat-gui/skins/{name}/:

skins/{name}/
skin.json # Branding, colors, logo, mode
fullpage/index.html # Landing page HTML
fullpage/page.css # Landing page styles
webchat/styleOptions.json
webchat/hostconfig.json
webchat/hooks.js
assets/logo.svg
assets/favicon.ico

When greentic.cap.bundle_assets.read.v1 capability is enabled, the runtime scaffolds webchat-gui assets into the bundle’s assets/ directory. Files placed there override the provider pack defaults — allowing custom skins without rebuilding the pack.

The runtime provides a native JWT token endpoint at:

GET /v1/messaging/webchat/{tenant}/token

Returns: {"token": "<jwt>", "expires_in": 1800, "conversationId": ""}

The token is signed with the jwt_signing_key secret configured during setup.

  • Tokens are short-lived (1 hour default)
  • Tokens are scoped to conversation
  • Refresh tokens are not exposed to client

When OAuth is enabled:

  • Authentication uses the PKCE flow, which does not require a client secret on the frontend
  • Tokens are validated server-side before granting chat access
  • The login overlay blocks all chat interaction until authentication succeeds
  • Session logout clears tokens from the client and invalidates the server-side session
{
"messaging-webchat": {
"allowed_origins": [
"https://www.example.com",
"https://app.example.com"
]
}
}
  1. Check console for JavaScript errors
  2. Verify script URL is correct
  3. Check CORS configuration
  1. Verify Direct Line endpoint is accessible
  2. Check WebSocket support
  3. Review firewall settings
  1. Check conversation is active
  2. Verify token hasn’t expired
  3. Review browser console for errors