Survey Widget
Render multi-question, client-triggered surveys with banner, modal, or inline display and per-survey trigger rules
Overview
The Survey widget renders multi-question surveys inside your product — open text, scale, NPS, single/multi choice, boolean — and only fires when one of the survey's triggers matches (URL pattern, custom event, or time-on-page). The widget pulls live survey definitions from the server on load, so customers paste the snippet once and you author surveys from the dashboard.
This is the in-product counterpart to the email magic-link survey landing page. Surveys triggered by lifecycle events (trial expiry, subscription cancelled, etc.) reach users via email; this widget covers the client-side trigger path.
Try every display mode at the live demo. The demo bypasses the cooldown so you can re-fire the survey indefinitely.
Surveys are available on the Pro plan. Non-Pro projects receive an empty config and the widget renders nothing.
Three display modes are supported:
- Banner — bottom slide-in (default)
- Modal — centered overlay with backdrop
- Inline — mounted inside a container you specify
Quick Start
Drop the script anywhere on your page:
The widget fetches live surveys from GET /api/v1/projects/{project_key}/surveys-config. It only fires when all of these hold:
- A survey is in Live status with a
ClientSidetrigger. - A trigger rule matches (URL pattern, custom event, or time-on-page).
- The visitor is past the survey's cooldown window (default 90 days).
- The per-survey sample-rate roll passes (default 100%).
- The org is on the Pro plan.
Configuration Options
Required Attributes
data-project-keystringrequiredYour unique project identifier from the SeggWat dashboard.
Optional Attributes
data-modestringdefault:"banner"Display mode. Valid values: banner, modal, inline.
banner— fixed bottom slide-in, dismissiblemodal— centered overlay with backdropinline— mounted intodata-container, no backdrop
data-mode="modal"data-containerstringCSS selector for the container to mount the widget into. Required when data-mode="inline".
data-container="#survey-slot"data-survey-idstringWhen set, only the survey with this id is considered and its triggers are bypassed — the widget shows it as soon as it loads (still subject to cooldown / sample rate). Useful for embedding a specific survey on a static landing page.
data-cooldown-daysnumberOverride the per-survey cooldown for all surveys returned by this embed. 0 disables the cooldown entirely. Intended for dev / QA.
data-button-colorstringdefault:"#2563eb"Hex color used for primary buttons, selected states, and progress bar. Hex format only (e.g. #10b981).
data-languagestringdefault:"auto-detect"Language code for the widget UI chrome (Step labels, Next/Back/Submit/Skip). Supported: en, de, sv, fr. Falls back to English when unsupported. Per-survey copy overrides authored in the dashboard take precedence over these defaults.
data-show-powered-bybooleandefault:"true"Show or hide the "Powered by SeggWat" footer. Set to false, 0, or no to hide.
data-versionstringTrack responses against specific application versions (e.g. 1.2.3, v2.0.0-beta). Stored on linked Rating / Feedback / SurveyResponse rows.
data-api-urlstringdefault:"auto-detect"Override the API endpoint. Useful for self-hosted or staging environments.
Triggers (configured per survey)
Each survey has its own trigger configuration. The widget evaluates triggers on every survey returned by the config endpoint and shows the first one that matches (one survey at a time).
URL path patterns
Glob-style patterns matched against window.location.pathname:
*matches any sequence within a single path segment**matches across segments
/pricing
/billing/**
/account/*/settingsCustom event names
Names the widget listens for. Fire them from your own code at meaningful milestones:
window..;
window..;Time-on-page
Show the survey N seconds after the widget initializes if no earlier trigger fires.
Question Kinds
The widget renders any combination of these question shapes:
| Kind | Input | Notes |
|---|---|---|
open_text |
textarea | Honors max_chars and placeholder |
nps |
0–10 button row | Shows "Not likely" / "Very likely" labels |
scale |
min–max button row | Custom low_label / high_label shown beneath |
single_choice |
radio list | One option from a list |
multi_choice |
checkbox list | Multiple, with optional min / max cardinality |
boolean |
two-button toggle | Custom true_label / false_label |
Choice options can set allow_other_text: true. When the user picks that option, a free-text "Other:" input appears beneath the choices and is submitted as other_text on the answer.
Each question can also be marked required. The Next button stays disabled until the answer is non-empty; non-required questions show a Skip button.
Thank-You Actions
After the last question, the widget shows the server-resolved thank-you action:
- Message — headline + optional body text
- DiscountOffer — headline, coupon code (monospace, dashed border), CTA button. The widget stays open longer (12s) before auto-hiding to give the user time to copy the coupon
- Reactivate — headline + reactivate CTA (typically for win-back flows)
- Custom — arbitrary HTML (only org admins can author surveys, so this is trusted to the same extent as the survey definition)
Examples
Banner (default)
Modal centered overlay
Inline inside a specific container
Force a specific survey, bypass its triggers
Dev / QA (bypass cooldown across all surveys)
JavaScript API
The widget exposes window.SeggwatSurvey for programmatic control:
Identify the user
SeggwatSurvey.;User IDs are opaque — max 255 chars, alphanumeric + hyphens + underscores. Never send PII.
Fire a custom event trigger
SeggwatSurvey.;Dispatches a seggwat:survey:<name> event on window. Any survey whose event_names contains that name will be shown (subject to cooldown + sample rate).
Show a specific survey manually
SeggwatSurvey.; // respects cooldown
SeggwatSurvey.; // bypass cooldown (dev only)
SeggwatSurvey.;Inspect state
SeggwatSurvey.; // boolean — inside cooldown window?
SeggwatSurvey.; // read-only snapshot of live surveysReset local state
Clears this visitor's per-survey cooldown / sample-roll stored in localStorage. Does not delete server-side data.
SeggwatSurvey.;How It Works
- Script loads, auto-detects its base URL, bootstraps
seggwat-core.jsif not already present. - Fetches the live surveys list from
GET /api/v1/projects/{project_key}/surveys-config. - For each survey, evaluates triggers in order: URL pattern match (immediate), then arms time-on-page timer + listens for event names.
- On match: checks
localStoragecooldown + sticky sample-rate roll. If both pass, displays the survey in the configured mode. - User steps through one question per screen (Prev / Next), with required validation.
- On submit, POSTs to
/api/v1/surveys/submitwithproject_key+survey_id+ answers. - Server atomically writes: one
Ratingper scale / NPS / boolean answer, one combinedFeedbackrow for all open-text answers (taggedsource: Survey), and oneSurveyResponsesidecar linking everything. - Widget renders the resolved
thank_youaction and auto-hides after a short grace.
API Endpoints
Submit a response
POST /api/v1/surveys/submitPayload (client-triggered path):
Response (201 Created):
feedback_id is only present when at least one open-text answer was given. thank_you is null if the visitor didn't qualify (e.g. partial submission with show_to: completers).
Fetch the widget config
GET /api/v1/projects/{project_key}/surveys-configPublic, no auth. Returns the live, ClientSide-triggered surveys for the project plus their triggers, cooldown, sample rate, copy overrides, and thank-you actions. Returns an empty list when the project is not on Pro.
Track impressions and dismissals
POST /api/v1/surveys/eventsPublic, no auth. Batches shown / dismissed events from the widget so the dashboard can show response and dismissal rates next to raw submission counts. The widget fires these automatically — you don't need to call this endpoint yourself.
Payload:
Response (202 Accepted):
The third event kind, submitted, is incremented server-side from POST /surveys/submit. Don't send it from the widget.
Privacy and cookies
The Survey widget is built to keep customer pages free of consent-banner surface area. Concretely:
- No cookies are set. Per-survey cooldown / sample-rate state is stored in
localStorage, which under EU guidance is considered "strictly necessary" for delivering the feature the visitor is interacting with. - No tracking identifiers leave the browser. The widget never sends an IP, user agent, fingerprint, or anonymous visitor ID as part of an event. Server logs naturally capture IP for short-lived rate limiting, but that data is never persisted into the analytics counters.
- Event counters are pure aggregates. Impressions and dismissals are stored only as
(project, survey, day, event_kind, count)rows. There is no way to reconstruct who saw which survey from that data, which means the counters are not personal data under the GDPR. submitted_byis opt-in. If you callSeggwatSurvey.setUser("...")or passdata-user-id, that identifier rides along on the submitted response. It does not appear on impression / dismissal events.
If your privacy policy already covers SeggWat for feedback / ratings, you typically do not need to add a separate disclosure for survey event counters — the same "non-identifying aggregate analytics" framing applies.
Data Model
Each submission writes up to three correlated row types:
- Rating — one row per scale / NPS / boolean answer, feeding the existing aggregation pipelines (
type: "scale" | "nps" | "helpful"). - Feedback — a single combined row for all open-text answers in the submission, formatted as
**prompt**\nanswer\n\n**prompt**\nanswer. Taggedsource: "Survey". Flows through the regular triage queue. - SurveyResponse sidecar — links every Rating + Feedback row, denormalizes every answer (including its
prompt_snapshot), and carriescompleted,locale,path,version,submitted_by.
Question identity is preserved via stable string ids on each SurveyQuestion. Reordering, renaming, or rewording questions in the dashboard later doesn't corrupt analytics — historical responses keep referencing the original question_id and carry their own prompt snapshot.
Best Practices
Use Cases
Activation Pulse
Fire SeggwatSurvey.trigger("activation_complete") after the user completes onboarding. A 3-question survey (clarity / value / blockers) captures impressions while they're fresh.
Pricing-Page Intent
URL trigger on /pricing. Ask what they're evaluating and the biggest blocker. Pair with a DiscountOffer thank-you on completion.
Feature Feedback
Custom-event trigger after the user uses a new feature N times. Tight, focused: clarity / usefulness / what's missing.
Pre-Cancel Save
URL trigger on /account/cancel. Open-text "what's not working" + a reactivate CTA in the thank-you. Catches churn intent before it lands.
