JavaScript API
The script tag creates one client and exposes it as window.signal. Calls never throw and never block your page: when something fails, a method returns null or false and the client emits an error event.
// Wait until Signal has a session (resolves within a few seconds, or carries on without).
await window.signal?.ready;
console.log(window.signal.visitorId);
Tracking events
window.signal.track("cta_clicked", { label: "Book a demo" });
window.signal.track("pricing_viewed");
type: letters, digits and_ . : -, up to 64 characters.data: any small JSON object.- Events are batched and sent every 2 seconds (
page_view,cta_clicked,pricing_viewed,resource_requestedandcontact_startedgo immediately), and on page hide. - Signal records
page_view,engagement(time on the page) and, on Shopify,product_viewby itself. - Event types listed under
classifyOnin your configuration make Signal re-estimate the visitor straight away.
Identifying a visitor
When a visitor tells you who they are on your own forms or login, pass it on:
await window.signal.identify({
name: "Robin Baker",
email: "robin@example.com",
company: "Bakery Robin",
externalId: "user_123", // your own id
traits: { plan: "pro", seats: 12 },
});
All fields are optional. Only identify people who gave you these details themselves.
Reading what Signal knows
const profile = await window.signal.getProfile();
// {
// visitorId: "vis_…",
// identity: { name?: "Robin", company?: "…", emailKnown: true },
// signals: { leadTemperature: { value: "hot", confidence: 0.86, label: "Lead temperature" } },
// session: { pageViews: 4, engagedSeconds: 130, returningVisitor: true }
// }
Events
const off = window.signal.on("signal_changed", ({ key, value, confidence, previous }) => {
if (key === "leadTemperature" && value === "hot") showSalesBanner();
});
off(); // unsubscribe
| Event | Payload | When |
|---|---|---|
ready |
{ visitorId, sessionId, config } |
A session opened (or the ids changed). |
profile_changed |
profile | The visitor's profile changed. |
signal_changed |
{ key, value, confidence, previous? } |
An estimate changed. |
question |
InteractionEvent |
Signal wants to ask a question. |
offer |
InteractionEvent |
Signal wants to show an offer. |
action |
InteractionEvent |
A configured browser action (message, link, …). |
chat |
InteractionEvent |
The team is chatting with this visitor. |
outcome |
{ interactionId, routeId?, step } |
The visitor chose a route, gave details or declined. |
form |
{ formId, phase, runId? } |
A smart form started, moved a step, was submitted or ended. |
next |
next interaction | Every time Signal's next step changes. |
identified |
profile | After identify(). |
error |
Error |
A call failed (network, validation, rate limit). |
Drawing your own UI
question, offer, action and chat events carry { interaction, preventDefault() }. Call preventDefault() to keep it out of the built-in popup and show it your way:
window.signal.on("question", (event) => {
event.preventDefault();
const { interactionId, question } = event.interaction;
// question.input.type is "choice", "text" or "email"
renderMyQuestion(question, (optionId) =>
window.signal.answer({ questionId: question.id, optionId, interactionId }));
});
window.signal.on("offer", async (event) => {
event.preventDefault();
const { interactionId, offer } = event.interaction;
// offer.routes: [{ id, type, label, effort, needs: [{ key, label, type }] }]
const step = await window.signal.chooseRoute(interactionId, offer.routes[0].id);
// step.step is "collect" (ask for step.fields), "open" (step.url), "done", "chat" or "products"
});
To turn the popup off entirely, add data-signal-popup="false" to the script tag.
| Method | Returns | Use |
|---|---|---|
answer({ questionId, optionId?, text?, interactionId? }) |
{ next, profile } or null |
Answer a question. |
dismiss(interactionId) |
boolean |
The visitor closed it. |
chooseRoute(interactionId, routeId) |
step or null |
The visitor picked a route of an offer. |
submitContact(interactionId, { routeId, fields, consent }) |
step or null |
Send the fields a collect step asked for. |
declineOffer(interactionId) |
boolean |
"No thanks". |
getNext() |
next interaction | Ask for the current next step. |
Smart forms from code
await window.signal.mountForm("#contact", "projectLead"); // element or selector, form key
Consent
window.signal.setConsent({ storage: "persistent", analytics: true, personalisation: true });
See Consent and storage.
Other methods
| Method | What it does |
|---|---|
reset() |
Forget this visitor and start a new anonymous one (e.g. on log-out). |
lastErrorCode() |
The error code of the last failed call, e.g. "too_many_submissions". |
destroy() |
Stop Signal on this page and remove the popup and forms. |
DOM events
windowreceivessignal:event(event.detail = { event, data }) for configuredemit_eventactions, when the built-in popup is on.documentreceivessignal:cart-added(event.detail = { variantId, handle }) when a visitor adds a recommended product to the cart from the popup.