What is public and what is secret#
- The quote form's key is public. It sits in your page's source. It only works from the websites you listed, only while the form is
turned on, and only for the form's own product and journey. To replace
it, press New key on the quote form's page. Tokens from a key last one
hour, and the form gets new ones as needed.
- The API application's client secret is secret. Keep it on your server.
With
getToken, only the short-lived token reaches the browser, and it only
works from the API application's allowed origins. If a secret leaks, rotate it
on the API application's page.
- Each application's share token. A token from the key (or from
getToken) is one anyone can get, so it can't reach an application on its
own. The form keeps the share token of the application it started and sends
it in an X-Share-Token header with every request about that application.
Subcontext answers any other request about an existing application with
404. A link that opens a quote carries a signed token for its
application, never the quote id alone.
- The live connection. The form never puts the application's private link
token in an address. It swaps it for a short-lived ticket first.
- Rate limits. Subcontext limits how many requests one IP address can make
in a short time, including token requests. Over the limit, requests are
refused with status 429.
- Payments are taken by Stripe in Stripe's own secure fields. Card and bank
details never reach your page or Subcontext.
Content security policy#
A content security policy (CSP) is a header some websites send to control what
a page may load. If your site has one, add these:
| Directive |
Add |
Why |
script-src |
https://js.subcontext.com |
The quote form script (and the webchat or form guide, if you use them). |
connect-src |
https://subcontext.com wss://subcontext.com |
Talking to Subcontext, and the live connection (wss:// is a secure WebSocket). |
style-src |
'unsafe-inline' |
The form adds its stylesheet as a <style> tag and uses some inline styles (including each form's colours). |
script-src |
https://js.stripe.com |
Stripe's script. Only if the journey takes payment. |
connect-src |
https://api.stripe.com |
Only if the journey takes payment. |
frame-src |
https://js.stripe.com https://hooks.stripe.com https://checkout.stripe.com |
Stripe's card fields, the bank's card check (3-D Secure) and the Direct Debit setup. Only if the journey takes payment. |
font-src, style-src |
your font hosts |
If you load fonts for fontFamily or displayFont. |
Troubleshooting#
First, turn on debug: true to see detailed logs in the browser console.
Errors and warnings are always logged, starting [Subcontext.Quote]. When
something goes badly wrong, the form shows the message (with its code in
brackets, if there is one) and a "Try again" button, and calls onError.
Nothing appears#
| What you see |
What to do |
Console: a data-subcontext-quote element needs data-key, data-product and data-journey |
Add the missing attribute to the placeholder. |
Error: Subcontext.Quote.init: missing required option 'container' (or productSlug, journeySlug) |
Add the missing option. |
Error: Subcontext.Quote.init: missing required option 'getToken' (or 'key') |
Give key or getToken. |
Error: Subcontext.Quote.init: container not found |
The selector matched nothing when init() ran. Run it after the element is on the page. |
| Nothing at all, no errors |
Check the script loaded (your CSP may block it), and that data-base-url or apiBase, if you set one, is https://subcontext.com. |
| Message |
Code (status) |
What to do |
Unknown quote form key. |
invalid_key (401) |
The key is wrong, was replaced with a new one, or the quote form was removed. Copy the current code from the quote form's page. |
This quote form has been turned off. |
form_disabled (403) |
Press Turn on on the quote form's page. |
This account can't take quotes right now. |
not_entitled (403) |
Your organisation's subscription has lapsed. Ask Subcontext. |
This website isn't on the quote form's list of allowed websites. |
origin_not_allowed (403) |
Add this page's exact origin under Websites that may use it. Remember www. and non-www. are different. |
This quote isn't available right now. |
not_available (409) |
The product or journey isn't live. Ask Subcontext if you're not sure why. |
The quote form couldn't start (429). |
(429) |
Too many token requests from this IP address. Wait a few minutes. |
Resource not found |
not_found |
The product or journey slug is wrong for your organisation, or the product has nothing published yet. |
The request took too long to complete: please check your connection and try again. |
timeout |
No answer within 20 seconds. Check the connection and try again. |
We couldn't generate a quote with the details provided. |
rate_unavailable |
The product has no price for these answers. |
Errors with your own server (getToken)#
| Message |
What to do |
origin 'https://...' is not allowlisted for this OAuth application (forbidden) |
Add the page's origin to the API application's allowed origins. (With a key, this means the quote form's websites changed after the page loaded. Reload the page.) |
this OAuth application is server-only and may not be used from a browser context (forbidden) |
The API application has no allowed origins. Add your website. |
OAuth token is not valid for this resource (forbidden) |
Your token request didn't ask for resource=https://subcontext.com/api/v1. |
From the token request: The requested resource must be https://subcontext.com/api/v1. (invalid_target) |
Your token request is missing resource, or it has the wrong value. |
getToken() must return a string or { access_token, expires_in } |
Make getToken resolve to the token string or that object. |
Other problems#
| What you see |
What to do |
this quote form's token starts only its own quote (forbidden) |
With a key, productSlug and journeySlug must be the quote form's own. The first page shows, but saving it fails. Copy them from the quote form's page. |
Banner: We couldn't save your last answers: retrying… |
Saving a page failed and the form is trying again. Check the browser's network tab for the failing request. |
Console: cable subscription rejected: ticket invalid or expired, or the WebSocket keeps reconnecting |
The live connection was refused. Check your CSP allows wss://subcontext.com. The form still works without live updates; only the chat's live answers are missing. |
Console: webchat not loaded: ... |
The webchat option didn't have supported: true. |
Console: formGuide skipped: agentId missing ... |
Give an agentId in formGuide or at the top level. |
Need help?#
Email Subcontext at hello@subcontext.com.