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="pk_live_YOUR_PROJECT_KEY" data-api="https://signalpad.app" ></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. |
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, metadata?)
Records a custom event. This is how Signalpad measures feature adoption — by matching your track() calls against the feature event key set on each update in the dashboard.
| Parameter | Type | Description |
|---|---|---|
| eventName | string | The event key. Must exactly match the feature event key on the update for adoption to register. |
| metadata | object | Optional. Extra context stored alongside the event. Not used for matching — purely for your records. |
// Track feature usage — must match the dashboard event key exactly
signalpad.track("voice_note_sent");
// With optional metadata
signalpad.track("export_triggered", {
format: "csv",
row_count: 1420,
});
// React example — in an event handler
function handleExport() {
exportData();
signalpad.track("dashboard_exported");
}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.
// Open the panel — same as clicking the badge
signalpad.open();
// Close it
signalpad.close();
// Example: hook into your own nav item
document.getElementById("whats-new-btn")
.addEventListener("click", () => signalpad.open());signalpad.open(). This gives you full control over when updates are surfaced.signalpad.startFlow(updateId)
Starts the guided flow attached to a specific update, bypassing the panel UI. Use this to trigger walkthroughs from your own buttons or deep-link into a flow from an email or notification.
// Start a flow programmatically — updateId is the UUID from the dashboard
signalpad.startFlow("f3a1b2c4-d5e6-7890-abcd-ef1234567890");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 follows the user’s OS preference via prefers-color-scheme. |
| trigger | on_load · on_click · on_event | on_load auto-opens after 1.8 s if there are unread updates. on_click opens on badge tap only. on_event requires signalpad.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. |