Widget SDK
The Signalpad widget exposes a global window.signalpad object after the script loads. All methods are safe to call before the widget finishes initialising — they queue internally and flush once the widget is ready.
Installation
Embed the script in your HTML. One tag, no bundler required.
<script src="https://signalpad.app/widget.js" data-project="your_key_here" data-api="https://signalpad.app" data-consent="required" ></script>
| Attribute | Type | Description |
|---|---|---|
| data-projectrequired | string | Your project key. Found in Dashboard → Settings → Widget. |
| data-apioptional | string | Override the API base URL. Defaults to https://signalpad.app. Useful for self-hosted instances or local development. |
| data-consentoptional | string | Set to required for GDPR-style consent gating: the widget collects nothing until you call signalpad.consent("granted"). See Privacy & consent. Omit for default behaviour. |
| data-environmentoptional | string | Tag events with an environment (e.g. production, staging) so you can filter analytics and keep test traffic out of your real numbers. |
signalpad.identify(userId, attributes?)
Associates the current session with a known user. Call this after your auth flow resolves. Attributes are stored against the user and used by the targeting engine to decide which updates to show.
| Parameter | Type | Description |
|---|---|---|
| userId | string | Your internal user ID. Must be stable across sessions. |
| attributes | object | Optional. Any JSON-serialisable key-value pairs — plan, role, company, created_at, etc. Used for advanced targeting. |
// Basic — user ID only
signalpad.identify("usr_4821");
// With attributes for targeting
signalpad.identify("usr_4821", {
plan: "pro",
role: "admin",
company: "SignalPad",
created_at: "2024-06-15",
beta: true,
});identify() after track(), previous anonymous events are not retroactively merged. Call identify() as early as possible in your app lifecycle.signalpad.track(eventName, properties?)
Records a product event. Events auto-register the first time they arrive — no dashboard setup needed. They show up under Events, and you can link any event to an update as its success metric to measure adoption. Calls are batched and sent efficiently in the background.
| Parameter | Type | Description |
|---|---|---|
| eventName | string | A stable event key. Convention: object.action, past tense (e.g. export.completed). |
| properties | object | Optional. Extra context stored with the event and available as breakdowns. Sensitive keys (email, password, token…) are stripped automatically. |
// Track any product event — it auto-registers
signalpad.track("voice_note.sent");
// With properties (used for breakdowns)
signalpad.track("export.completed", {
format: "csv",
row_count: 1420,
});
// React example — in an event handler
function handleExport() {
exportData();
signalpad.track("dashboard.exported");
}signalpad.register(properties)
Registers “super properties” that are merged into every subsequenttrack() event automatically. Handy for context you want on all events — plan, workspace, app version.
signalpad.register({ plan: "pro", app_version: "2.4.0" });
// Every later event now carries plan + app_version
signalpad.track("export.completed"); // → { plan: "pro", app_version: "2.4.0" }signalpad.registerElement(key, elementOrRef)
Registers a DOM element under a named key so guided flows can spotlight and anchor tooltips to it. Use this instead of fragile CSS selectors — your elements are referenced by stable logical names regardless of class or ID changes.
const btn = document.getElementById("record-btn");
signalpad.registerElement("voice-record-btn", btn);import { useRef, useEffect } from "react";
function RecordButton() {
const ref = useRef<HTMLButtonElement>(null);
useEffect(() => {
// Pass the ref — the SDK reads .current automatically
signalpad.registerElement("voice-record-btn", ref);
}, []);
return <button ref={ref}>Record</button>;
}The key you register here must match the Element key set on each flow step in the guided flow builder.
signalpad.open() / signalpad.close()
Programmatically open or close the “What’s new” panel. Useful when your own UI triggers the widget — for example, a “Release notes” menu item.
No-code alternative: You can add the data-sp="open" attribute to any element on your site to automatically bind it to the open panel action.
// Open the panel — same as clicking the badge signalpad.open(); // Close it signalpad.close(); // HTML example: Add data-sp="open" to any button to auto-bind it! // <button data-sp="open">What's New</button>
signalpad.open() or an element with data-sp="open". This gives you full control over when and how updates are surfaced.signalpad.startFlow(id)
Starts a guided flow programmatically, bypassing the panel UI. Pass an update IDto start the walkthrough attached to that update, or a standalone flow ID for a flow whose trigger is set to Manual. Use this to launch tours from your own buttons, a help menu, or a deep link from an email or notification.
// Update-tied walkthrough — id is the update UUID from the dashboard
signalpad.startFlow("f3a1b2c4-d5e6-7890-abcd-ef1234567890");
// Standalone flow with a Manual trigger — id is the flow UUID
document.querySelector("#help-tour").onclick = () =>
signalpad.startFlow("a1b2c3d4-e5f6-7890-abcd-ef1234567890");A standalone flow only launches if it is active, its audience targeting matches the current user, and it has not already been completed (for once-per-user / until-completed frequencies). Only one flow runs at a time.
signalpad.consent(status)
Grants or withdraws consent to collect data. Only has an effect when the widget is installed with data-consent="required". Call it from your cookie banner’s Accept / Reject handlers.
// User clicked "Accept" in your consent banner
banner.onAccept = () => signalpad.consent("granted");
// User clicked "Reject" (or withdrew consent later)
banner.onReject = () => signalpad.consent("denied");Privacy & consent
Signalpad is a data processor: the widget runs on your site and collects your end-users’ data on your behalf. Two features help you stay compliant (GDPR and similar).
Consent mode
Add data-consent="required" to the script tag and the widget starts in a fully dark state — no cookie, no collection, no network calls that record behaviour — until you callsignalpad.consent("granted"). It also honours the browser’s Do Not Track signal automatically. Callingconsent("denied") clears any queued events and stops tracking. Without the attribute, the widget behaves as before.
PII scrubbing
As a safety net, Signalpad strips personally-identifying data even if it’s passed by mistake. Sensitive property keys (email, password, token, ssn, card fields, and more) are dropped, email-shaped values are redacted, and sensitive URL query params (?token=, ?email=…) are removed from captured page URLs. Scrubbing runs both in the widget before sending and again on our servers at ingest.
Catch-up mode Pro+
Drip-feeding updates one at a time works when users open your app daily. It breaks when someone returns after two weeks — they see one update, dismiss it, and miss the other twelve you shipped while they were gone.
With Catch-up mode enabled, returning users who’ve been away for catch_up_away_days+ days see a summary panel on first open: every update they missed, grouped by type, with a one-click option to either mark everything read or step through them one-by-one (so they can still react and start guided flows).
Enable from Dashboard → Settings → Widget → Catch-up mode. The widget evaluates eligibility on each load against the user’s last_seen_at timestamp — no code changes required after enabling.
localStorage and never re-shown for the same updates — even if they stay away for another two weeks.Widget configuration
All widget configuration is managed from the dashboard — not in code. The widget reads its config on load from /api/sdk/config?key=YOUR_PROJECT_KEY. Changes in the dashboard take effect on the next page load.
| Option | Values | Description |
|---|---|---|
| position | bottom-right · bottom-left · top-right · top-left | Where the badge anchors on screen. |
| theme | light · dark · auto | auto intelligently sniffs your host page for class="dark" or data-theme="dark" to instantly match your app's theme. If none are found, it falls back to the user’s OS preference via prefers-color-scheme. |
| trigger | on_load · on_click · on_event | on_load (Floating Badge) auto-opens panel after 1.8s if there are unread updates. on_click (Floating Badge) opens on badge tap only. on_event (Hidden) completely hides the badge, requiring signalpad.open() or data-sp="open". |
| custom_css | string | Injected as a <style> tag after the widget’s default styles — use to override colours, border-radius, font. |
| catch_up_enabledPro+ | boolean | When true, returning users who’ve been away for catch_up_away_days+ days see a “Here’s what you missed” summary on first open. |
| catch_up_away_daysPro+ | number | How many days of inactivity trigger the catch-up summary. Default: 7. |