Behavioural insight for trial users.
TrialSignal helps you understand how your trial users behave: pageviews, engagement and high-intent actions (like clicking “Upgrade”).
There are three core pieces:
trialsignal.io/collect) – receives JSON from your app.trialsignal.io/tracker.js) – handles pageviews + trial state.tsTrack()) – lets you log key actions.
Paste this snippet into your app layout / template so it appears on every page
(usually just before </head> or at the top of <body>).
<!-- TrialSignal.io -->
<script>
!function(w,d,_,endpoint){
// Init config
w.tsAnalytics = {
key: "ts_Cf9OSCZiLzvMajszVhuz0KaIuHgyTprzcsXtE61XoCOXPN2r7Bg6gkcCiwwj73GR",
userId: "83dcefb7",
userEmail: "Pencil Function Bands",
trial: {
status: "converted",
type: "14-day trial",
startedAt: "2019-11-28 11:26:26",
endsAt: "2020-05-28",
convertedAt: "2020-04-24 14:18:31 00:00:00",
plan: "ultimate_gbp_monthly"
}
};
// Global tsTrack() for custom events
w.tsTrack = function(name, props){
if (!w.tsAnalytics || !w.tsAnalytics.key) return;
try {
var payload = {
project_key: w.tsAnalytics.key,
event_type: "event",
user: {
id: w.tsAnalytics.userId || null,
label: w.tsAnalytics.userEmail || null
},
event: {
name: name,
properties: props || {}
},
ts: new Date().toISOString()
};
// Always use fetch. No sendBeacon.
fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
keepalive: true,
credentials: "omit"
}).catch(function(){});
} catch(err){}
};
// Load tracker.js (pageviews + trial sync)
var s = d.createElement("script");
s.async = 1;
s.src = "https://trialsignal.io/tracker.js?v=1";
d.head.appendChild(s);
}(window, document, 0, "https://trialsignal.io/collect");
</script>
YOUR_PUBLIC_KEY_HERE.
Once tracker.js is loaded, it automatically sends a
pageview event on page load.
tracker.js calls the backend with a payload like:
{
"project_key": "YOUR_PUBLIC_KEY_HERE",
"event_type": "pageview",
"session_id": "ts_analytics_sess_value",
"user": {
"id": 123,
"label": "[email protected]"
},
"page": {
"path": "/pricing",
"url": "https://yourapp.com/pricing",
"referrer": "https://google.com"
},
"ts": "2025-12-08T15:26:15.864Z",
"user_agent": "Mozilla/5.0 ...",
"device": "Desktop" // Desktop | Mobile | Tablet
}
localStorage as ts_analytics_sess.analytics_session, and pageviews
in analytics_pageview, including IP (packed), user agent,
device, and country where available.
TrialSignal keeps a single trial record per user in
analytics_trial. This is updated via trial_state events.
On page load, if you pass a trial object and a userId, the tracker will send:
{
"project_key": "YOUR_PUBLIC_KEY_HERE",
"event_type": "trial_state",
"user": {
"id": 123,
"label": "[email protected]"
},
"trial": {
"status": "trialing", // or 'converted', 'expired', 'cancelled', etc.
"type": "standard_1mth",
"started_at": "2025-12-08T15:26:15.864Z",
"ends_at": "2026-01-10T15:26:15.864Z",
"converted_at": null,
"plan": null
},
"ts": "2025-12-08T15:26:15.864Z"
}
(account_id, project_id, user_id), a new row is inserted.status, trial_started_at, trial_ends_at always update.trial_type and converted_plan only update if you pass a non-null value.converted_at is set when you supply a value (e.g. on upgrade).user_id is required for
trial_state events. If it’s missing, the backend returns
400 user_id required for trial_state.
Use tsTrack(name, properties) wherever you want to log a
meaningful action (e.g. “clicked upgrade”, “created first project”, etc.).
<button id="upgrade-btn">Upgrade to Ultimate</button>
<script>
document.getElementById('upgrade-btn')?.addEventListener('click', function () {
window.tsTrack && window.tsTrack('clicked_upgrade_button', {
location: 'pricing_page',
plan: 'ultimate'
});
});
</script>
tsTrack() sends an event payload to the same /collect endpoint:
{
"project_key": "YOUR_PUBLIC_KEY_HERE",
"event_type": "event",
"user": {
"id": 123,
"label": "[email protected]"
},
"event": {
"name": "clicked_upgrade_button",
"properties": {
"location": "pricing_page",
"plan": "ultimate"
}
},
"ts": "2025-12-08T15:26:15.864Z"
}
analytics_event.session_id, it links the event to that session.last_seen_at, so backdated events don’t affect session duration.
After a user logs in (or when you know who they are), render the snippet with their
userId, userEmail, and trial information. This ensures:
status: 'trialing', startedAt, and endsAt.status: 'converted', convertedAt, and plan.status: 'expired'.status: 'cancelled'.clicked_upgrade_buttoncreated_first_eventsent_first_invoiceinvited_team_memberKeep names lowercase and snake_case – it makes reporting and filtering easier.
The backend will respond with JSON – on hard errors you’ll see
{"ok": false, "error": "..."}. For unknown
event_type values, it just returns
{"ok": true, "ignored": true}.