Skip to content
XICBOT

Settings

Deutsch
Documentation · Website widget

Widget delivery and security

How XICBOT delivers the loader and the chat window: versioned file names, checksums, security policy, response headers, key checks and per-visitor limits.

In plain language

This page explains how the chat window gets to your website. The widget has two parts. A small loader sits on your page. The chat window runs in a frame of its own. This keeps it apart from your page. The chat window checks its files when it loads them. Limits apply to each visitor. The page also explains what happens when there is a fault.

Last updated

All documentation topics

The widget is made of a small loader on your page and a chat window that runs in a frame of its own. This page explains how both are delivered, which headers they carry, which checksums the chat window uses to load its parts, how the interface checks a widget key and which limits apply to each visitor. For adding the widget to your site, see Embed the widget.

The widget files

Delivery at https://cdn.xicbot.de serves exactly four files. If you serve the widget from your own domain, the same files sit under your path; see Own path on your domain.

FilePurposeContent type
einbetten.jsLoader on your page. It reads the attributes of the script tag and builds trigger and frame. It does not send any network request itself.text/javascript; charset=utf-8
rahmen.htmlSkeleton of the chat window, page title Chat. It carries <meta name="robots" content="noindex, nofollow">.text/html; charset=utf-8
rahmen.cssStyling of the chat windowtext/css; charset=utf-8
rahmen.jsLogic of the chat window: loading settings, running the conversation, showing messagestext/javascript; charset=utf-8
version.jsonOverview of the current versions, see Version overviewapplication/json; charset=utf-8
  • The root / returns rahmen.html.
  • A HEAD request receives the same headers without a body.
  • Any other name, an outdated version included, gets status 404 with the text nicht gefunden.

Versioned names and caching

Every file can be reached under two names: its entry name, such as rahmen.js, and a versioned name like rahmen.1a2b3c4d5e.js. The ten hexadecimal characters are the start of the SHA-256 hash of the delivered content, so a changed file gets a new versioned name. For rahmen.html the hash is taken only after the references to the other two parts have been inserted. A new version of rahmen.css or rahmen.js therefore also produces a new version of the skeleton.

RequestCache-ControlMeaning
Entry name, such as einbetten.jspublic, max-age=300Browsers and caches keep the file for up to five minutes.
Versioned name, such as rahmen.1a2b3c4d5e.jspublic, max-age=31536000, immutableValid for a year without asking the server again.
rahmen.html under either nameno-storeFetched afresh on every opening because it carries the security policy.
version.jsonno-storeReflects the state at the moment of the request.
  • Only the current version is served. If the identifier in a versioned name does not match the current content, delivery answers 404 nicht gefunden rather than quietly serving new content under the old name.
  • Every file states its version in the X-Xicbot-Version header.
  • The embed code points to the entry name einbetten.js. A new widget version therefore reaches your page without any change to the embed code once the five-minute cache period has run out.

Checksums in the chat window

In its source, rahmen.html refers to the entry names rahmen.css and rahmen.js. On delivery, both references are replaced by the versioned names, and the SHA-384 hash of each content is added as integrity. The browser can then take the parts from its cache as long as the version is current, and it rejects any file whose content does not match the hash.

rahmen.html html

<link rel="stylesheet" href="rahmen.<version>.css" integrity="sha384-…" crossorigin="anonymous">
<script src="rahmen.<version>.js" integrity="sha384-…" crossorigin="anonymous"></script>

crossorigin="anonymous" is required here. The chat window has an opaque origin, so every request it makes counts as cross-origin. Without the attribute the browser would keep the response opaque and the check would fail. So that the parts still load, delivery sends Access-Control-Allow-Origin: * and Cross-Origin-Resource-Policy: cross-origin with every file.

Version overview

/version.json lists the current versions of all four files. The response is not cached (no-store) and any page may read it (Access-Control-Allow-Origin: *).

FieldContent
generatedTime of the most recent change among the four files, in ISO 8601 format
originAddress the chat window is loaded from; with your own path, your address
apiAddress of the interface the chat window talks to
files[].nameEntry name of the file
files[].versionTen hexadecimal characters of the SHA-256 hash
files[].versioned_nameVersioned name of the file
files[].sriSHA-384 hash in the form sha384-…
files[].bytesSize in bytes

Shell command bash

# Fetch the current versions
curl -s https://cdn.xicbot.de/version.json
# Look at the headers of the loader
curl -sI https://cdn.xicbot.de/einbetten.js

Security policy of the chat window

rahmen.html arrives with a policy of its own in the Content-Security-Policy header. It applies inside the chat window only, not to your page; what your own policy has to allow is covered in Content security policy of your page. The addresses in it are the base the chat window is loaded from and the address of the interface, which with your own path means your domain.

DirectiveValueEffect
default-src'none'Anything not explicitly allowed stays blocked.
script-srcBase with trailing slash, such as https://cdn.xicbot.de/Scripts only from the base of the chat window
style-srcBase with trailing slashStyles only from the same base
connect-srcInterface address with trailing slashRequests only to the chat interface
img-srcBase with trailing slash, data: and https:Images from the base, as data: addresses and over https from other addresses as well. The window only places external images in product cards, and only with an https address.
base-uri'none'No <base> element can redirect the addresses.
form-action'none'The window does not submit forms.
frame-ancestors*The chat window may be embedded in any page. Allowed domains decides where a conversation may start.
  • The trailing slash limits the permission to the path of the base. With your own path https://example.com/chat it covers https://example.com/chat/, not the rest of the domain.
  • Separation from your page comes from the sandbox of the frame, not from frame-ancestors; see Isolation from your page.

Response headers

Each of the four files is sent with these headers:

HeaderValuePurpose
Content-Typeper file, see The widget filesContent type with UTF-8 character set
X-Content-Type-OptionsnosniffThe browser does not guess a different content type.
Referrer-Policyno-referrerFollow-up requests do not reveal the referring page.
Access-Control-Allow-Origin*The chat window, with its opaque origin, may load and verify the files.
Cross-Origin-Resource-Policycross-originThe files may be used within other sites.
Cache-Controlsee Versioned names and cachingHow long caches keep the file
X-Xicbot-Versionten hexadecimal charactersVersion of the delivered file
Content-Security-Policyonly on rahmen.htmlPolicy of the chat window, see above
Varyonly on rahmen.html: X-Xicbot-Base, X-Xicbot-ApiKeeps the versions for different base addresses apart in caches.
  • Vary is needed because the policy depends on the X-Xicbot-Base and X-Xicbot-Api headers. Without it a cache could hand one customer’s skeleton and policy to the next.
  • X-Frame-Options is left out on purpose, since such a header would stop the window from being embedded in your page.
  • On cdn.xicbot.de, Strict-Transport-Security: max-age=31536000; includeSubDomains and X-Robots-Tag: noindex, nofollow are added; see Two embedding routes. The noindex in the rahmen.html skeleton applies on both routes, including your own path.
  • version.json carries only Content-Type, X-Content-Type-Options, Access-Control-Allow-Origin and Cache-Control.

Addresses in the headers

With your own path, your server sets the X-Xicbot-Base and X-Xicbot-Api headers; see What your server forwards. On cdn.xicbot.de, XICBOT sets them itself and overrides any header of the same name that a visitor sends. These addresses feed the policy of the chat window and the origin and api fields in version.json.

  • An address is accepted if it consists of http:// or https://, a host name of lower-case letters, digits, dots and hyphens, an optional port and a path of letters, digits and the characters ., _, ~ and -.
  • At most 200 characters. Surrounding whitespace is trimmed, and so is a trailing slash.
  • The check is strict because the address ends up in the policy, where a semicolon or a space would add a directive.
  • A header that is set but unusable yields status 400 with the text kopf ungueltig instead of silently falling back to the default address.
  • Without X-Xicbot-Api, X-Xicbot-Base is used for the interface as well.

Isolation from your page

The section Chat window on your website in the app states: The chat window runs in its own frame. It can neither read nor change the page it sits on. This is how that works:

  • The frame carries sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox" without allow-same-origin, plus referrerpolicy="no-referrer", loading="eager" and the title Chat.
  • Without allow-same-origin the chat window has an opaque origin. Your page cannot read its content, and the chat window cannot read yours. That alone does not protect the history: anyone who knows the conversation ID from your page's storage can retrieve it through the API.
  • The loader passes its settings in the fragment of the frame address after #: key, interface address, origin of your page, position, language, display mode, the identifier of a running conversation, and the font, corner radius and brand colour of your page. Browsers do not send the fragment to the server, so it does not appear in any access log.
  • The loader itself sends no network requests. Everything that fetches data runs inside the chat window.
  • The chat window calls the interface without cookies or sign-in (credentials: omit) and without revealing the referring page.
  • The interface sends cross-origin permission headers only under /api/v1/public/: for the origin null that such a frame sends, for the own origins of the app and of delivery, and for origins that XICBOT explicitly allows. Allowed are GET, POST, DELETE and OPTIONS with the headers Content-Type and Accept. The Retry-After header is readable, a preflight is cached for 600 seconds, and requests with credentials are not permitted.
  • Messages between loader and chat window are checked for sender and origin on both sides; see Checks on both sides.
  • The frame is built only after your page has finished loading. With the consent gate switched on, it waits for consent; see Consent and visitor rights.
  • Without data-xicbot the loader builds nothing. A second loader on the same page has no effect.

Key check at the interface

Every request under /api/v1/public/widget/{key}/ first checks the public key of the assistant. Two separate switches on the assistant decide: it has to be set to active, and Enable chat window has to be switched on. An unknown, a disabled, a paused and a draft assistant give the same answer, so guessing a key does not reveal whether it exists.

SituationResponseEffect in the chat window
Unknown key404 unknown_widgetThis chat is not available., the trigger disappears
Enable chat window is switched off404 unknown_widgetas above
The assistant is set to Draft or paused rather than active404 unknown_widgetas above
Your account is not active404 unknown_widgetas above
The demo access has expired404 demo_expiredas above
The assistant has no active version yetSettings with ready: false, 422 no_version when a conversation startsThis assistant is not ready yet.
Conversation start from a page outside Allowed domains403 host_not_allowedThis chat is not enabled for this website.

Only unknown_widget and demo_expired make the chat window disappear: it tells the loader that the assistant is gone, and the loader removes trigger, bubble and frame from your page. A 404 without one of these codes usually comes from a wrong address, such as data-api pointing at the wrong host, and is treated as a disruption.

When a conversation starts, the chat window reports the origin of your page as passed on by the loader. The host name is compared in lower case, cut to 120 characters. An entry in Allowed domains includes its subdomains; with an empty list, a conversation can start on any website. Details are in Allowed domains.

Per-visitor limits

A visitor is a source address combined with the widget key. IPv4 addresses count individually, IPv6 addresses per /64 network because a connection there receives a whole network. An IPv4 address written in IPv6 notation counts as an IPv4 address. Counting by key alone would let one visitor use up the limit for everybody. Visitors behind a shared exit, such as a company network, a mobile network or the same /64 network, count as one visitor, however, and share this widget's limits.

LimitValueApplies to
Calls (widget)60 per minuteLoading settings, starting a conversation, loading history, rating an answer, revocation, information request, deleting a conversation, rejecting a tool proposal, feedback on a form, viewing attachments. They all share one counter.
Sending (widget-send)12 per minuteSending a message, confirming a tool proposal, uploading an attachment
Calls under your accountPer-minute value of your plan, plus a starting value of 2000 per dayEvery call except sending a message and attachments, including confirming a tool proposal and merely loading the settings
Questions under your accountStarting values of 10 per minute and 200 per daySending a message
  • The first two limits are the same for every account. The other two take their values from your account but are also counted per visitor and key, so one visitor who writes a lot does not slow down anyone at a different address or, for IPv6, in a different /64 network.
  • Each account-based limit has a per-minute value against bursts and a per-day value against sustained load.
  • When a call belongs to a conversation, the conversation is counted alongside the address, with the same values. Either counter can reject; both are increased only if neither rejected. The conversation counter does not lift the shared limit of an address.
  • Your plan sets the per-minute value for calls; see Plans and quotas. XICBOT sets the values for questions for your account.
  • A value below 1 switches an account-based limit off.
  • Attachments count only against the first two limits.

Response when a limit is exceeded

An account-based limit answers with status 429, a Retry-After header and the body below. wait gives the waiting time in seconds, the same number as Retry-After; the two limit values are those of your account.

antwort.json json

{
  "error": "too_many_requests",
  "limit_per_min": 10,
  "limit_per_day": 200,
  "wait": 42
}

The widget and widget-send limits also answer with 429 and Retry-After, but without wait in the body. General limits of the interface are described in API limits, the endpoints themselves in API: widget.

How the chat window handles disruptions

After a 429, the chat window waits as long as wait in the body says. Without wait it reads Retry-After, and failing that it uses a fallback. The wait lies between one second and one hour; from 90 seconds on the window shows it in minutes. The wording of every message is listed under Messages.

StepSituationBehaviour
Loading settings429Waits for wait or Retry-After, otherwise 30 seconds, then tries again. Counts towards the maximum of six fetches.
Loading settingsNetwork failure or status 500 and aboveFurther attempts after 15, 30 and 60 seconds, then after 2 and 4 minutes. After the sixth fetch, The chat cannot be reached right now. remains.
Loading settingsAny other errorImmediately The chat cannot be reached right now., with no further attempt
Starting a conversation429Waits for wait or Retry-After, otherwise 20 seconds. No cap on attempts.
Starting a conversationNetwork failure or status 500 and aboveUp to three further attempts after 15, 30 and 60 seconds, then The chat cannot be reached right now.
Starting a conversation403This chat is not enabled for this website.
Starting a conversationOther status, such as 422The chat could not be started.
Loading history404 or 410 without a key-check codeAn unknown or expired conversation is not an error: the window starts over and shows the greeting if one is set.
Loading history429 or another disruptionUp to five further attempts, after wait (fallback 20 seconds) or after 30 seconds. Then The conversation could not be loaded.
Sending a message429The visitor’s own bubble disappears and the text returns to the input field if that is empty. Sending stays blocked until the wait is over, fallback 30 seconds. The message Too many messages in a short time. Your message did not arrive and is back in the input field — please send it again in …. shows the time left.
Settings, start or history404 unknown_widget or demo_expiredChat window and trigger disappear; see Key check at the interface.

Delivery check in the app

Each time the embed code is fetched, XICBOT checks afresh whether the delivered file einbetten.js can be read, rather than relying on a stored value. If it cannot, the message The delivered widget file could not be read. The embed code is shown, but the delivery itself should be checked. appears under Embed code via our address. The embed code can still be copied. In that case, please let us know at mail@xicbot.de.

Through the interface, GET /api/v1/assistants/{id}/embed returns the field reason for this: file_missing if the file is absent or cannot be accessed for reading, file_unreadable if reading it fails nonetheless, and null otherwise.

Further reading

Frequent questions

Do I need to replace the embed code after a new widget version?

No. The embed code points to the entry name einbetten.js, which browsers cache for up to five minutes. The skeleton rahmen.html is fetched afresh on every opening and refers to the current versioned names.

Can the chat window read the content of my page?

No. The frame runs in a sandbox without allow-same-origin and therefore has an opaque origin. It can neither read nor change your page, and your page cannot read its content. The two only exchange messages that are checked for sender and origin. One of them carries the conversation ID, which the loader keeps in your page's storage; anyone who knows it can retrieve the history through the API.

Why has the chat trigger disappeared from my page?

The interface rejected the key with unknown_widget or demo_expired. Check in the app that Enable chat window is switched on, the assistant is set to active and your account is active. An expired demo access has the same effect.

What does the message about too many requests mean?

A per-visitor limit has been reached. The chat window shows the waiting time and then tries again by itself. Counting happens per source address, for IPv6 per /64 network, and widget key, so visitors with a different address are not affected, while visitors behind the same exit, such as a company network, are.

Why does delivery answer with status 400 when I use my own path?

Your server set X-Xicbot-Base or X-Xicbot-Api to an address that does not fit the accepted pattern, for example with a query string, a space or more than 200 characters. Delivery then answers kopf ungueltig instead of quietly falling back to the default address.

Something still unclear?

Tell us which page and which step you mean. We reply personally.

Enquiry in three steps

  1. 1. Topic
  2. 2. Your case
  3. 3. Contact

What is this about?

One click is enough, everything else is optional.

Tell us briefly about your case

Everything in this step is optional. It helps us reply with something concrete straight away instead of asking back.

Enquiries per month, roughly (optional)
When should it start? (optional)
How can we reach you?

Name and email address are enough. We reply personally.

We use your details to handle your enquiry. More on this in the privacy policy.