Vai al contenuto

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 are phone and name
  • A guest session stored by the loader in the same browser, separated by widget key. If localStorage is blocked, continuity can stay on the current page
  • If you send external_id, Quoting24 looks for a company with the same web_code. With company_name as 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

  1. Widget channel active in Settings → Channels (Impostazioni → Canali)
  2. Logo, color, position, greeting, welcome, and out-of-hours message configured
  3. Allowed domains updated, including staging and localhost
  4. 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>:

<script
  src="https://WIDGET_HOST/assets/widget/script.js?key=PUBLIC_WIDGET_KEY"
  async
></script>

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&amp;mode=embedded&amp;target=support-chat"
  async
></script>
  • target is the element ID, without #
  • The iframe fills the container
  • There is no floating button
  • open(), close(), and toggle() 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.

widget.init({
  name: "Mario Rossi",
  phone: "+393331234567",
});

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.

widget.init({
  email: "mario.rossi@ricambi-stella.example",
});

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:

  1. With a non-empty external_id, it looks for a company in the same organization whose web_code equals that value
  2. If it finds one, it links the contact. The existing company’s name and email stay unchanged
  3. If it does not find one and a non-empty company_name is also present, it creates the company with name = company_name, web_code = external_id, and the email when present, then links the contact
  4. 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_id already in web_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.