Configuration
Everything Signal asks and offers comes from your project's configuration: a JSON document you edit in the dashboard (Configuration), publish with the Server API, or let the setup wizard write. Every change becomes a new version; you can restore an older one at any time.
Keys (ids) start with a letter and use letters, digits, _ or -. Texts may use the placeholders {firstName} and {company}.
{
"project": "acme",
"language": "en",
"mode": "live",
"pages": [{ "match": "/pricing", "category": "pricing", "important": true }],
"signals": { "...": {} },
"questions": { "...": {} },
"routes": { "...": {} },
"offers": { "...": {} }
}
Signals
What Signal estimates about each visitor, each with a confidence between 0 and 1.
"signals": {
"lookingFor": {
"type": "choice", "label": "Looking for", "important": true,
"instructions": "What is this visitor most likely looking for?",
"options": { "website": "A new website", "app": "A web app", "other": "None of these" }
},
"timing": {
"type": "score", "label": "Timing",
"instructions": "How soon does this visitor want to start?",
"levels": ["Just looking", "This year", "This quarter", "Now"]
}
}
| Type | Fields |
|---|---|
choice |
instructions, options (2 or more) |
score |
instructions, levels (2–10, low to high) |
proposition |
question (a yes/no question), optional criteria: { true, false } |
All types take label, important (shown in alerts and Insights) and bands: { ignoreBelow: 0.3, askBelow: 0.8 }: below askBelow, Signal may ask a question to be sure.
Pages
"pages": [
{ "match": "/pricing", "category": "pricing", "important": true },
{ "match": "/services/*", "category": "service" },
{ "match": "/blog/**", "category": "blog" }
]
* matches one path segment, /** at the end any depth. The first view of an important page makes Signal re-estimate the visitor.
Questions
"questions": {
"whatKind": {
"text": "What kind of business do you run?",
"learns": ["businessType"],
"priority": 5,
"input": {
"type": "choice",
"options": [
{ "id": "agency", "label": "Agency or services", "sets": { "traits": { "segment": "agency" } } },
{ "id": "shop", "label": "Online shop" }
],
"other": { "label": "Something else…", "trait": "businessOther" }
}
}
}
| Field | Meaning |
|---|---|
text |
The question. |
learns |
Signals the answer settles; Signal asks when one of them is unsure. |
priority |
Higher is asked first. |
when |
A condition that must hold. |
repeatable |
Ask again after it was answered (default false). |
input |
choice (options, optional other for a typed answer), text (writes: identity.name, identity.company or trait), or email (consentText). |
Choice options can set traits and, for shops, filters (productType, tags, vendor, collection, priceMin, priceMax) used by product recommendations.
Routes
The ways a visitor can take the next step. Common fields: label, effort (low, medium, high), description, needs (contact fields to ask for).
| Type | Extra fields | Notes |
|---|---|---|
book |
eventUrl, display (panel or tab) |
A booking page, e.g. Calendly, shown in the popup or a new tab. |
callback |
timeOptions, confirmation |
Always asks for a phone number. |
message |
channel (whatsapp, sms, telegram), number, prefill |
Opens a chat app with a prefilled message. |
send_resource |
resourceUrl, emailTemplate { subject, body, button }, allowDirectOpen, confirmation |
Emails a guide; always asks for an email address. |
lead_form |
needs, confirmation |
A short contact form. |
link |
url, target (_blank or _self) |
Any link, e.g. a trial sign-up. |
subscribe |
list, confirmation |
Newsletter sign-up with marketing consent. |
chat |
greeting |
Opens a live chat with your team. |
products |
intro, mode (answers, similar, both), count (1–3) |
Matching products from your Shopify catalog. |
Offers
"offers": {
"readyToTalk": {
"when": { "type": "signal_equals", "key": "leadTemperature", "value": "hot", "minConfidence": 0.75 },
"text": "Want to talk it through with our lead developer?",
"routes": ["call", "guide"],
"priority": 10
}
}
show (1 or 2 routes at once), priority, and pressure (low offers only low-effort routes). reach.maxOffersPerVisit (default 2) limits offers per visit.
Conditions
Used by offers, questions, follow-ups and rules.
| Type | Fields |
|---|---|
signal_equals |
key, value (an option), minConfidence? |
signal_score_gte |
key, value (a level number, 0 = the first level), minConfidence? |
proposition_gte |
key, value (0–1) |
trait_equals |
key, value |
identity_known |
field: name, email or company |
answered |
question |
products_viewed_gte |
value (number of product pages) |
all, any |
conditions: [...] |
Forms
"forms": {
"earlyAccess": {
"title": "Get early access",
"questions": ["whatKind", "whenStart"],
"goal": ["businessType", "timing"],
"contact": { "needs": ["name", "email"], "message": true },
"confirmation": "Thanks {firstName}, we'll be in touch."
}
}
Embed with <div data-signal-form="earlyAccess"></div>. The form skips questions that are already answered and stops asking once its goal signals are settled (or after maxQuestions, default 5).
Follow-ups
A short email a few days later, only to visitors who agreed to be contacted.
"followUps": {
"afterGuide": {
"after": "delivered", "waitDays": 3,
"subject": "Did the guide help, {firstName}?",
"body": "Hi {firstName},\n\nAny questions about the guide? Happy to help.",
"button": "Book a call", "link": { "route": "call" }
}
}
after: contact_given, delivered, form_submitted, callback_requested, booked or clicked. stopIf (default booked, won, unsubscribed, declined) cancels it.
Rules and actions
Rules run actions when a condition becomes true, for example a webhook:
"actions": { "toCrm": { "type": "webhook", "integration": "crm", "maxPerVisitor": 1 } },
"rules": [{ "id": "hot-lead", "when": { "type": "signal_equals", "key": "leadTemperature", "value": "hot", "minConfidence": 0.8 }, "then": ["toCrm"] }]
Other action types: show_message, show_resource, show_cta, open_url, open_calendar and emit_event. Every action has a cooldown (default 1d) and an optional maxPerVisitor.
Notifications
"notifications": { "to": ["sales@example.com"], "events": ["lead_reachable", "callback_requested", "booked", "form_submitted", "chat_message"] }
Team alerts by email, with the whole visit.
Privacy, brand and language
"privacy": {
"region": "eu",
"texts": { "followUpConsent": "You may contact me about my request.", "privacyUrl": "https://example.com/privacy" },
"retention": { "contactMonths": 24, "eventMonths": 3, "outcomeMonths": 24 }
},
"brand": { "siteUrl": "https://example.com", "senderName": "Acme", "accentColor": "#2f5bea", "popupTheme": "light" },
"language": "en"
privacy.region(eu,uk,us,other) sets consent defaults and the storage default.languageisenornl;uioverrides any built-in word (e.g.{ "noThanks": "Not now" }).mode: "shadow"records what Signal would do without showing or sending anything.