Integration guide

Put a survey on your own site.

Three options, ordered from least to most code. Pick the one that matches who’s installing it.

1

Public link

Anyone — no code.

Every published survey has a public URL. Send it via email, drop it in a Slack message, link it from a button — wherever you can put a URL, you can put a Questaion survey.

Example
https://questaion.com/s/your-survey-slug
2

Embed widget

Marketers, designers — one line of HTML.

Our 5 KB JS widget renders the survey directly on your page. Three modes — pick the one that fits the moment.

Floating button (popup)

A small button sticks to the bottom-right of every page. Visitors tap it whenever they have feedback to share.

Snippet
<script
  src="https://questaion.com/embed.js"
  data-survey="your-slug"
  data-mode="popup"
  data-label="Feedback"
  async></script>

Slide-out (timed)

Appears from the corner after a delay (default 5s). Great for post-purchase or after the user has spent time on a page. Dismissed surveys are remembered per session so visitors aren’t nagged.

Snippet
<script
  src="https://questaion.com/embed.js"
  data-survey="your-slug"
  data-mode="slideout"
  data-delay="6000"
  async></script>

Inline

Renders the conversational survey directly inside a page section — ideal for landing pages, contact pages, or in a help center.

Snippet
<div data-questaion-survey="your-slug"></div>
<script src="https://questaion.com/embed.js" async></script>

Attribution metadata

Pass arbitrary context with data-q-* attributes. Each one becomes a query param so the response carries the context — handy when you want to join it back to a booking, an order, or a user record.

With attribution
<script
  src="https://questaion.com/embed.js"
  data-survey="post-stay"
  data-mode="popup"
  data-q-booking-id="bk_8721"
  data-q-property="cabin-7"
  data-email="guest@example.com"
  async></script>
3

API + webhooks

Developers — full programmatic control.

For server-side use: mint per-customer survey links from a backend job, receive signed webhook events when responses come in, pull aggregates for your own admin dashboard.

1. Get an API key

Workspace settings → Integrations → New key. Format: qsk_live_… (shown once, stored hashed). Treat it like a password.

⚠️ Watch out for trailing newlines when scripting env-var setup

If you set QUESTAION_API_KEY or QUESTAION_SURVEY_ID via shell pipes, use printf '%s' "$value", not echo. echo appends a \n that’s invisible in dashboards (Vercel, Heroku, Fly) but corrupts every outgoing request — the slug becomes …/post-stay\n/summary and the header becomes Bearer qsk_live_…\n. Both look fine in the dashboard. Both fail with survey not found or invalid api key. If you hit that error and the value looks right, this is almost always the cause.

2. Mint a per-customer link

cURL
curl -X POST https://questaion.com/api/v1/external/invitations \
  -H "Authorization: Bearer qsk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "survey_slug": "post-stay",
    "email": "guest@example.com",
    "metadata": { "booking_id": "bk_8721", "checkin": "2024-08-12" }
  }'

# → { "url": "https://questaion.com/s/post-stay?inv=…", "token": "…" }

Email that url to your customer. When they complete the survey, the response is linked back to your metadata automatically.

3. Receive signed webhooks

Workspace settings → Integrations → Add webhook. Questaion POSTs JSON to your URL when a response is completed, signed with HMAC-SHA256 in X-Questaion-Signature (Stripe-compatible format).

Verifying signatures (Node)
import crypto from "node:crypto";

function verify(secret, body, header, toleranceS = 300) {
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=", 2)));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now()/1000 - t) > toleranceS) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${body}`)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}

4. Read aggregates

Summary
curl -H "Authorization: Bearer qsk_live_..." \
  https://questaion.com/api/v1/external/surveys/{id}/summary

# → { response_count, completed, completion_rate, avg_duration_seconds, nps }