Реалізація 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







