JavaScript API#
The script adds window.Subcontext.leadform, and a shortcut window.Subcontext.createLead. Other Subcontext
scripts on the same page share window.Subcontext without interfering with each other. Loading the script twice is
safe.
| Call | Gives back | What it does |
|---|---|---|
init(options) |
a form handle | Makes a form, drawn or headless (see Options). |
mountAll(root) |
a list of form handles | Draws every [data-subcontext-leadform] element inside root (default: the whole page) that isn't drawn yet. |
createLead(values) or submit(values) |
a promise of a result | Sends a lead using the page's key. |
consent() |
a promise of the consent wording, or null |
The firm's consent wording and boxes. |
mountConsent(target) |
a promise of true or false | Draws the consent block into your own form. createLead then sends what was ticked. |
openChat(result, target) |
a promise of true or false | After a successful send, opens the chat that follows the form (see The chat after the form). target is optional. |
version |
text | The script's version. |
Subcontext.createLead, submit, consent, mountConsent and openChat all use one shared headless form. It is
made the first time you use any of them, with the script tag's key and no other settings. Each init() makes a
separate form. Its settings (provenance, values, messages, fields) apply only to leads sent through its
own handle, not to the shortcuts or to other forms on the page.
The form handle#
What init() gives back.
| Member | What it is |
|---|---|
mode |
'head' (drawn) or 'headless'. |
ready |
A promise of true or false. Drawn: true once drawn; false if it couldn't load (no key, wrong key, website not allowed, campaign paused, no target). Headless: true once its settings loaded, false if not. A false comes with an onError call saying why, and a console error (except for a paused campaign). |
submit(values) |
Sends a lead and gives back a promise of a result. See What you can send. Calling it again while a send is still going gives back the same promise, so double clicks don't send twice. |
consent() |
A promise of the consent wording, or null. |
mountConsent(target) |
Draws the firm's consent block in your element. submit() then sends its answers, unless you pass your own consent. |
openChat(result, target) |
Opens the chat after the form. False when there is no chat. |
fields() |
A promise of the fields. Drawn: the fields on the form. Headless: the fields you listed in fields, with their labels and types filled in. |
on(event, listener) |
Listens for 'success' or 'error'. Gives back a function that stops listening. |
destroy() |
Drawn forms: removes the form and its listeners. |
The result#
Every send gives back a result. It never throws an error, so you don't need try/catch.
{ ok: true, status: 'received' | 'held', chatToken?: string }
{ ok: false, code: string, message: string, fields?: Record<string, string> }
received: the lead is stored.held: the lead is stored, but the campaign's rules have held it for someone to review. Treat this as a success and thank the person.chatToken: only there when the form opens a chat after sending.code: what went wrong, for your code to check. See Troubleshooting.message: a message you can show the visitor.fields: a message for each field that needs fixing, by field key (with codeinvalid). For example{ email: 'Enter a valid email address, like name@example.com.' }.
If the connection drops, there is no answer within 15 seconds, or there is a server error, the script tries once more after a short pause. It marks both tries as the same lead, so the lead is never stored twice.
The consent wording (consent())#
{
notice: string | null, // what giving their details means
opt_outs: { key, label }[], // "tick if you'd rather not" boxes
marketing: { title, text, options: { key, label }[] } | null, // one box per channel, never ticked in advance
versions: { ... } // which wording they saw, sent back automatically
}
Events and callbacks#
| Event | Fired on | event.detail |
When |
|---|---|---|---|
subcontext:leadform:ready |
window |
the Subcontext.leadform API |
Once, when the script has loaded and starts drawing forms. Fires again if the script is loaded again. |
subcontext:leadform:submitted |
the form's element (bubbles up) | the result | A drawn form was sent successfully. With a success page set, it fires just before the visitor is taken there. |
subcontext:leadform:error |
the form's element (bubbles up) | the result | A drawn form's send failed. Not fired for checks on earlier pages of a multi-page form. |
onSuccess, onError and handle.on('success' | 'error', fn) fire for every send in both modes, including a
headless send that failed the checks before sending (code: 'invalid'). They fire before the page events. If your
listener throws an error, it is caught and written to the console, so it can't break the form.
onError also hears once about a form that couldn't load. handle.on('error') and the page events are only for
sends.
The page events bubble up, so one listener on document hears every drawn form on the page. For example,
for analytics:
document.addEventListener('subcontext:leadform:submitted', (event) => {
window.dataLayer?.push({ event: 'lead_submitted', status: event.detail.status })
})
What you can send#
createLead(values) and handle.submit(values) take:
| Key | What it is | What Subcontext does with it |
|---|---|---|
first_name, last_name, name, email, phone |
Who they are. An email or a phone number is needed. | Finds the person in your CRM, or adds them. |
message |
What they wrote, up to 5,000 characters. | Kept as the lead's text, and read for useful details like any free text. |
| any other key | One of your firm's fields, or a key of your own. Keys are lowercase with underscores, start with a letter, up to 63 characters. Values can be text, a number, true/false, or a list of text (lists aren't allowed for the standard keys above). Up to 1,000 characters each, 40 keys in all. | Kept on the lead. A list is kept as one comma-separated value. |
consent |
The consent answers: { box_key: true or false }. See Consent. |
Recorded in the person's consent history. |
provenance |
Where they came from. See Where the visitor came from. | Decides which source the lead counts under in Insights. Ad clicks are reported back to Google Ads and Meta if you have connected them. |
turnstile_token or cf-turnstile-response |
Only if your form uses Cloudflare Turnstile: the token from your own Turnstile widget. | Checked with Cloudflare, not stored. |
Empty text and empty lists are left out. Spaces at the start and end of text are removed.
The chat after the form#
On the Design tab, under After it's sent, a form can Thank them, then open a chat with one of your agents. The agent must be live.
- Drawn form: the chat opens under the thank-you message. If you set a success page (
data-success-urlorsuccessUrl), the visitor is taken there instead and no chat opens. - Your own form: after a successful send, call
openChat(result, target). It gives backfalseif the form has no chat, or the send failed.targetis the element to put the chat in; without it, the chat goes at the end of the page.
const result = await Subcontext.createLead(values)
if (result.ok) await Subcontext.leadform.openChat(result, '#chat-here')
The agent greets the visitor using what they sent. If the page already has the Subcontext web chat on it, that chat is opened instead of adding a second one.
The result's chatToken links the chat to this lead and lasts two hours. openChat uses it, so you don't need
to handle it. The chat is linked to the person in your CRM only when the lead was a new person.
If your site has a content security policy, add the line for the chat (wss://subcontext.com).