Back to Docs
Feedback Collection
Collect customer feedback from your app using widgets, forms, or direct API calls.
import { useState } from 'react';
interface FeedbackWidgetProps {
apiKey: string;
userEmail?: string;
userName?: string;
}
export function FeedbackWidget({ apiKey, userEmail, userName }: FeedbackWidgetProps) {
const [isOpen, setIsOpen] = useState(false);
const [content, setContent] = useState('');
const [status, setStatus] = useState<'idle' | 'sending' | 'sent' | 'error'>('idle');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setStatus('sending');
try {
const res = await fetch('https://simpleproduct.dev/api/v1/feedback', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: 'Feedback',
content,
email: userEmail,
name: userName,
source: 'react-widget',
metadata: { page: window.location.pathname },
}),
});
if (res.ok) {
setStatus('sent');
setTimeout(() => {
setIsOpen(false);
setStatus('idle');
setContent('');
}, 2000);
} else {
setStatus('error');
}
} catch {
setStatus('error');
}
};
if (!isOpen) {
return (
<button
onClick={() => setIsOpen(true)}
style={{
position: 'fixed',
bottom: '20px',
right: '20px',
padding: '12px 24px',
borderRadius: '8px',
border: 'none',
background: '#000',
color: '#fff',
cursor: 'pointer',
}}
>
Feedback
</button>
);
}
return (
<div style={{
position: 'fixed',
bottom: '20px',
right: '20px',
width: '320px',
padding: '20px',
borderRadius: '12px',
background: '#fff',
boxShadow: '0 4px 20px rgba(0,0,0,0.15)',
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '16px' }}>
<h3 style={{ margin: 0 }}>Send Feedback</h3>
<button onClick={() => setIsOpen(false)} style={{ background: 'none', border: 'none', cursor: 'pointer' }}>✕</button>
</div>
{status === 'sent' ? (
<p>Thanks for your feedback!</p>
) : (
<form onSubmit={handleSubmit}>
<textarea
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder="What's on your mind?"
required
style={{ width: '100%', minHeight: '100px', marginBottom: '12px', padding: '8px', borderRadius: '6px', border: '1px solid #ddd' }}
/>
<button
type="submit"
disabled={status === 'sending'}
style={{ width: '100%', padding: '10px', borderRadius: '6px', border: 'none', background: '#000', color: '#fff', cursor: 'pointer' }}
>
{status === 'sending' ? 'Sending...' : 'Send'}
</button>
{status === 'error' && <p style={{ color: 'red', marginTop: '8px' }}>Something went wrong. Please try again.</p>}
</form>
)}
</div>
);
}
// Usage in your app
function App() {
return (
<>
{/* Your app content */}
<FeedbackWidget
apiKey="YOUR_API_KEY"
userEmail={currentUser?.email}
userName={currentUser?.name}
/>
</>
);
}API Reference
POST /api/v1/feedback
Headers
Authorization: Bearer YOUR_API_KEY(required)Content-Type: application/json
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | Feedback title/subject |
| content | string | Yes | The feedback message |
| string | No | Customer email (creates/links customer record) | |
| name | string | No | Customer name |
| externalId | string | No | Your user ID for identity matching |
| source | string | No | Where feedback came from (e.g., "widget", "nps") |
| metadata | object | No | Any custom JSON data |
Response
{
"data": {
"id": "abc123",
"title": "Feedback",
"content": "...",
"person": { "id": "...", "name": "John", "email": "john@example.com" },
"createdAt": 1234567890
}
}
Tips
- •Link feedback to customers: Include
emailto automatically create/link customer records - •Track sources: Use the
sourcefield to see where feedback comes from - •Add context: Use
metadatato include page URL, user agent, feature flags, etc.