Skip to content

Update Webchat Settings

PATCH
/webchat/settings

Partial edit of the theme. An unset field is left untouched.

THIS IS LIVE. A saved theme is what the next visitor sees, with no publish step in between.

To REMOVE a design-token override, list its path in themeOverridesRemove. Sending null for it does not remove it - under a partial update that reads as “leave it alone”.

Authorizations

Parameters

Header Parameters

X-Target-Org-Id
Any of:
string

Request Body required

Partial edit of the widget theme. An unset field is left untouched.

themeOverridesRemove is the only way to DELETE a token override rather than set it to something - sending null for a token would be “unset in this request”, which the partial-update rule reads as “leave it alone”.

object
assistantMessageBgColor
Any of:
string
<= 20 characters
assistantMessageTextColor
Any of:
string
<= 20 characters
assistantName
Any of:
string
<= 100 characters
borderRadius
Any of:
string
<= 20 characters
botAvatarCustomUrl
Any of:
string
<= 1000 characters
botAvatarMode
Any of:
string
Allowed values: agent preset custom
botAvatarPresetUrl
Any of:
string
<= 1000 characters
botIconType
Any of:
string
<= 50 characters
chatWidth
Any of:
string
<= 20 characters
chat_bot_id
required

Owning chat bot id (UUID).

string
conversationQuestionLimit
Any of:
integer
<= 20
conversationQuestionsAfterFirstMessage
Any of:
string
Allowed values: hide remain collapse
copyOverrides
Any of:
object
key
additional properties
string
darkMode
Any of:
boolean
designTokens
Any of:

Extended widget theme tokens stored in chat_widget_settings.designTokens.

Shape is the shared FE/BE contract — the TypeScript twin lives in frontend/features/webchat/widget-preview/chat-design-settings.ts.

object
badge
Any of:

Unread launcher-badge colors (hex-validated).

object
background
Any of:
string
text
Any of:
string
colors
Any of:

Color slots consumed by the widget’s —wcw-* CSS var vocabulary.

LIGHT-mode and mode-invariant leaves ONLY. Per-mode dark variants live on the sibling DesignTokenColorsDark group (designTokens.colorsDark), NOT as *Dark twins here (Phase G). The @field_validator("*") hex guard below is exactly WHY the dark palette must be a sibling group rather than a nested colors.dark object — a nested dict would be fed to _require_hex_color and 422 the save (the same force that made DesignTokenHeader separate). A legacy stored colors.<name>Dark leaf is lifted into colorsDark at parse time by DesignTokens._lift_legacy_dark_leaves — no data migration.

object
actionCardBg
Any of:
string
actionCardBorder
Any of:
string
actionCardIconTint
Any of:
string
actionCardText
Any of:
string
agentBubbleBg
Any of:
string
agentBubbleBorder
Any of:
string
agentBubbleText
Any of:
string
askCardBg
Any of:
string
askCardBorder
Any of:
string
askCardText
Any of:
string
border
Any of:
string
bubbleAssistantBg
Any of:
string
bubbleAssistantText
Any of:
string
bubbleUserBg
Any of:
string
bubbleUserText
Any of:
string
headerGradientFrom
Any of:
string
headerGradientTo
Any of:
string
heroText
Any of:
string
heroTextMuted
Any of:
string
launcherBg
Any of:
string
launcherIcon
Any of:
string
link
Any of:
string
messagesSearchBg
Any of:
string
messagesSearchBorder
Any of:
string
messagesSearchText
Any of:
string
messagesTitleText
Any of:
string
sendButtonBg
Any of:
string
sendButtonIcon
Any of:
string
slotChipBg
Any of:
string
slotChipHoverBg
Any of:
string
slotChipHoverBorder
Any of:
string
slotChipText
Any of:
string
surface
Any of:
string
textPrimary
Any of:
string
textSecondary
Any of:
string
typingDot
Any of:
string
colorsDark
Any of:

Per-mode DARK colour overrides (designTokens.colorsDark, Phase G).

A sibling of DesignTokenColors holding the dark variant of every colour leaf that renders differently in dark mode. ABSENT ⇒ the widget renders exactly as it did before this group existed (the dark literal, or — for a leaf an org set in colors and that historically painted both modes — that colors value). Only leaves with a genuine dark render path live here; a mode-invariant leaf (launcher, header gradient, send button, hero) has no entry, so no dead tokens are introduced.

Field names carry NO Dark suffix — the group is the mode. The 13 leaves that previously lived as colors.<name>Dark twins were migrated here; the upconverter on DesignTokens lifts any legacy value so no row regresses.

object
actionCardBg
Any of:
string
actionCardBorder
Any of:
string
actionCardIconTint
Any of:
string
actionCardText
Any of:
string
agentBubbleBg
Any of:
string
agentBubbleBorder
Any of:
string
agentBubbleText
Any of:
string
askCardBg
Any of:
string
askCardBorder
Any of:
string
askCardText
Any of:
string
border
Any of:
string
bubbleAssistantBg
Any of:
string
bubbleAssistantText
Any of:
string
composerPopoverBg
Any of:
string
composerPopoverBorder
Any of:
string
messagesSearchBg
Any of:
string
messagesSearchText
Any of:
string
slotChipBg
Any of:
string
slotChipHoverBg
Any of:
string
slotChipHoverBorder
Any of:
string
slotChipText
Any of:
string
surface
Any of:
string
textPrimary
Any of:
string
textSecondary
Any of:
string
typingDot
Any of:
string
geometry
Any of:

RFC Phase E4 — launcher / panel / badge / chip geometry, CLAMPED.

The most locked-down group in the model, because chat-script injects these into the CUSTOMER’s page rather than into the widget iframe (RFC §17.1). Unclamped geometry on a host page is a defacement vector, so every leaf is whole-pixel-only and range-bounded — see _require_px_in_range.

Ranges for launcherSize / launcherOffset* are INHERITED from the ratified product decision (RFC §19 decision 4: “launcher geometry in scope with clamped ranges — size ~36-64px, offsets ~8-48px”), not invented here.

Widget COLLAPSED WIDTH is deliberately absent: it is already plumbed as the flat chatWidth column and read at runtime by the deployed bundle, so a token would be a second writer for one property.

── TWO CLAMPS ARE ONE-DIRECTIONAL (max pinned at today’s value) ──────────── widgetWidthExpanded (max 640) and launcherOffsetX (max 20) may only ever SHRINK. This is not conservatism, it is a live constraint:

The expanded panel needs a widgetWidthExpanded + launcherOffsetX viewport to fit, but the mobile veto that hides the expand control is the hardcoded MOBILE_BREAKPOINT_PX = 560 (chat-script/src/components/container.ts, mirrored as a literal @media (max-width: 560px) in styles.ts). Today’s 640 + 20 = 660 already exceeds it, so viewports 561-659px ALREADY render an overflowing panel — a pre-existing defect (GH #12173). Letting either value GROW would widen that band onto more real devices.

A media-query CONDITION cannot read a custom property, so the guard cannot be derived the way the panel’s bottom is; correcting it means making MOBILE_BREAKPOINT_PX config-derived across container.ts + expand.ts + the emitted media query. That is GH #12173, and it is what lifts these two ceilings. Until it lands, 640 and 20 are load-bearing — do not raise them.

THE RULE, so it is applied correctly next time: an axis needs a one-directional clamp exactly when it has NO viewport clamp. The horizontal axis has none. The vertical axis has calc(100dvh - 140px), and the worst case under these ranges is launcherOffsetY(48) + launcherSize(64) + gap(11) = 123px, which stays inside that 140px reserve with 17px to spare at every viewport height — which is why launcherOffsetY, launcherSize, badgeSize and chipGap are free to grow. Any future range change must preserve launcherOffsetY + launcherSize + 11 <= 140.

object
badgeSize
Any of:
string
<= 10 characters
chipGap
Any of:
string
<= 10 characters
launcherOffsetX
Any of:
string
<= 10 characters
launcherOffsetY
Any of:
string
<= 10 characters
launcherSize
Any of:
string
<= 10 characters
widgetHeight
Any of:
string
<= 10 characters
widgetHeightExpanded
Any of:
string
<= 10 characters
widgetWidthExpanded
Any of:
string
<= 10 characters
header
Any of:

How the header surface is painted — mode + gradient blend point.

A SEPARATE group, not new DesignTokenColors slots, and that is forced: DesignTokenColors hex-validates EVERY field (@field_validator("*")), so a "solid" mode or a numeric stop there would 422 the whole save.

Both leaves are ABSENT by default and absence is meaningful — it means “render exactly as this org rendered before this group existed”:

  • mode absent -> gradient (today’s only behaviour).
  • fromStop absent -> no interpolation hint is emitted at all, so the CSS is byte-identical to the legacy string (see buildWidgetCssVars). The 50/50 default is therefore expressed as ABSENCE, never as a written 50 — existing rows keep rendering without relying on hint-equivalence.
object
angle
Any of:
integer
<= 360
fromStop
Any of:
integer
<= 100
mode
Any of:
string
Allowed values: solid gradient
input
Any of:

Composer input surface colors (hex-validated).

object
background
Any of:
string
borderColor
Any of:
string
focusBorderColor
Any of:
string
motion
Any of:

Interaction motion — one duration (

object
duration
Any of:
string
<= 20 characters
easing
Any of:
string
<= 60 characters
radius
Any of:

Border-radius slots (CSS dimensions; launcher is typically 50%).

object
bubble
Any of:
string
<= 20 characters
control
Any of:
string
<= 20 characters
input
Any of:
string
<= 20 characters
launcher
Any of:
string
<= 20 characters
widget
Any of:
string
<= 20 characters
shadows
Any of:

Box-shadow slots — charset-restricted, see _require_css_shadow.

object
bubble
Any of:
string
<= 200 characters
launcher
Any of:
string
<= 200 characters
launcherHover
Any of:
string
<= 200 characters
suggested
Any of:
string
<= 200 characters
widget
Any of:
string
<= 200 characters
spacing
Any of:

Spacing slots — padding shorthand (1-4 dims) and single-dim gaps.

object
bubblePadding
Any of:
string
<= 50 characters
sectionGap
Any of:
string
<= 50 characters
transform
Any of:

RFC Phase E3 — interaction transform SCALES (unitless multipliers).

Motion timing (duration + easing) already shipped as DesignTokenMotion; this is the other half of E3. Every leaf is optional and absence means “the literal chat-script/src/styles.ts ships today”, so transform absent = byte-identical rendering.

Each leaf carries its OWN range because the ranges are semantic, not cosmetic: a “grow” interaction above ~1.2 reads as a glitch rather than feedback, and a “press” interaction must stay at or below 1.0 or the element grows when pushed.

Rotation is deliberately NOT here. RFC §16 scopes E3 to scales, and the launcher’s icon swap uses a ±90deg PAIR that must stay symmetric — one leaf would invite an asymmetric swap, two would invite a broken one.

object
chipActive
Any of:
string
<= 10 characters
chipHover
Any of:
string
<= 10 characters
launcherActive
Any of:
string
<= 10 characters
launcherHover
Any of:
string
<= 10 characters
rippleFrom
Any of:
string
<= 10 characters
rippleTo
Any of:
string
<= 10 characters
windowEnter
Any of:
string
<= 10 characters
typography
Any of:

Font family / weights. fontUrl is an optional CSS URL (Google Fonts css2).

object
bodyWeight
Any of:
integer
>= 100 <= 1000
fontFamily
Any of:
string
<= 100 characters
fontSize
Any of:
string
<= 20 characters
fontUrl
Any of:
string
<= 1000 characters
headingWeight
Any of:
integer
>= 100 <= 1000
letterSpacing
Any of:
string
<= 20 characters
lineHeight
Any of:
string
<= 20 characters
version
integer
default: 1
expected_revision
Any of:
integer
>= 1
fontFamily
Any of:
string
<= 100 characters
fontSize
Any of:
string
<= 20 characters
headerImageUrlExternal
Any of:
string
<= 1000 characters
headerImageUrlInternal
Any of:
string
<= 1000 characters
headerTextColor
Any of:
string
<= 20 characters
homeQuestionLimit
Any of:
integer
<= 20
launcherQuestionLimit
Any of:
integer
<= 20
primaryColor
Any of:
string
<= 20 characters
robotIconColor
Any of:
string
<= 20 characters
robotIconType
Any of:
string
<= 50 characters
secondaryColor
Any of:
string
<= 20 characters
showHeader
Any of:
boolean
showQuickActions
Any of:
boolean
showSuggestedQuestions
Any of:
boolean
suggestedQuestionsBgColor
Any of:
string
<= 20 characters
suggestedQuestionsSectionBgColor
Any of:
string
<= 20 characters
surface
Any of:
string
Allowed values: external internal
themeOverridesRemove
Any of:
Array<string>
<= 64 items
themeSource
Any of:
string
Allowed values: default brand_kit custom
userMessageBgColor
Any of:
string
<= 20 characters
userMessageTextColor
Any of:
string
<= 20 characters

Responses

200

Successful Response

object
data
required

An open envelope for one webchat object.

Open because the widget’s config grows a field per product decision and a pinned schema would DROP it. Same choice the desk surface made.

object
key
additional properties
any
pagination
Any of:
object
has_next
required

Whether there are more pages

boolean
limit
required

Items per page

integer
next_cursor
Any of:
string
notes
Any of:
Array<string>
page
required

Current page number (1-indexed)

integer
total
required

Total number of items

integer
total_available
Any of:
integer
total_is_lower_bound
Any of:
boolean

422

Validation Error

object
detail
Array<object>
object
loc
required
Array
msg
required
string
type
required
string