Back to home

The Feedforward Widget

A <30KB feedback widget that drops into any site.

What it is

The Feedforward widget is a tiny, self-contained survey surface built with Preact. It renders inside a closed Shadow DOM, so the host page’s styles can’t leak in and the widget’s styles can’t leak out — it looks the same on every site. It ships no external fonts and no heavy dependencies, so it stays well under the 30KB budget.

  • Preact runtime, no framework bloat on the host page
  • Closed Shadow DOM for full style isolation
  • System font stack — zero web-font requests
  • Async, non-blocking load that never delays your page

Installation

Drop this one-line script tag anywhere in your page and you’re live:

<script
  src="https://widget.getfeedforward.com/v1.js"
  data-embed-key="pk_live_..."
  data-survey-id="srv_..."
  async
></script>

Find your embed key and survey id in the dashboard under Settings → Install.

Configuration

Customize behavior with data-* attributes on the script tag:

  • data-embed-key — your public embed key (required)
  • data-survey-id — which survey to show (required)
  • data-locale — language override, e.g. en, fr, de
  • data-api — override the API base URL (self-hosting only)

Programmatic API

When you need control, the widget exposes a small global. Trigger it from a button, a route change, or after a purchase:

// Available on window once the script loads
Feedforward.show();              // open the survey
Feedforward.hide();              // dismiss it
Feedforward.identify("user_42"); // attach a known user id

Security

Because the widget runs on untrusted host sites, the server trusts nothing it sends. Sessions are authenticated with HMAC-signed embed tokens (short-lived, replay-protected), and every cross-frame message is validated against its origin before it is ever processed.

  • HMAC-signed, 1-hour embed tokens with per-token replay protection
  • Origin-validated postMessage on every listener
  • Visitor IPs are never stored raw — only hashed

Performance

Every kilobyte is accounted for. The bundle is under 30KB gzipped and is enforced in CI on every commit — it can’t silently grow.

  • <30KB gzipped, hard-gated in CI
  • No eval and no dynamic Function()
  • Loads async — zero impact on your page’s first paint