Security#
- The key is public. The
lfk_...key is in your page's source. It only identifies the form, and it only works from the websites on the form's allowed list. - Getting a new key. On the Install tab, New key gives the form a new key. The old one stops working at once, so update the snippet on your website straight away.
- The website check. Every request carries the key, and Subcontext checks that the page's website (its
origin, meaning
https://plus host plus port) is on the allowed list. - No cookies or logins. The form sends no cookies and needs no secret. The only token it uses is the short-lived chat token.
- Nothing secret goes in your page. There is no secret key for the lead form. Never put an API token (for the server API) in a web page.
- Bots. The form turns away most bots without asking the visitor anything. A bot that is caught gets the same
answer as a person.
- Drawn forms have a hidden trap field that people never see.
- Drawn forms that are sent unrealistically fast (in under a second) are dropped.
- Built-in check (Install tab, Bot checks). Turn it on if spam still gets through. The form solves a
small puzzle in the background while people fill it in. Visitors don't see or click anything, it works with
screen readers, and nothing about the visitor is kept. The puzzle makes sending in bulk slow and costly for
bots.
createLeadsolves it before sending, so your own form uses it too. - Cloudflare Turnstile is supported for forms Subcontext has set it up for. Ask Subcontext if you need it.
- Links.
data-success-urlanddata-privacy-urlmust start withhttp:orhttps:. Anything else (likejavascript:) is ignored. - Privacy in logs. What visitors type is not written to Subcontext's request logs.
Content security policy#
If your website sends a Content-Security-Policy header (a list of where your pages may load things from), add these:
| Directive | Allow | Why |
|---|---|---|
script-src |
https://js.subcontext.com |
The form script (and the chat script, if the form opens a chat after sending). |
connect-src |
https://subcontext.com |
Loading the form, sending leads, the bot check and Results counts. |
connect-src |
wss://subcontext.com |
Only if the form opens a chat after sending: the chat's live connection. |
style-src |
your nonce on the script tag, or 'unsafe-inline' |
The form's styles and accent colour are added as <style> elements carrying the script tag's nonce. Or use styles: false (an accent colour still needs the nonce). |
script-src and frame-src |
https://challenges.cloudflare.com |
Only if your form uses Cloudflare Turnstile. |
A nonce is a random value your server makes for each page and puts in both the header and the tag:
<script nonce="RANDOM" src="https://js.subcontext.com/leadform-latest.js" data-key="lfk_YOUR_KEY" defer></script>
Limits#
- Sends per visitor: 10 leads in 10 minutes from one IP address. After that the visitor sees "You've sent this
a few times. Please wait a few minutes and try again." (code
rate_limited). - Bot check puzzles: 30 in 10 minutes per IP address.
- Results counts: 120 in 10 minutes per IP address.
- Values: up to 1,000 characters each, 5,000 for
message, and 40 extra keys per lead. - Waiting: a send that gets no answer in 15 seconds counts as a timeout (and is retried once).
People sharing one internet connection (an office, for example) share these limits.
Troubleshooting#
Problems are always written to the browser console, starting with Subcontext.leadform. Add
data-debug="true" to the script tag to see more.
If a drawn form can't load, it shows "This form isn't available right now. Please try again later." and the
console says the form couldn't load (<code>): <message>. If the campaign is paused, the form shows "This form
isn't taking enquiries at the moment."
Error codes#
code and message are what you get in the result (and in onError).
| Code | HTTP status | Message | What it means and what to do |
|---|---|---|---|
no_key |
No lead form key: add data-key="lfk_..." to the script tag, or pass key to init(). | No key anywhere. Check data-key on the script tag. |
|
invalid_key |
401 | Unknown lead form key. | The key is wrong, or the form was given a new key. Copy it again from the Install tab. |
form_disabled |
403 | This lead form has been turned off. | Turn the form on (on the form's page, Turn on). |
not_entitled |
403 | This account can't take leads right now. | The Subcontext subscription has lapsed. |
origin_not_allowed |
403 | This website isn't on the lead form's list of allowed websites. | Add your website exactly (https://, host, port) on the Install tab. Pages opened straight from your computer (file://) never work. Serve the page from a web server. |
campaign_paused |
409 | This form isn't taking enquiries at the moment. | Make the campaign active. |
campaign_archived |
409 | This form isn't taking enquiries at the moment. | The campaign is archived. Use a form on an active campaign. |
invalid |
422, or before sending | Please check the highlighted fields. | Look at result.fields for each field's message. Very rarely the message is "That couldn't be sent. Please try again." instead. |
challenge_failed |
422 | That didn't go through. Please send it again. | The bot check's answer was out of date or already used. The script has already tried again once. |
turnstile_failed |
422 | Please complete the check that you're not a robot, then send again. | Cloudflare Turnstile only. |
rate_limited |
429 | You've sent this a few times. Please wait a few minutes and try again. | Over 10 sends in 10 minutes from one IP address. Wait and try again. |
network |
We couldn't send that. Check your connection and try again. | No answer at all. The visitor is offline, or the request was blocked by your content security policy or an ad blocker. Already retried once. | |
timeout |
We couldn't send that. Check your connection and try again. | No answer in 15 seconds. Already retried once. | |
server |
5xx | Something went wrong on our side. Please try again in a moment. | Already retried once. If it keeps happening, contact hello@subcontext.com. |
no_target |
head mode needs a target element: init({ mode: "head", target: "#my-form" }) | init({ mode: 'head' }) with no target, or a target that isn't on the page yet. Nothing is drawn. |
By symptom#
- Nothing appears and there's no error. Check that the element has the attribute
data-subcontext-leadform. If you useinit(), pass atargetthat is already on the page wheninitruns. - The form appears on first load but not after moving around a single-page app. Call
Subcontext.leadform.mountAll()after adding the element. Subcontext is undefined. Your code ran before the script loaded. Use thewhenLeadformhelper in Set it up from JavaScript.origin_not_allowedon one version of your site.https://example.comandhttps://www.example.comare different websites, and so arehttp://localhost:4100andhttp://127.0.0.1:4100. Add each one you use.- The form says "This form isn't taking enquiries at the moment." The campaign is paused or archived. Make it active.
- Settings from
init()are ignored bySubcontext.createLead. The shortcut has no settings. Callsubmiton the handleinit()gave you. invalidfrom your own form with nothing obviously wrong. Subcontext checks designed fields you send (their choices, lengths, formats, and that a checkbox field gets a clear yes or no). Look atresult.fields.- Some of your own consent answers aren't recorded. Only the box keys from
consent()count, only boxes the form shows, and only clear yes or no words. Unticked boxes are never sent. - The chat doesn't open. The form needs a live agent under After it's sent, the send must have succeeded,
a success page can't be set, and your content security policy must allow
wss://subcontext.com. - The lead arrived but nobody got an email. Set who gets one on the Design tab (Email each enquiry to, up to 5 addresses). A lead that was retried is the same lead, so it doesn't send a second email.
- Nothing shows on the Results tab. Only drawn forms are counted, and not if
data-analytics="false"is set.
Need help?#
Email hello@subcontext.com.