UserSay is an embeddable AI interview widget that talks to your users at the moments that matter — when they hit a credit limit, after their first week on a paid plan, or right before they churn. You get structured insights, they get a reward.
Quick Overview
Create a project in the UserSay dashboard and configure your research agenda
Add the SDK script to your app — one <script> tag
Identify the user after login so the SDK knows who they are
Fire triggers at the right moments — SDK opens the interview modal automatically
Handle completion — grant rewards via onComplete callback
The fastest way to get started — paste this before </body>:
<!-- Step 1: Load the SDK -->
<script src="https://usersay.ai/usersay-sdk.js"
data-project="your-project-slug" async></script>
<!-- Step 2: Identify user + fire trigger -->
<script>
UserSay.identify({ uid: currentUser.id, plan: currentUser.plan });
// Fire when the moment is right
if (currentUser.credits <= 0) {
UserSay.trigger('credits_exhausted');
}
</script>
The SDK handles the rest: opens a modal with the AI interview, deduplicates (won't re-show after dismiss or completion), and fires your onComplete callback when the interview finishes.
Replace your-project-slug with your project's slug (e.g. mymap). You can find it in your project settings.
Step 1 — Identify the User
Call UserSay.identify() after the user logs in. This tells the SDK who the current user is and enables deduplication (the same user won't see the interview twice).
UserSay.identify({
uid: currentUser.id, // required — your user's unique ID
plan: currentUser.plan, // optional — 'free', 'pro', etc.
credits: currentUser.credits // optional — current credit balance
});
Field
Type
Required
Description
uid
string
Yes
Unique user ID — used for deduplication
plan
string
No
Current plan (e.g. "free", "pro")
credits
number
No
Current credit balance
Step 2 — Fire a Trigger
Call UserSay.trigger() at the moment you want the interview to appear. The SDK opens a modal with the AI interview automatically.
// When credits run out
if (currentUser.credits <= 0) {
UserSay.trigger('credits_exhausted');
}
// Day 7 of a paid plan
if (daysSinceUpgrade(currentUser) === 7) {
UserSay.trigger('day_7_paid');
}
// User visited /pricing
if (location.pathname === '/pricing') {
UserSay.trigger('pricing');
}
// Any custom moment
UserSay.trigger('completed_onboarding');
The trigger name is passed to the AI as context — it shapes the opening question. See Triggers for the list of built-in trigger contexts.
Step 3 — Handle Completion
Register a callback to run when the user finishes the interview:
UserSay.onComplete(function() {
// Grant reward — do this server-side for security
fetch('/api/grant-credits', { method: 'POST' });
// Or show a toast
showToast('Thanks! 50 credits have been added.');
});
Note: For granting real credits or discounts, always enforce rewards server-side. The onComplete callback fires client-side and should be used for UI feedback only (toasts, banners). See Reward Callbacks.
Full Example
<!-- In your app layout -->
<script src="https://usersay.ai/usersay-sdk.js"
data-project="mymap" async></script>
<script>
// After user logs in
UserSay.identify({
uid: currentUser.id,
plan: currentUser.plan,
credits: currentUser.credits
});
// Register completion handler
UserSay.onComplete(function() {
fetch('/api/grant-interview-reward', { method: 'POST' });
});
// Fire trigger when appropriate
if (currentUser.credits <= 0) {
UserSay.trigger('credits_exhausted');
}
</script>
SDK API Reference
Method
Description
UserSay.identify({ uid, plan?, credits? })
Identify the current user. Call after login.
UserSay.trigger(eventName)
Fire a trigger — opens the interview modal.
UserSay.open(triggerName?)
Open the modal directly, bypassing dedup rules.
UserSay.close()
Close the modal programmatically.
UserSay.onComplete(fn)
Register a callback for interview completion.
Script Tag Attributes
Attribute
Required
Description
data-project
Yes
Your project slug (e.g. mymap)
data-host
No
Override base URL — useful for self-hosting
Deduplication
The SDK automatically prevents re-showing the interview:
Completed — if the user finished the interview, it will never show again for that user/project combination
Dismissed — if the user closed without completing, the interview is suppressed for 7 days
Dedup state is stored in localStorage under usersay:{project}:{uid}:completed and usersay:{project}:{uid}:dismissed.
Async Loading
Because the script tag has async, your UserSay.identify() and UserSay.trigger() calls may run before the SDK has loaded. The SDK handles this automatically — calls made before load are queued and replayed once the script initializes.
Next Steps
Triggers — full list of trigger contexts and what they tell the AI
'use client';
export function CreditsBanner() {
function handleCreditsTrigger() {
window.UserSay?.trigger('credits_exhausted');
}
return (
<div>
You've used all your credits.
<button onClick={handleCreditsTrigger}>
Chat with us for 50 free credits
</button>
</div>
);
}
Planned: @usersay/react
The React package will provide a more idiomatic API:
// Planned API — not yet available
import { UserSayWidget } from '@usersay/react';
export default function App() {
return (
<UserSayWidget
projectId="proj_xxx"
user={{ id: currentUser.id, plan: currentUser.plan }}
onComplete={() => grantCredits(currentUser.id, 50)}
/>
);
}
And a hook:
// Planned API — not yet available
'use client';
import { useUserSay } from '@usersay/react';
export function CreditsBanner() {
const { trigger } = useUserSay();
return (
<button onClick={() => trigger('credits_exhausted')}>
Chat with us for 50 free credits
</button>
);
}
Configure when UserSay interviews are shown to your users.
Triggers define when an interview invitation appears. UserSay uses lifecycle-based triggers — interviews happen at moments that naturally prompt reflection, not random popups.
How Triggers Work
Your app calls UserSay.trigger('event_name') at the right moment
The SDK checks dedup — skips if the user already completed or was recently dismissed
If clear, the SDK opens a modal with the AI interview
The trigger name is passed to the AI as context — it shapes the opening question
Built-in Trigger Contexts
These trigger names have built-in AI openers — the AI knows how to start the conversation naturally based on the context.
Trigger
Key
AI Opening Context
Credits Exhausted
credits_exhausted
User just hit their usage limit while working on something
Day 7 Paid
day_7_paid
User has been on a paid plan for one week — knows the product well
Inactive
inactive
User hasn't been active for 7+ days after regular usage
Visited Pricing
pricing
User just visited the pricing page — thinking about money
Custom
custom (default)
Generic opener — AI introduces itself and asks what the user was trying to do
Custom Trigger Names
You can pass any string to UserSay.trigger(). If it doesn't match a built-in context, the AI uses a generic opener.
// Built-in context — AI knows how to open this
UserSay.trigger('credits_exhausted');
// Custom event — AI uses generic opener
UserSay.trigger('completed_first_export');
UserSay.trigger('cancelled_subscription');
UserSay.trigger('hit_api_limit');
Example: Wiring Triggers in Your App
// After page load / user auth
UserSay.identify({ uid: user.id, plan: user.plan, credits: user.credits });
UserSay.onComplete(() => grantReward(user.id));
// Credits ran out
if (user.credits <= 0) {
UserSay.trigger('credits_exhausted');
}
// Day 7 of paid plan
if (user.plan !== 'free' && daysSinceUpgrade(user) === 7) {
UserSay.trigger('day_7_paid');
}
// Visiting /pricing
if (location.pathname === '/pricing') {
UserSay.trigger('pricing');
}
// Long inactivity (check on login)
if (daysSinceLastActive(user) >= 7) {
UserSay.trigger('inactive');
}
Deduplication
The SDK prevents the same user from seeing the interview multiple times:
Completed → never show again
Dismissed → suppress for 7 days
This is handled automatically in localStorage. You don't need to track this yourself.
Planned: Dashboard-Configured Triggers
Planned — Currently triggers are fired manually from your code. Dashboard-based auto-triggers are on the roadmap.
In a future release, you'll be able to configure trigger rules directly in the UserSay dashboard — no code changes needed:
Grant users credits, discounts, or other incentives after completing an interview.
Rewards incentivize users to complete interviews. When a user finishes a conversation, you grant the reward from your app.
How It Works Today
The SDK fires a client-side callback when the interview completes. Use this to trigger a server-side reward grant from your app.
UserSay.onComplete(function() {
// Call your own API to grant the reward
fetch('/api/grant-interview-reward', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: currentUser.id })
});
});
Important: Always enforce rewards server-side. The onComplete callback fires in the browser and should not be the sole gatekeeper — a user could trigger it manually. Use it to initiate a server call; validate and grant the reward on your backend.
Reward Types
Configure what reward users receive in your Setup page:
Type
Example
When to use
Credits
+50 credits
In-product currency (most common)
Coupon code
THANKS20
Discount on next billing cycle
Custom link
/redeem?token=xxx
Anything else — gift cards, swag, etc.
The reward type and value are passed to the AI, which mentions it naturally at the end of the interview ("Your 50 credits have been added!").
Client-Side Feedback
Show a toast or notification to the user after the interview completes:
UserSay.onComplete(function() {
// Fire server-side grant
fetch('/api/grant-interview-reward', { method: 'POST' });
// Show UI feedback
showToast('Thanks for sharing! 50 credits have been added.');
});
Planned: Webhooks
Planned — Server-to-server webhook delivery is on the roadmap.
In a future release, UserSay will POST directly to your server when an interview completes — no client-side callback needed:
This is more reliable than the client-side callback (works even if the user closes the tab) and easier to secure with signature verification.
Next Steps
Triggers — configure which moments trigger interviews
API Reference — manage sessions programmatically (planned)
API Reference
Server-side API for creating sessions, sending messages, and retrieving insights.
Planned — The api.usersay.ai REST API is not yet available. This page documents the planned API for reference.
The UserSay REST API will let you create and manage interview sessions programmatically — useful for mobile apps, server-side triggers, or custom interview UIs.
What's Available Today
Endpoint
Status
Description
GET /api/sdk/config?project={slug}
Released
Returns trigger rules for the JS SDK. CORS-enabled.
POST /api/research/chat
Released
Streaming chat endpoint used internally by the interview widget
api.usersay.ai/v1/*
Planned
Full REST API for sessions, insights, and project management
GET /api/sdk/config
Used by the JS SDK to fetch trigger configuration on init.