Реалізація NPS-опитування на сайті
Користувачі йдуть, а ви не знаєте чому? NPS-опитування (Net Promoter Score) — стандарт вимірювання лояльності, але його впровадження часто роблять неправильно: показують всім підряд, без сегментації, не пов'язують з конкретними подіями. Результат — низька конверсія відповідей та сміттєві дані, які не допомагають бізнесу.
За 5 років ми впровадили NPS більш ніж у 50 проєктах — від SaaS до інтернет-магазинів. В одному з проєктів правильне налаштування тригерів збільшило конверсію відповідей на 30%, а обробка критиків у реальному часі знизила відтік на 15%. В іншому проєкті миттєві сповіщення про критиків скоротили догляд клієнтів на 25%. Наш досвід гарантує чисті дані та actionable інсайти.
Правильна сегментація та частота показу — запорука високого NPS та релевантних даних. Наприклад, для інтернет-магазину ми налаштовуємо показ після кожної покупки для нових клієнтів, а для B2B SaaS — після завершення тріалу або першого контакту з підтримкою.
Зв'яжіться з нами, щоб отримати консультацію щодо впровадження NPS та оцінити потенційну економію на ретеншн.
Як типові помилки вбивають цінність NPS
Перша помилка — показувати опитування всім користувачам без розбору. Новачки (менше 7 днів) ще не сформували враження, а користувачі з відкритими тікетами підтримки свідомо незадоволені. Підсумок — зміщені дані.
Друга — рідке або занадто часте з'явлення. Якщо опитування показувати раз на півроку, ви пропускаєте зміну настроїв. Якщо щодня — користувачі блокують віджет adblock-ом. Оптимум: раз на 90 днів, прив'язаний до ключової дії.
Третя — ігнорування сегментації. Критики (0-6) і промоутери (9-10) потребують різного підходу. Критику потрібно негайно відповісти та вирішити проблему; промоутера — подякувати та запропонувати програму лояльності.
Які дані збирати разом з оцінкою?
Окрім оцінки (0-10) та коментаря, ми зберігаємо: ID користувача або session_id, подію-тригер (order_completed, trial_ended), URL сторінки, IP-адресу та часову мітку. Це дозволяє сегментувати результати та виявити проблемні точки інтерфейсу. У бекенді це виглядає так:
// database/migrations/create_nps_responses_table.php Schema::create('nps_responses', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete(); $table->string('session_id')->nullable(); $table->tinyInteger('score')->unsigned(); $table->text('comment')->nullable(); $table->string('trigger_event')->nullable(); // 'order_completed', 'trial_ended' $table->string('page_url')->nullable(); $table->ipAddress('ip')->nullable(); $table->timestamps(); }); // NpsController public function store(Request $request): JsonResponse { $validated = $request->validate([ 'score' => 'required|integer|min:0|max:10', 'comment' => 'nullable|string|max:1000', 'trigger_event' => 'nullable|string|max:100', ]); $response = NpsResponse::create([ ...$validated, 'user_id' => auth()->id(), 'session_id' => $request->session()->getId(), 'page_url' => $request->input('page_url'), 'ip' => $request->ip(), ]); // Зберігаємо в сесії щоб не показувати знову $request->session()->put('nps_submitted_at', now()->timestamp); // Алерт у Slack якщо критик залишив коментар if ($validated['score'] <= 6 && !empty($validated['comment'])) { SlackNotification::send('#feedback', "NPS {$validated['score']}: {$validated['comment']}"); } return response()->json(['success' => true]); } Коли і кому показувати: умови тригерів
Час показу — 60% успіху. Ми використовуємо три категорії тригерів:
| Тип тригера | Приклади подій | Затримка показу |
|---|---|---|
| Подійний | Завершення замовлення, закінчення тріалу, перше використання функції | Відразу через 2-3 сек |
| Часовий | Через 14-30 днів після реєстрації, щоквартально | Залежить від циклу |
| Сегментний | Виключити новачків (<7 днів), користувачів з відкритими тікетами | — |
Backend: модель і API
// database/migrations/create_nps_responses_table.php Schema::create('nps_responses', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->nullable()->constrained()->nullOnDelete(); $table->string('session_id')->nullable(); $table->tinyInteger('score')->unsigned(); $table->text('comment')->nullable(); $table->string('trigger_event')->nullable(); // 'order_completed', 'trial_ended' $table->string('page_url')->nullable(); $table->ipAddress('ip')->nullable(); $table->timestamps(); }); // NpsController public function store(Request $request): JsonResponse { $validated = $request->validate([ 'score' => 'required|integer|min:0|max:10', 'comment' => 'nullable|string|max:1000', 'trigger_event' => 'nullable|string|max:100', ]); $response = NpsResponse::create([ ...$validated, 'user_id' => auth()->id(), 'session_id' => $request->session()->getId(), 'page_url' => $request->input('page_url'), 'ip' => $request->ip(), ]); // Зберігаємо в сесії щоб не показувати знову $request->session()->put('nps_submitted_at', now()->timestamp); // Алерт у Slack якщо критик залишив коментар if ($validated['score'] <= 6 && !empty($validated['comment'])) { SlackNotification::send('#feedback', "NPS {$validated['score']}: {$validated['comment']}"); } return response()->json(['success' => true]); } Як перевірити, чи варто показувати віджет?
// Middleware або хелпер для перевірки public function shouldShowNps(Request $request): bool { $user = auth()->user(); if (!$user) return false; // Не показувати частіше ніж раз на 90 днів $lastShown = $request->session()->get('nps_shown_at'); if ($lastShown && now()->timestamp - $lastShown < 90 * 86400) { return false; } // Вже відповів if ($request->session()->has('nps_submitted_at')) return false; // Користувач зареєстрований > 14 днів return $user->created_at->diffInDays(now()) >= 14; } Frontend: віджет на React
// NpsWidget.tsx export function NpsWidget({ triggerEvent }: { triggerEvent: string }) { const [score, setScore] = useState<number | null>(null); const [comment, setComment] = useState(''); const [step, setStep] = useState<'score' | 'comment' | 'done'>('score'); const submit = async () => { await fetch('/api/nps', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ score, comment, trigger_event: triggerEvent }), }); setStep('done'); }; const label = score === null ? '' : score <= 6 ? 'Критик' : score <= 8 ? 'Нейтральний' : 'Промоутер'; return ( <div className="fixed bottom-6 right-6 w-80 rounded-xl bg-white shadow-xl p-5"> {step === 'score' && ( <> <p className="font-semibold mb-3">Наскільки ймовірно, що ви порекомендуєте нас?</p> <div className="flex gap-1 justify-between mb-2"> {Array.from({ length: 11 }, (_, i) => ( <button key={i} onClick={() => { setScore(i); setStep('comment'); }} className={`w-7 h-7 rounded text-sm ${i <= 6 ? 'bg-red-100' : i <= 8 ? 'bg-yellow-100' : 'bg-green-100'}`}> {i} </button> ))} </div> <div className="flex justify-between text-xs text-gray-400"> <span>Малоймовірно</span><span>Дуже ймовірно</span> </div> </> )} {step === 'comment' && ( <> <p className="font-semibold mb-2">Оцінка: {score} ({label}). Що вплинуло на вашу оцінку?</p> <textarea value={comment} onChange={e => setComment(e.target.value)} className="w-full border rounded p-2 text-sm h-20 resize-none" placeholder="Необов'язково..." /> <button onClick={submit} className="mt-2 w-full bg-blue-600 text-white rounded py-1.5 text-sm"> Відправити </button> </> )} {step === 'done' && <p className="text-center text-green-600 font-medium">Дякуємо за зворотний зв'язок!</p>} </div> ); } Аналітика NPS: SQL-запит для розрахунку
-- Розрахунок NPS за період SELECT COUNT(*) FILTER (WHERE score >= 9)::float / COUNT(*) * 100 AS promoters_pct, COUNT(*) FILTER (WHERE score <= 6)::float / COUNT(*) * 100 AS detractors_pct, (COUNT(*) FILTER (WHERE score >= 9) - COUNT(*) FILTER (WHERE score <= 6))::float / COUNT(*) * 100 AS nps_score FROM nps_responses WHERE created_at >= now() - interval '90 days'; Який NPS вважати хорошим? Порівняння з альтернативами
NPS опитування вигідніше CSAT (задоволеність конкретною угодою) тим, що воно вимірює загальну лояльність, а не разове враження. І краще CES (легкість взаємодії) для стратегічних рішень. NPS — найкращий індикатор майбутнього зростання: компанії з NPS >50 зростають у 2 рази швидше за конкурентів.
| Метрика | Фокус | Питання | Шкала |
|---|---|---|---|
| NPS | Лояльність | "Порекомендуєте?" | 0-10 |
| CSAT | Задоволеність | "Задоволені послугою?" | 1-5 |
| CES | Легкість | "Було легко?" | 1-7 |
Приклад розрахунку NPS
Якщо 50% промоутерів (9-10) і 20% критиків (0-6), то NPS = 50 - 20 = 30. Нейтральні (7-8) не враховуються.Процес впровадження
- Аналітика: визначаємо ключові події та сегменти користувачів.
- Проектування: розробляємо тригери та дизайн віджету.
- Реалізація: пишемо API, міграції, компоненти.
- Тестування: перевіряємо умови показу, коректність збору даних.
- Деплой: викочуємо в прод та налаштовуємо моніторинг.
Що входить в роботу
- Модель даних та API (Laravel 11, PHP 8.3+)
- Компонент віджету (React 18, Next.js або Vue 3)
- Умови показу з сегментацією
- Інтеграція з Slack / Telegram для алертів по критиках
- SQL-скрипт для аналітики та дашборд (опціонально)
- Документація по тригерах та підтримці
Терміни та економія
Віджет NPS з базовою аналітикою — від 3 до 4 робочих днів. Своєчасне виявлення критиків знижує відтік на 20-40%. Для оцінки вашого проєкту зв'яжіться з нами — ми підготуємо індивідуальну пропозицію. Замовте впровадження NPS і почніть збирати чисті дані про лояльність вже через тиждень.
Джерело: Wikipedia: Net Promoter







