Skip to navigation

Web chat widget

Put a Jelliu agent on your website with one script tag.
View as Markdown

The web chat widget adds a floating chat button to your site. Visitors who open it talk to one of your Jelliu agents, by text or, if you enable it, by voice.

Two credentials, not interchangeable

Every widget has two identifiers. Keep them apart:

CredentialWhat it isWhere it goes
Widget ID (a UUID)Public. Identifies the widget. Safe to put in your page.In the embed script URL. The widget sends it as the x-widget-id header or the wid query parameter.
Widget API keySecret. For calling the public widget endpoints directly from your own code.Only in the x-widget-api-key header, and only from a server or app you control. Never in a URL.

The widget API key is shown once, as plaintext_api_key in the response that creates the widget. Jelliu stores only a keyed hash of it, and no other response carries the key or its hash: listing, reading and updating a widget return no api_key field. If you lose the key, create a new widget. The standard embed does not need it.

The widget API key is not a workspace API key (jl_...), and neither one belongs in a web page. The only credential the embed snippet uses is the public widget ID.

Embed the widget

1

Create the widget

Create it in the dashboard under Widgets, or with the API (a write key is enough):

curl -sS -X POST "https://api.jelliu.co/api/widgets" \
-H "Authorization: Bearer $JELLIU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "7c1d9f30-5e2a-4a7e-9c8f-91a0b1c2d3e4",
"name": "Website chat",
"allowed_origins": ["https://www.example.com"],
"branding": {
"primary_color": "#2563EB",
"position": "bottom-right",
"title": "Chat with us"
}
}'

The widget’s id in the response is your widget ID.

2

Add the script to your site

Paste this before the closing body tag of every page where the chat should appear, replacing WIDGET_ID:

<script src="https://api.jelliu.co/widget-embed/WIDGET_ID/embed.js" async></script>

This is the same snippet the dashboard gives you. The script is public and cached by browsers for up to 5 minutes, so branding changes can take that long to show up.

3

Check the allowed origins

Load your page and open the chat. If it does not answer, make sure the page’s origin is in the widget’s allowed_origins (see below).

What the script does

  • Adds a round launcher button in the corner set by branding.position, in your primary_color.
  • Loads the chat panel in an iframe the first time a visitor opens it, so pages where nobody chats only load the small loader.
  • Shows a side panel on desktop and a full-screen panel on screens up to 520 px wide.
  • Allows the chat panel to use the microphone, which voice conversations need. Browsers still ask the visitor before granting it.
  • Runs once per page, even if the snippet is included twice.
  • On Shopify storefronts, forwards the signed-in customer’s email, name and ID, and the shop domain, so conversations are linked to the right contact.

Allowed origins

A widget answers only on the sites listed in allowed_origins. The chat panel reports the origin of the page that embeds it, and Jelliu compares it with the list:

  • The scheme and port must match exactly.
  • The hostname must match, except that one leading www. is ignored: https://example.com also covers https://www.example.com, and the reverse.
  • Other subdomains need their own entry. https://shop.example.com is not covered by https://example.com.
  • * is never accepted. At least one origin is required.

Requests from a page that is not allowed receive 403 with code FORBIDDEN and the message Origin not allowed.

Widget settings

agent_id
string (uuid)Required

The agent that answers visitors. It must be an agent of your workspace that has not been deleted. Any other ID, including an agent of another workspace, is refused on create and update with 404 AGENT_NOT_FOUND (El agente no existe en este espacio de trabajo.).

name
stringRequired

1 to 200 characters.

allowed_origins
string[]Required

Full origins such as https://www.example.com. At least one.

branding
object

primary_color (required inside branding, hex #RRGGBB), position (bottom-right or bottom-left), title (up to 100 characters), subtitle (up to 120), quick_replies (up to 4 short replies), agent_names (up to 8 display names), agent_avatars (up to 8 HTTPS image URLs aligned with agent_names), and i18n (per-language greeting and quick_replies, keyed by language such as es or en).

The object replaces the stored branding; it is not merged.

branding.logo_url was removed on 2026-09-15. It validated, stored and shipped to every visitor, and nothing ever drew it — the widget draws no logo. Sending it now returns 400 VALIDATION_FAILED with El campo branding.logo_url ya no existe: el widget no dibuja ningun logo. under error.details.fieldErrors.branding.

It is also stripped from every response — the admin reads, POST /widget/init and the embed script — so a client that reads a widget, changes one key and sends the object back will not trip over a stored value. Values already saved stay in the database untouched.

greeting_message
string

Up to 2000 characters.

voice_enabled
booleanDefaults to false

Let visitors talk to the agent by voice.

rate_limit_rpm
integerDefaults to 30

Requests per minute allowed for this widget, per credential, on the public /widget/* endpoints. An integer of 1 or more; 30 when you do not send it. Settable on create and on PUT /api/widgets/{id}, and editable from the dashboard.

Raising it does not lift the per-workspace cap of 300 requests per minute across all of a workspace’s widgets, which still applies on top — a workspace with ten widgets at 300 rpm each is still capped at 300 rpm in total. The per-IP limit of 60 requests per minute applies as well.

Update a widget with PUT /api/widgets/{id} (send only the fields to change) and remove it with DELETE /api/widgets/{id}.

Limits

Public widget traffic is limited at several levels:

LevelLimit
Per visitor IP60 requests per minute
Per widgetrate_limit_rpm (30 per minute by default)
Per workspace300 requests per minute across all widgets
Per workspace, per dayA daily cap on visitor messages; once reached, messages are refused until the next UTC day

Exceeding a limit returns 429 with code RATE_LIMIT_EXCEEDED.

Calling the widget endpoints directly

The standard embed handles everything. If you build your own chat interface, the widget endpoints are served from https://api.jelliu.co:

MethodPathBody
POST/widget/initvisitor_id (required), metadata (optional object). Returns 201 with a session_id.
POST/widget/messagesession_id (uuid), message (1 to 4000 characters). An optional attachment can be sent as a multipart file field, up to 10 MB; a larger file is refused with 413 PAYLOAD_TOO_LARGE.

Authenticate with the x-widget-api-key header and send x-widget-parent-origin set to one of the widget’s allowed origins. Keep the widget API key on a server you control.