Post-Purchase Survey Implementation on Website
Post-purchase survey provides direct feedback from buyer at moment of highest loyalty. Properly implemented survey yields data for funnel optimization, reveals traffic sources Analytics misses (word-of-mouth, podcasts, offline ads), and reduces returns via proactive expectation management.
Where and When to Display
Three placement points with different effectiveness:
1. Success page (immediately after payment). Highest conversion — 30–50%. User still "in the moment", hasn't closed tab. Show 1–2 questions max.
2. Email within 3–7 days. Lower conversion (5–15%), but can ask more questions about actual product experience. Works for physical goods.
3. Inline in personal account. Non-intrusive reminder on next visit. Works for SaaS and subscription products.
Questions: What to Ask
Classic set for e-commerce:
1. How did you hear about us? (single select)
□ Google / Yandex
□ Social media (Instagram, TikTok, VK)
□ Friend/colleague recommendation
□ YouTube
□ Podcast
□ Repeat customer
□ Other: [field]
2. What influenced your purchase decision? (multi select)
□ Price
□ Reviews
□ Brand reputation
□ Specific feature/specification
□ Competitor comparison
3. How likely to recommend us? (NPS, 0–10)
Traffic source question is most valuable. Google Analytics, Meta Pixel, UTM tags lose up to 40% attribution (iOS 14.5+, Safari ITP, VPN, direct visits). Survey fills gap.
Technical Implementation
Data model:
CREATE TABLE surveys (
id BIGSERIAL PRIMARY KEY,
order_id BIGINT REFERENCES orders(id),
user_id BIGINT REFERENCES users(id),
answers JSONB,
nps_score SMALLINT CHECK (nps_score BETWEEN 0 AND 10),
source VARCHAR(100),
completed BOOLEAN DEFAULT false,
token UUID DEFAULT gen_random_uuid(), -- for email link without login
created_at TIMESTAMP DEFAULT NOW(),
completed_at TIMESTAMP
);
answers in JSONB stores arbitrary structure without migrations when questionnaire changes.
API endpoints:
POST /api/surveys — create survey on order creation
GET /api/surveys/{token} — get questions (public, via token)
POST /api/surveys/{token} — save answers
Success page component (React):
const PostPurchaseSurvey: React.FC<{ orderId: number }> = ({ orderId }) => {
const [step, setStep] = useState(0);
const [answers, setAnswers] = useState<Record<string, unknown>>({});
const [dismissed, setDismissed] = useState(false);
const questions = [
{ id: 'source', type: 'single', label: 'How did you hear about us?', options: [...] },
{ id: 'nps', type: 'nps', label: 'How likely to recommend?' },
];
const handleAnswer = (questionId: string, value: unknown) => {
setAnswers(prev => ({ ...prev, [questionId]: value }));
if (step < questions.length - 1) {
setStep(s => s + 1);
} else {
submitSurvey({ ...answers, [questionId]: value });
}
};
if (dismissed) return null;
return (
<div className="survey-card">
<button className="dismiss" onClick={() => setDismissed(true)}>×</button>
<QuestionRenderer
question={questions[step]}
onAnswer={handleAnswer}
/>
<ProgressDots total={questions.length} current={step} />
</div>
);
};
Step-by-step progress instead of long form lifts completion rate 20–35%.
Email Survey
Survey token embedded in transactional email. Link format /survey/{token} opens page without login needed:
// Laravel Mailable
public function build() {
return $this->markdown('emails.survey')
->with([
'survey_url' => route('survey.show', $this->survey->token),
'order' => $this->order,
]);
}
Page /survey/{token} uses same React component but with expanded question set (5–7 vs 2).
Dashboard and Analytics
Minimal dashboard:
-
Source distribution — pie chart by
answers->>'source' - NPS trend — line graph NPS per week
-
Completion rate —
COUNT(*) WHERE completed / COUNT(*) * 100
SQL for source distribution:
SELECT
answers->>'source' AS source,
COUNT(*) AS cnt,
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 1) AS pct
FROM surveys
WHERE completed = true
AND created_at >= NOW() - INTERVAL '30 days'
GROUP BY 1
ORDER BY 2 DESC;
NPS Segmentation and Follow-up
| Score | Segment | Action |
|---|---|---|
| 9–10 | Promoter | Ask for review / referral program |
| 7–8 | Passive | Nothing or discount offer email |
| 0–6 | Detractor | Auto-ticket to support |
Trigger on Detractor:
// In observer after survey save
if ($survey->nps_score <= 6) {
SupportTicket::create([
'user_id' => $survey->user_id,
'subject' => 'Low NPS follow-up',
'priority' => 'high',
'meta' => ['order_id' => $survey->order_id, 'nps' => $survey->nps_score],
]);
}
Timelines
| Task | Time |
|---|---|
| Data model + API | 0.5 day |
| Success page component | 1 day |
| Email integration with token | 0.5 day |
| Analytics dashboard | 1 day |
Total: 2.5–3 working days.







