JavaScript API and events

The lead form's JavaScript calls, form handle, results, events and callbacks, what a lead can carry, and the chat after the form.

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.

ts
{ 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 code invalid). 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.

ts
{
  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:

js
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-url or successUrl), the visitor is taken there instead and no chat opens.
  • Your own form: after a successful send, call openChat(result, target). It gives back false if the form has no chat, or the send failed. target is the element to put the chat in; without it, the chat goes at the end of the page.
js
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).

Stuck, or something missing?

Get in touch and a person on the team will help.

Contact us