Widget B2C — guest visitor and company
This section covers integrations where the widget stays in a guest session:
- public site, unknown visitor: script and contact form only
- site or portal that already knows the account:
init()with profile and company data, form skipped when the data is enough, contact linked to the company
Personal history on another browser or device is a different integration. See Widget B2B. Full field contract, account changes in a SPA, CSP, and provisioning: Technical integration.
Associating a company does not authorize another person’s history. A user authenticated on the site remains a widget guest until you send external_user_id together with a valid restore_id.
What you get
- A floating button, or a chat embedded in a page container
- An initial form for unknown visitors, labeled Telephone and Name. In
init()the fields arephoneandname - A guest session stored by the loader in the same browser, separated by widget key. If
localStorageis blocked, continuity can stay on the current page - If you send
external_id, Quoting24 looks for a company with the sameweb_code. Withcompany_nameas well, it can create the missing company and link the contact to it - Tickets and replies in Quoting24, as on the other channels
The guest session rereads its most recent conversation even after the tab is closed. It does not follow the person to another browser.
1. Prerequisites in Quoting24
- Widget channel active in Settings → Channels (Impostazioni → Canali)
- Logo, color, position, greeting, welcome, and out-of-hours message configured
- Allowed domains updated, including staging and
localhost - Embed code copied from the channel configuration: script URL and public key
Use the host and key of the same environment. In the examples, replace WIDGET_HOST and PUBLIC_WIDGET_KEY.
Domains are host names, without https://, port, path, or *.. example.com includes subdomains. Empty list: no restriction. Locally, serve the page over HTTP, not as file://.
The company_name in init() is the customer’s legal name. It is not the widget title and it is not configured from this payload.
2. Install the floating button
Paste the script once, before </body>:
The button appears after the visual settings load. A new visitor sees the contact form. Someone returning in the same browser resumes the guest session, if storage is available.
The script exposes a single window.Quoting24Widget.
3. Embed the chat in the page
Create the container before the script and give it an explicit height:
<section aria-labelledby="support-heading">
<h2 id="support-heading">Support</h2>
<div id="support-chat" style="width: 100%; height: 600px;"></div>
</section>
<script
src="https://WIDGET_HOST/assets/widget/script.js?key=PUBLIC_WIDGET_KEY&mode=embedded&target=support-chat"
async
></script>
targetis the element ID, without#- The iframe fills the container
- There is no floating button
open(),close(), andtoggle()have no effect- In this mode the site decides height and width. In floating mode, on mobile, the chat can fill the viewport
Embedded mode alone does not enable history restore.
4. Send the customer profile
When the site knows the account, send the profile after the loader has loaded. Fields are strings. Omit unknown ones: do not send null, numeric IDs, or an empty email.
window.Quoting24Widget.init({
external_id: "52",
company_name: "Ricambi Stella S.r.l.",
name: "mario.rossi",
phone: "+393331234567",
email: "amministrazione@ricambi-stella.example",
});
| Data in the site or ERP | Field | Meaning |
|---|---|---|
Stable company reference, the same value imported into web_code |
external_id |
Exact match with companies.web_code, in the widget organization |
| Legal name | company_name |
Used if the company must be created |
| Person or user name | name |
Name shown on the contact |
| Account email | email |
Saved on the company only when the company is created |
| Phone, if you know it | phone |
Contact number. Prefer international format, for example +393331234567 |
This payload does not send external_user_id or restore_id. Keep name and company_name separate: do not concatenate the legal name onto the user name to obtain the association. The public field is external_id. Do not send web_code, the Quoting24 internal UUID, or companies.code.
The form shows Name and Telephone. In init() those fields are name and phone. The email field is email. Name, Telephone, and Email are not keys: the loader drops them, the same way it drops web_code in place of external_id.
Examples
Name and phone, the same data as the form. The chat starts and does not look up a company.
Profile with a company. name is the person, phone is the contact number, and email is written on the company only if this call creates it.
widget.init({
external_id: "52",
company_name: "Ricambi Stella S.r.l.",
name: "Mario Rossi",
phone: "+393331234567",
email: "mario.rossi@ricambi-stella.example",
});
Email only. A valid address in email is enough to open the session.
If a field exceeds the limits or the email is malformed, the request is rejected. Limits and behavior of each field: Technical integration.
Wait for the loader
An async script may not have run yet when later code calls init(). For prefilled data, use a loader module instead of the static tag, imported once in the main layout:
// /assets/quoting24-loader.js
const widgetScriptUrl =
"https://WIDGET_HOST/assets/widget/script.js?key=PUBLIC_WIDGET_KEY";
let widgetPromise;
export function loadWidget() {
if (window.Quoting24Widget) {
return Promise.resolve(window.Quoting24Widget);
}
if (widgetPromise) return widgetPromise;
widgetPromise = new Promise((resolve, reject) => {
const script = document.createElement("script");
script.id = "quoting24-widget-script";
script.src = widgetScriptUrl;
script.async = true;
script.onload = () => {
if (window.Quoting24Widget) resolve(window.Quoting24Widget);
else {
script.remove();
reject(new Error("Widget API is not available"));
}
};
script.onerror = () => {
script.remove();
reject(new Error("Widget script failed to load"));
};
document.body.appendChild(script);
}).catch((error) => {
widgetPromise = undefined;
throw error;
});
return widgetPromise;
}
import { loadWidget } from "/assets/quoting24-loader.js";
const widget = await loadWidget();
widget.init({
external_id: "52",
company_name: "Ricambi Stella S.r.l.",
name: "mario.rossi",
phone: "+393331234567",
email: "amministrazione@ricambi-stella.example",
});
Import the module from the bundler, or load the entry file with <script type="module" src="/assets/support.js"></script>. For embedded mode, add &mode=embedded&target=support-chat to the URL and mount the container first.
loadWidget() only reports that the JavaScript API is available. init() returns void: it queues the profile if the iframe is not ready and does not wait for the session. For the outcome, use isReady() and hasSessionError().
init() replaces the stored profile. It does not merge it with the previous one. Pass every field that must remain.
A page change in a SPA does not need a new script or a reset(). On a person or account change, and on logout, call reset() before the new profile: changing only the fields does not detach the previous guest session. Full pattern: Login, logout, and account change.
5. How the company is associated or created
Quoting24 runs this flow at initialization, even if the visitor has not sent a message yet:
- With a non-empty
external_id, it looks for a company in the same organization whoseweb_codeequals that value - If it finds one, it links the contact. The existing company’s name and email stay unchanged
- If it does not find one and a non-empty
company_nameis also present, it creates the company withname = company_name,web_code = external_id, and the email when present, then links the contact - If it cannot find or create the company, it continues with the available profile. If that is not enough for a new guest, it shows the form. Database errors or the request limit fail initialization
| Data sent | Situation in Quoting24 | Result |
|---|---|---|
external_id: "52", company_name: "Ricambi Stella S.r.l." |
web_code = "52" exists |
Uses the existing company and does not change its name |
| Same data, plus a valid email | web_code = "52" does not exist |
Creates the company with web code 52, legal name, and email, and associates the contact |
external_id: "52", name: "mario.rossi" |
No match | Starts the guest without a new company association |
external_id: "52", with no other data |
A match exists | Starts the guest associated with the company, with the default name Visitatore |
external_id: "52", with no other data |
No match | Shows the contact form |
company_name without external_id, plus name |
Any | Starts the visitor. Does not search for or create a company |
The search does not use legal name, email, phone, VAT number, or companies.code. "052" and "52" are different values; spaces in external_id are not removed. If you import the registry first, write in web_code the same value the site will send in external_id. See also Companies → Web code.
A new guest starts if there is at least a name, a phone, a valid email, or an external_user_id, or a company found or created through external_id. init({}) and company_name alone do not start a session.
For a contact that is already linked, a new valid code can change their company. Omitting the code, or sending one that does not resolve, does not clear the previous association. A person or account change requires reset().
Limits
web_code is not unique. With several matches, public initialization takes the first one by ascending creation date. Simultaneous requests that do not find the company yet can create more than one: creation is not atomic and does not replace an import or a sync from the ERP.
Implicit creation is limited to 20 attempts per 60-second window and IP address, in the backend process that handles the request. Above the limit, initialization fails with TOO_MANY_REQUESTS: retry with a delay, without continuous init() loops.
The data comes from the browser and the visitor can alter it. The link does not prove membership of the company and does not grant company history. Send company_name only if you want to allow this public creation. To update companies that already exist, use Quoting24 tools or an authorized ERP integration.
The email from public initialization is stored only on the new company. It is not written on the contact and it does not update a company that already exists. A phone already used by another contact does not merge contacts or chats.
6. Behavior on the site
| Situation | Expected behavior |
|---|---|
| First visitor, script only | Button, then form, then chat |
init() with a sufficient profile |
Form skipped |
external_id already present as web_code |
Contact associated; company name and email unchanged |
New external_id with company_name |
Company created and contact associated |
New code and name, without company_name |
Chat without a new company |
| New code only | Contact form |
| Legal name and visitor name only | No search or creation by name |
| Reload in the same browser | Guest session resumed, if storage is available |
| Another browser or device | New guest session |
| Outside working hours | Out-of-hours banner; composition stays usable |
| Mobile, floating button | The chat can fill the viewport |
Attachments: PDF, DOC, DOCX, JPG/JPEG, PNG. Maximum 10 MiB per file and 100 MiB of referenced attachments per conversation.
7. Useful controls
When the floating button is visible you can connect a site button to window.Quoting24Widget?.open().
| Method | Use |
|---|---|
init(profile) |
Sets or replaces the current profile |
open() / close() / toggle() |
Floating widget only. Closing is not a logout |
isReady() |
Visitor session started. Stays false on the contact form |
hasSessionError() |
Session error reported by the iframe |
reset() |
Clears profile, local session, saved guest credential, and unread count of this instance |
dispose() |
Unmounts the interface and listeners. Does not clear the saved guest credential or the global API object |
Do not wait for isReady() to let a new guest open the form. There is no public availability event. These indicators do not describe the whole network state.
Checklist before release
- [ ] Host and key of the same environment
- [ ] Site and staging domains in the list, as host names only
- [ ] Logo, color, greeting, welcome, and out-of-hours message checked
- [ ] New browser, script only: form, message, ticket in Quoting24
- [ ]
external_idalready inweb_code: association, company record unchanged - [ ] New code with
company_name: company created with that web code - [ ] New code without
company_name: no company created - [ ] Empty email, numeric ID, or legal name too long: payload rejected, to be corrected
- [ ] Reload and SPA navigation: one instance, guest session kept
- [ ] Account change:
reset()immediately, late responses discarded - [ ] Mobile: composition usable
- [ ] Unauthorized domain: widget rejected
Frequent problems: Troubleshooting.