Back to Docs

Feedback Collection

Collect customer feedback from your app using widgets, forms, or direct API calls.

New: narrated feedback

Instead of a form, let people record themselves using your app and talking through what they see. One call to sp.feedback.record() with the SDK. We record the mic only, never the screen, and work out which element "this button" meant. Read the guide →

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

FieldTypeRequiredDescription
titlestringYesFeedback title/subject
contentstringYesThe feedback message
emailstringNoCustomer email (creates/links customer record)
namestringNoCustomer name
externalIdstringNoYour user ID for identity matching
sourcestringNoWhere feedback came from (e.g., "widget", "nps")
metadataobjectNoAny 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 email to automatically create/link customer records
  • •Track sources: Use the source field to see where feedback comes from
  • •Add context: Use metadata to include page URL, user agent, feature flags, etc.