Surveys & full-page forms
Multi-question popups shown by the tag, and hosted full-page forms shared by link — built from templates in the dashboard.
A survey is an ordered list of questions — NPS, rating, single/multiple choice, short/long text, email, number — with optional welcome and thank-you screens and a per-survey theme. Build one in Dashboard → Surveys from a template (post-purchase NPS, CSAT, exit-intent, email capture, quick poll …), from scratch, or by describing it — the AI generator turns a sentence like "post-purchase NPS for the new skincare line with an open follow-up" into a ready-to-edit draft.
One setting decides how it reaches the visitor:
| Delivery | How it works |
|---|---|
| Modal / popup | Injected on your site by the tag — corner popup (popup_br/popup_bl) or centered modal. |
| Full page | A hosted page at /f/<siteId>/<key> on the dashboard host — share the link in emails, DMs, QR codes. Nothing is injected. |
Custom form domain
Hosted forms can live on your own hostname. In the survey editor's hosted-form card, set a custom form domain (site-wide) and add a DNS-only CNAME:
| Type | Name | Value |
|---|---|---|
| CNAME | forms | connect.pixelfox.app |
The TLS certificate issues automatically on the first request (same gate as
first-party tracking domains), and every active form becomes
https://forms.yourstore.com/<key>.
When a modal survey shows
All of these gates must pass, in order:
- Status — only
activesurveys ship to the tag. - Trigger — one of: a pageview whose URL matches
target_url_pattern(substring, or regex with a~prefix); a tracked event matchingtarget_event; a trigger rule targeting the survey; or an explicitsignals.showSurvey("key")call. - Frequency —
once_visitor(persisted),once_session, oralways. - Rollout —
rollout_percentageuses a sticky hash ofdistinct_id:survey_id, so the same visitor stays in or out. - Schedule — optional
starts_at/ends_atwindow. Outside it the survey (and its hosted form) stops shipping even while status is active. - Delay —
delay_msafter the trigger fires.
Size: the engage bundle is lazy
Survey and promo rendering lives in a separate signals-engage.min.js bundle.
The core tag only calls GET /v1/engage at boot; the bundle is injected only
when that returns something active, so sites without surveys or promos load
zero extra bytes.
Responses
Answers POST to /v1/surveys/respond keyed by question id. The backend
normalizes them per question type, marks the response completed when every
required question was answered, and captures the email when an email question
validates. Results — NPS score, per-option histograms, CSV export — live at
Dashboard → Surveys → Responses. Hosted-form submissions carry
metadata.channel = "hosted_form"; widget submissions carry "widget".
SDK API
signals.showSurvey("post-purchase-nps"); // → boolean (found?)
signals.dismissSurvey();
signals.onSurveys((surveys) => { /* active surveys loaded */ });