Your API key never reaches the browser in either method.
Before you start
- A survey that is
active, with in-app collection turned on and at least one allowed origin. You can set this in the survey’s in-app settings, or withsettings.inAppon the Surveys API. - The GainTrace person who is signed in to your product. A signed token matches them by its
subclaim. A backend session uses thecontactIdandaccountIdyou send.
Allowed origins
settings.inApp.allowedOrigins lists the exact origins that may show the survey, such as https://app.example.com. No paths, no trailing slash, no wildcards. Use HTTPS. For development, http://localhost and http://127.0.0.1 are also accepted. You can list up to 20.
Install with a signed token
Your server signs a token for the signed-in person. Your page passes it to the SDK, and GainTrace checks the signature and decides whether to show the survey. Your backend makes no call to GainTrace per session.Step 1: create an identity key
In Surveys, open Sending and identity and create an identity key. You need the permission to manage API keys, which admins have by default.- GainTrace shows the secret once, when you create the key. Store it as a server secret.
- The key has an id that starts with
gtk_. You put it in the token header askid. - A workspace can have 2 active keys at a time. Revoked keys stay listed for 90 days.
- Revoking a key stops tokens signed with it from verifying at once.
403.
Step 2: sign a token on your server
Sign a JWT with HS256, using the key’s secret as the HMAC secret and the key id askid. GainTrace accepts only HS256. Tokens with any other algorithm are refused.
A token can be at most 4,096 characters.
401.
Step 3: identify and show
Load the script, hand the token to the SDK, and ask for the survey.GaintraceSurveys.reset() when the person signs out. It forgets the token, stops any pending checks and removes an open survey without recording a dismissal.
show({ survey }) returns a promise. It resolves to the open survey, or to null when the person doesn’t qualify yet. If the survey is waiting on a page rule or a trigger, the SDK keeps checking, as described in Targeting.
The delay comes from the survey’s settings (
delayMs). The delay option doesn’t apply to this install.
What the browser sends
The SDK callsPOST /api/survey/{id}/identify on the address the script was loaded from. It is a browser endpoint: it needs an Origin header and only answers origins on the survey’s allowed list. It allows 120 requests a minute for each network address.
A request that works returns 200. If the person qualifies you get eligible: true and an embedUrl. If not, you get eligible: false and a reason (see Who sees the survey). The reason unknown_person means the token’s sub, email and company didn’t match anyone in your workspace.
show rejects with an Error that carries the status as status.
Install with a backend session
Your server asks GainTrace for a session for the signed-in person. Your page loads the SDK and shows the URL the session returned.Step 1: get a session from your backend
Create a route in your application that makes this request with the signed-in person’s ids, then returns thedata object to the browser. You need a secret server key (gt_live_) with the write:surveys scope.
parentOrigin is the exact origin of the page that will show the survey. It must be on the allowed list. contactId and accountId are the GainTrace ids of the person and their Company.
context is optional. Send it when the survey has page rules or a trigger. url is the page the person is on, without the # fragment. It must be on parentOrigin, or the request returns 422. You can also send event and dwellSeconds, described in Targeting. The body can carry a subject too, to tie the answer to a record.
The response
When the person qualifies, you get a signed URL:embedUrl is valid for one hour. Request a session when you intend to show the survey, not ahead of time.
When the person doesn’t qualify, you get eligible: false and a reason. See Who sees the survey.
Step 2: add the script to your page
Load the script, ask your backend for the session, and show the survey when it’s eligible.GaintraceSurveys.show after sign-in. Don’t call GaintraceSurveys.destroy() on sign-out: it acts like Close survey, so a survey that has loaded and not been answered counts as a dismissal. A dismissal suppresses in-app surveys for that person for the workspace’s dismissedRecontactDays.
Options
GaintraceSurveys.show(options) takes one object.
Placement
Set the default insettings.inApp.position, or override it per call with the position option.
On a narrow screen every position shrinks to the window width minus 32px. A banner or corner card lets people answer when it suits them. Use
center when you need an answer before they continue.
Return value
show() gives you the open survey: an object with url, destroy() and snooze(). With a backend session it returns that object directly. With a signed token it returns a promise that resolves to that object, or to null.
- Calling
show()again with the same URL while a survey is open returns the open one. - Calling it with a different URL closes the open survey and starts the new one.
GaintraceSurveys.destroy()closes the open survey, if any.destroy(), andshow()with a different URL, act like Close survey. Once the survey has loaded and the person hasn’t answered, it counts as a dismissal. Intestmode it doesn’t.
show() throws an error with a message when something is wrong. For a backend session, the URL must be HTTPS (or localhost), must be the embed URL your backend returned, and the session must match this page’s origin and the display mode. The page’s origin must equal the parentOrigin you sent. If it doesn’t, move the script to an allowed origin or send the right parentOrigin.
Snooze and dismiss
The survey header has two buttons.- Close survey counts as a dismissal. The person won’t see an in-app survey again until the
dismissedRecontactDaysrest period ends, 7 days by default. - Ask me later hides the survey, and it can show again after 24 hours. The snooze applies to that survey and that person. After someone answers, the button goes away.
destroy() is Close survey and snooze() is Ask me later. Both only record something once the survey has loaded and the person hasn’t answered. In test mode they just remove the survey. Escape closes the survey the same way as Close survey.
Targeting
Targeting decides who sees the survey, on which pages, and when. Set it on the survey insettings.inApp.targeting, or in the survey’s in-app settings under Targeting. It has three parts, and all of them are optional.
PATCH /surveys/{id} replaces the whole inApp object when you send it, so send every inApp field, not just targeting.
Who
audience is either a saved segment, { "segmentId": "..." }, or rules, { "rules": { "company": {...}, "person": {...} } }. Both rule groups are required. Each group has a logic of and or or and up to 20 rules. A rule is { field, operator, value }. An empty group matches everyone.
People rules can use these fields: email, name, role, contactRole, isChampion, lastSeenAt, engagementScore and createdAt. They can also use traits.<name> for a trait on the person. Any other People field returns 422. Company rules use Company data-model fields.
not_targeted.
Where
pages limits the survey to pages that match.
includelists the pages that can show it. A page needs to match any one of them. With noincluderules, every page can show it.excludelists pages that never show it. An excluded page wins over an included one.- Each rule has a
matchofequals,containsorstarts_withand avalue. Values are matched against the page’s path and query string, such as/billingor/reports?view=weekly. They can’t contain spaces or#, and are at most 512 characters. Each list holds up to 20 rules. - Matching is case-sensitive. It ignores the fragment and a trailing slash.
equalswith a value that has no?compares only the path, so/reportsmatches/reports?view=weekly. Add the query to the value to match it exactly.
page_not_targeted.
When
trigger decides the moment.
Event names start with a letter or number and then use letters, numbers, dots, dashes, colons and underscores, up to 64 characters.
eligible: false with reason: "awaiting_trigger" and an awaiting object that lists the events or the timeOnPageSeconds it waits for.
How the SDK waits
With the signed token install, the SDK does the waiting for you, in memory only. Nothing is stored.- When the answer is
page_not_targeted, it asks again whenever the URL’s path or query changes, including single-page app navigation. - When the answer is
awaiting_triggerfor time on the page, it counts the seconds the tab is visible on the current URL and asks again when the count reaches the threshold. The count restarts when the path or query changes. A change to only the#fragment doesn’t restart it. GaintraceSurveys.track(name)only does something for a survey that is already waiting for that exact event. Callshowfirst, thentrack.- Each check is a request to the identify endpoint. The SDK never throws from these background checks. If GainTrace answers
401,403,404or422, it stops checking that survey. - Reloading the page starts again.
context.url for page rules, and context.event or context.dwellSeconds to satisfy a trigger, each time it requests a session.
Test mode (mode: "test") ignores targeting, so you can check the install on any page.
Order of checks
GainTrace checks that the survey is active, then the origin, the cooldown and recontact rules, dismissals, sampling and snooze. Then it checks targeting: page, audience, trigger. Only after targeting passes does it checkalready_seen, minimum_age, minimum_sessions and display_limit. A visit counts toward minSessions only after targeting passes.
How the survey behaves
- Focus. The centered modal traps focus and returns it to the element that had it before.
- Reduced motion. The survey slides up over 200ms. If the person prefers reduced motion, it fades in instead.
- Size. The survey resizes itself to fit its content, between 100px and 4,000px tall. Buttons are at least 44px tall.
- Test mode. With
mode: "test"you see a preview. Nothing is recorded, and closing it just removes it.
Language
In-app surveys show the survey in the language you ask for, using the translations on the survey. GainTrace picks the language in this order:- The language you pass. With a signed token, pass
langtoGaintraceSurveys.show({ survey, lang }). With a backend session, sendlangin the session request. It wins over everything else. - The person’s
localetrait, then theirlanguagetrait. - The browser’s language.
- The survey’s default language.
lang must be a language code such as es or pt-BR, or show throws “Pass a language code such as es or pt-BR.”
The widget’s own buttons (Ask me later and Close survey) and its screen reader announcement switch to that language too, and right-to-left languages (Arabic, Hebrew, Persian and Urdu) mirror the widget and the survey.
Who sees the survey
GainTrace applies these rules when a session is requested. A person who fails one getseligible: false with the reason below, and your page shows nothing.
A person sees each survey at most once per UTC day. Sessions count one per UTC day. In
test mode these rules are skipped, but the survey must still be active, in-app collection must be on, and the origin must be allowed.
The frequency rule is set for the whole workspace. See Surveys API.
Security and CSP
- The survey runs in an iframe from the GainTrace address. GainTrace only lets the origin you named in the session frame it. The iframe is sandboxed with
allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandboxand sends no referrer. Popups are allowed so a thank-you button can open a link in a new tab. The survey can’t navigate your page. - The script only accepts messages from that iframe.
- If your site sets a Content Security Policy, allow the GainTrace origin in
script-srcforsurveys.jsand inframe-srcfor the survey. For the signed token install, also allow it inconnect-src, because the page calls the identify endpoint. - The signed token install uses the address you load
surveys.jsfrom for the identify call and for the survey frame. The backend session returns embed URLs on your custom survey domain while the white-label add-on is active, so with that install list the custom domain inframe-srcas well. - The script adds a style element and inline styles to your page. If your
style-srcblocks inline styles, the survey won’t render correctly. - The script doesn’t write to your page’s storage. The signed token stays in memory.