Utkrushta · Candidate Experience · Design Exploration

Collecting candidate testimonials after a task assessment

Six ways to ask a candidate for a testimonial at the end of a task — ranked by how much they interrupt — plus the free-tier Senja reality that decides the architecture, and a recommended way to ship it without irritating anyone or paying a cent.

Prepared 10 Jul 2026 Scope utkrushta-assessment (candidate app) + backend Goals website proof · product insight · client sharing Hard limits candidate experience first · free tier only

The decision, first

Answer up front; the reasoning follows.

Recommended path

Own the data. Rent the display. Ask at the peak, consent later.

Build our own lightweight testimonial component and store every submission in our Supabase — this is the system of record, uncapped, fully under our consent and GDPR control. Treat Senja's free tier purely as an optional display shelf (or skip it and render our own Wall of Love). Ask in two gentle moments, never one pushy one.

Phase 1 · do now · S–M
OPT-3 — Inline card on the congratulations screen.

A soft star-rating card at the confetti peak, framed around the experience (not their score, which isn't ready yet). A two-step gate stores every rating — happy and unhappy — as private product data.

Phase 2 · fast follow · M
OPT-4 — Consent card on the report page.

Once the result is out, a persistent, non-interrupting card asks the explicit "may we publish this?" opt-in. Asking after the outcome sidesteps the coercion problem entirely.

Why not simpler? Two options look tempting and are traps. Auto-opening a modal (OPT-5) converts best but interrupts hardest and, gated to passers, never hears the unhappy candidates whose feedback we most need. Redirecting to Senja's own form (OPT-2) is an hour of work but bypasses our database, brands the ask as Senja, and burns the free tier's 15-testimonial lifetime cap in a single busy day. Both are covered below so the trade-off is on the record.

The binding constraint: what Senja's free tier actually allows

Verified against Senja's own pricing page and free-vs-paid help docs, 10 Jul 2026.

15
testimonials, lifetime — the real ceiling.

The free plan stores 15 text-or-video testimonials total, forever. Utkrushta runs hundreds of assessments, so Senja free can only ever be a curated shelf for our ~15 best — never the place the data lives. This, not the API paywall, is what forces the architecture.

$29/mo
is the price of the write API.

POST /testimonials (api.senja.io/v1, Bearer auth) is real and would make an automated backend → Senja push trivial — but it's a paid Starter feature, along with webhooks and Zapier. On free there is no programmatic way in. So the literal "backend pushes to Senja" plan is off the table until we choose to pay.

Free
still gives us a genuine display layer.

One hosted, embeddable, consent-capturing collection form (prefillable by name/email/url), unlimited Walls of Love and widgets, and video — all carrying a non-removable "Powered by Senja" watermark. Useful for display; not a system of record.

CapabilityFree tier?What it means for us
Stored testimonials (text + video)15 lifetimeThe binding limit. No path raises it. Kills "collect from every candidate" on Senja itself.
Write APIPOST /testimonialsNo · $29/moThe single blocker for an automated PATH B. Key lives in Senja's paid Automate settings.
Webhooks / ZapierNo · paidZapier's own free tier can't help — the Senja side of the connection is gated.
Hosted collection formYes · 1 formShareable link senja.io/p/<slug>/r/<id> + embeddable. Text and video.
Form prefill via URLname, email, url onlyRating and custom fields (e.g. task_session_id) cannot be prefilled — no clean way to correlate back to a session.
Display widgets / Wall of LoveUnlimitedBut watermarked on free; an embed on utkrusht.ai reads "Powered by Senja".
Consent-to-publish + GDPRAll plansBuilt-in consent message; submissions land "unapproved" for review. A genuine upside of using Senja's form.
Custom branding (logo/colours/domain)No · paid"Make it look like Utkrushta" needs a paid plan.

So which integration path? Hybrid.

Take the collection-and-storage half of PATH B; bridge to Senja by hand for free.

PATH A — embed / redirect Senja's form

Technically free and zero-backend, but wrong three ways: it caps at 15, it bypasses our DB so we lose the data for product work and client decks, and it pushes an anxious candidate onto a watermarked third-party page mid-flow.

PATH B (literal) — backend pushes via API

Impossible on free. Its defining move — POST /testimonials from our backend — is the $29/mo feature. Dead until we decide to pay.

✓ Hybrid — own the storage, rent the shelf

Build our own component; store every submission in Supabase (no cap, our consent/GDPR). Hand-load the ~15 best into Senja's free shelf, or better, render our own Wall of Love with the existing design system and skip the watermark. Design the schema so that turning on paid Senja + an Airflow poller later is a one-line switch.

We already own the pattern to clone. The candidate app has a working private-feedback flow for test sessions — FeedbackModal/api/testSessions/submitFeedback → a user_feedback JSONB column. It even guards against double-submit and fires confetti. It is simply not wired for task sessions, which have no feedback column at all. That gap is the whole build.

Where the ask can live

The candidate's real path after they click submit. The green stage is the emotional peak.

📤
Submit task
Candidate ends the assessment
🎥
Explain video
Optional "explain what you did" recording
🎉
Congratulations
Confetti + message. The peak-end moment.
OPT 1 · 2 · 3
👀
View Report CTA
Green button → opens report
OPT 2
📊
Report page
~20 min later, after AI analysis
OPT 4 · 5

One timing fact shapes everything: the candidate's score is not ready at the congratulations screen — task grading runs asynchronously (~20-min Fargate analysis). So there is no "they just saw a great result, ask now" moment. Every peak-moment ask must be framed around the experience — was the task realistic, was the process fair — never their performance.

The six options

Ordered gentlest → most forceful. Each shows a faithful mockup in the real candidate design system.

Option 1 · least intrusive

Dismissible toast → compact modal

🎉 CONFETTI

Congratulations!

Visit your report for performance details and suggestions.

View Report
Enjoyed the task? Leave a 20-second note (optional)
Leave review
Toast slides in ~1.5s after submit, bottom-centre. Ignoring it fades it forever.

A sonner toast fires on the congratulations screen; its button opens a small responsive modal (Dialog on desktop, bottom-sheet on mobile) with one star tap and one optional text box.

IntrusiveLow EffortS — reuses everything Best forProduct insight (b)
  • 1Confetti + "Congratulations!" shows as it does today.
  • 2A toast slides in: "Leave a 20-second note", easy to dismiss.
  • 3Ignored → fades and never returns. The View Report CTA was never blocked.
  • 4Tapped → modal with a StarRating + one optional box, framed on experience.
Strengths
  • Gentlest possible ask; best fit for "don't irritate".
  • Event-triggered micro-survey — highest honest response rates.
  • 100% existing primitives; smallest surface.
Weaknesses
  • Toasts are easy to miss → lower absolute volume.
  • Can be overlooked on mobile if they tap View Report fast.
  • Two taps to rate (toast → open → star).
Senja: writes to our Supabase only. A human later hand-copies the best consented quotes into Senja's 15 slots (or our own wall).
Option 2 · pilot only

Quiet link under the View Report button

Throwaway pilot
🎉 CONFETTI

Congratulations!

Visit your report for performance details and suggestions.

View Report
A single muted line beneath the CTA opens Senja's own form in a new tab.

One quiet line below the green CTA opens Senja's hosted form with name/email prefilled. Zero backend, zero database — the cheapest thing to ship, and the one to reach for only as a quick "do candidates even engage?" probe.

IntrusiveLow EffortS — ships in an hour Best forWebsite proof (a) — pilot
Strengths
  • Smallest possible build; no migration.
  • Senja handles consent + GDPR + video free.
  • Fast way to test appetite.
Weaknesses
  • Bypasses our DB — loses data for goals (b) and (c).
  • 15-cap fills uncontrolled; a busy day exhausts it.
  • Sends an anxious candidate to a watermarked third-party page.
  • New-tab redirect has the lowest completion of any option.
Senja: pure PATH A. Direct redirect to the single free hosted form. We keep no copy of the data.
Option 3 · Phase 1 pick

Inline card at the confetti peak

★ Recommended
🎉 CONFETTI

Congratulations!

How was your assessment experience?

About the task, not your performance. Optional, ~20s, no effect on your result.

What would you tell a future candidate?
Submit
View Report
Card sits in-column above the CTA. Scroll past it and nothing is blocked.

A soft card embedded in the congratulations column, above the CTA, with an inline star tap and optional text — captured at the true emotional peak. A two-step gate reveals a public-facing box on 4–5★ and a private "what would you change?" box on 1–3★, so every rating is stored.

IntrusiveLow–Med EffortM Best forProduct (b) + website (a)
  • 1Submit → confetti + "Congratulations!" (the designed peak).
  • 2A gentle card appears with an inline StarRating — one tap, no modal.
  • 34–5★ reveals a public box; 1–3★ reveals a private "what would you change?".
  • 4Submit → card swaps to "Thanks — this helps us improve." Never blocks the CTA.
Strengths
  • Hooks the genuine peak-end moment → best memory, honest engagement.
  • Stores detractor feedback too → real product data, FTC-clean.
  • Owns the data; reusable for client decks.
  • Feels part of the celebration — no new tab, no modal.
Weaknesses
  • More visible than a toast — a small minority may feel nudged.
  • Must frame on experience (score isn't ready yet).
  • Slightly more UI logic than OPT-1/2.
Senja: the primary capture surface. POSTs to our Supabase; best consented + approved quotes hand-loaded to Senja free or our own wall.
Option 4 · Phase 2 pick

Persistent card on the report page

Share your experience

Now that your assessment is complete — may we feature what you write?

This task felt like real work, not a puzzle…
You can feature this publicly on Utkrushta's site and share with clients.
Show me as:
First nameFirst + initialFull name
Share
Always present in the report; never pops up. The natural home for the publish opt-in.

An always-visible, never-auto-opening card on the report page. Because it lives after the result is known, it's the right place to ask the explicit publish-consent opt-in with name-display granularity — the moment the power imbalance has relaxed.

IntrusiveLow EffortM Best forWebsite (a) + clients (c)
Strengths
  • Zero interrupt — least "nagging" persistent form.
  • Post-result page is the correct place for publish consent (GDPR).
  • Org / position / score context all available for a richer ask.
Weaknesses
  • Only reachable after analysis — misses anyone who never revisits.
  • Also the page lower-scorers feel worst on — must stay experience-framed.
  • Standalone report has no sidebar today; card goes inline.
Senja: captures the granular publish consent. Approved quotes hand-loaded to Senja / our own wall.
Option 5 · highest volume, highest interrupt

Auto-opening modal on report open (gated)

Clones the proven FeedbackModal. Opens once, only for candidates who passed.

Mirror the existing (test-session) FeedbackModal exactly: on the report, once analysis is done, the candidate passed, and no prior feedback exists, auto-open a modal. Highest completion of any option — and the biggest interrupt.

IntrusiveHigh EffortM — near-perfect template exists Best forWebsite volume (a)
Strengths
  • Highest completion — an auto-modal at a good moment converts best.
  • Proven pattern already live; lowest design risk.
  • Built-in one-shot guard prevents re-prompting.
Weaknesses
  • Auto-opening is inherently the most intrusive.
  • Gated to passers → never hears detractors (a data blind spot).
  • Misses the peak; needs a report revisit.
Senja: POSTs to Supabase mirroring the existing dup-guard. Approved quotes synced by hand (or later, the DAG).
Option 6 · compliance-grade

Two-moment split — private rating, deferred consent

MOMENT 1 · AT THE PEAK

How was your experience?

Stored privately. Won't be seen until your assessment is complete.

MOMENT 2 · AFTER THE OUTCOME
"This task felt like real work, not a puzzle."
May we feature this? Optional — your process is complete, so it changes nothing.
Capture the rating privately at the peak; ask to publish only once the result is out.

Split the ask across two light touches: capture the star + quote at the peak as private data (lawful basis: legitimate interest), then request explicit publish consent later — on the report card or a post-outcome email — once the evaluation is over and consent is defensibly "freely given".

IntrusiveMedium, spread EffortL — two surfaces + email Best forClients (c) + website (a)
Strengths
  • Strongest GDPR/DPDP posture — defeats the power-imbalance problem (Recital 43).
  • Widest honest data at the peak; clean, granular publishing.
  • Withdrawal + moderation as first-class states.
Weaknesses
  • Most engineering — two surfaces + email + richer schema.
  • Deferred email must be frequency-capped once per candidate.
  • Some give a great private quote but never return to consent.
Senja: two writes — private capture, then a consent flip. Only consented + approved rows are ever promoted to Senja / our wall.

Why the phased OPT-3 → OPT-4 combo wins

It threads three findings that pull in different directions.

1 · The peak is the right moment, but it must be about the experience. The peak-end rule says ask at the emotional high — here, the relief of finishing. But grading is async, so we can't hook a "great score". OPT-3 asks at the confetti peak and frames strictly around the task and process. Its two-step gate stores every rating, so we also learn from the candidates who struggled — the feedback most worth having, and the segment an auto-modal gated to passers (OPT-5) would never hear. Recruiting's assessment stage carries a famously negative candidate-NPS; hearing the unhappy quietly and privately is how that improves.

2 · Publishing consent belongs after the outcome. Asking an anxious candidate mid-evaluation for permission to use their words publicly is close to coercive — and under GDPR/DPDP a consent obtained under that power imbalance is hard to defend as "freely given". OPT-4 moves the publish opt-in to the report page, after the result is known, where the imbalance has relaxed. That is where OPT-6's two-moment logic pays off without OPT-6's full cost.

3 · The storage design is right regardless of Senja. Every phase writes to our own Supabase — the collection half of PATH B, which we already have the FeedbackModal pattern to clone. Senja stays a manual, free display shelf (or we skip it and build our own Wall of Love with the design system, no watermark). If we ever decide $29/mo is worth automated sync, the schema below already has the columns to flip it on.

Guardrails to hold across every phase

GuardrailWhy
Text default; video strictly optionalWe already spent the candidate's on-camera willingness on the mandatory explainer video. Don't double-dip.
No cash incentiveRecognition / LinkedIn-badge only, never conditioned on positivity (FTC rule on reviews).
Ask onceReuse the !user_feedback single-shot guard the test flow already uses.
Never gate anythingThe report, the result, and the candidacy must never sit behind the testimonial.
Repeat the anti-coercion line"Separate from your evaluation · no effect on your result" everywhere the ask appears.

The exact words

Ready-to-use microcopy, tuned for the evaluative context.

At the peak · private capture (OPT-3)

"How was your assessment experience? This is about the task and the process, not your performance. It is optional, takes about 20 seconds, is reviewed separately after your assessment, and has no effect on your result or candidacy."

On 4–5★: "Glad it went well! Anything you'd tell a future candidate about what this was like? (optional)"
On 1–3★: "Thanks for the honesty — what would have made this better? (this is private and goes to our product team)."

The publish opt-in · after the outcome (OPT-4)

"☐ You can feature what I wrote publicly. I understand Utkrushta may show my testimonial on its website and marketing and share it with its clients and prospective clients. Show me as: ◯ First name only ◯ First name + last initial ◯ Full name. ☐ (optional) You can include my photo. ☐ (optional) You can include my video. This is completely optional and does not affect your assessment, result, or candidacy in any way. You can withdraw consent anytime — email privacy@utkrusht.ai or use the link we'll send — and we'll remove it. See our Privacy Policy."

The anti-coercion line · repeat everywhere

"This is optional and completely separate from your evaluation — it will not be seen until your assessment is complete and has no effect on your result."

Implementation sketch

Grounded in the existing backend patterns. Design the whole thing now; ship the free half first.

Frontend

  • Clone FeedbackModal/StarRating into an inline TaskExperienceCard on EndPage.tsx.
  • Two-step star gate; swap-to-thanks state; never blocks the CTA.
  • New route /api/taskSessions/submitFeedback mirroring the test one — plus a candidate-auth + ownership check.
  • Phase 2: persistent consent card on tasks-report.

Backend + DB

  • New dedicated testimonials table (not a JSONB blob — a moderation + sync state machine needs indexed columns).
  • Write path: Flask POST /v2/task-sessions/<id>/testimonial, copying the IDOR ownership guard next to the report route; classify EITHER_ROLE.
  • Own TestimonialDAO + Pydantic model (P0 DAO rule — no raw supabase.table() outside a DAO).
  • Denormalise source_hashcode onto the row so org-scoped surfaces can filter (same rule as apply rows).

Senja bridge (later, optional)

  • SenjaClient modelled on dodo_client.py — env SENJA_API_KEY, fail-loud if missing, documented in the same PR.
  • Push via an Airflow poller DAG, not inline — the sync happens after human approval, minutes-to-days later, and must be durable + retryable.
  • Sync only where consent_public AND approval_status='APPROVED' AND senja_sync_status IN ('PENDING','FAILED').
-- dedicated table (write as a migration, don't push). RLS + policies + FK actions per house rules.
CREATE TABLE testimonials (
  testimonial_id   uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  created_at timestamptz DEFAULT now(),  updated_at timestamptz DEFAULT now(),
  -- context, denormalised off the session at write time
  tasksession_id   uuid REFERENCES task_sessions(tasksession_id) ON DELETE SET NULL,
  user_id          uuid REFERENCES users(user_id)               ON DELETE SET NULL,
  position_id      uuid,   source_hashcode text,   -- org-visibility key
  -- content
  rating smallint CHECK (rating BETWEEN 1 AND 5),  quote text NOT NULL,
  author_name text,  author_headline text,
  -- gates
  consent_public  boolean NOT NULL DEFAULT false,
  approval_status text NOT NULL DEFAULT 'PENDING' CHECK (approval_status IN ('PENDING','APPROVED','REJECTED')),
  approved_by uuid,  approved_at timestamptz,
  -- Senja sync bookkeeping (dormant until we ever pay)
  senja_sync_status text NOT NULL DEFAULT 'NONE' CHECK (senja_sync_status IN ('NONE','PENDING','SYNCED','FAILED')),
  senja_id text,  synced_at timestamptz,  sync_error text
);
CREATE UNIQUE INDEX testimonials_tasksession_uidx ON testimonials(tasksession_id);
CREATE INDEX testimonials_moderation_idx ON testimonials(approval_status, senja_sync_status);

Decisions for you

Eight calls to make before implementation — a few change the schema.

1Website display: hand-load ~15 best into Senja's watermarked free shelf, or build our own Wall of Love with the design system (no cap, no watermark, but we maintain the widget)? This decides whether Senja is even in the loop at launch.
2Pay later? Willing to spend $29/mo on Senja Starter eventually for automated push? If yes, greenlight the full testimonials table + poller DAG now. If never, a simpler task_sessions.user_feedback JSONB may suffice for the private data.
3Schema: dedicated testimonials table (recommended — first-class moderation + sync + consent) vs a JSONB column on task_sessions (cheaper, symmetric with test sessions, awkward to query a review queue).
4Write path: Flask POST /v2/task-sessions/<id>/testimonial (backend, keeps any future Senja key server-side) vs a Next.js /api/… route writing Supabase directly (symmetric with the test flow). Pick one.
5FK on erasure: SET NULL (keep a published quote even if the session/user is purged) vs CASCADE (GDPR erasure removes the quote too). House rules require this be explicit.
6CTA green: brand #1B6740, the brighter #1DB954, or match EndPage's existing green-600? Currently inconsistent — pick deliberately.
7Deferred-consent channel: a post-outcome email (needs a template + frequency cap, once per candidate) vs only the report card (no email, but misses non-returners).
8Recognition: offer a LinkedIn-shareable skill badge / "wall of candidates" as the non-cash thank-you? Must be framed for honest feedback, never conditioned on positivity.

Sources. Senja pricing + free-vs-paid help docs and REST API reference (verified 10 Jul 2026); candidate-app files (EndPage.tsx, FeedbackModal.tsx, StarRating.tsx, design tokens in globals.css); backend (task_sessions model + DAO, Flask v2_task_sessions blueprint, dodo_client.py external-API pattern, Airflow DAG conventions); testimonial-UX literature (peak-end rule, NPS/promoter-gating, FTC review guidance, GDPR Recital 43 on freely-given consent). One item to verify before relying on it: whether Senja issues an API key at all on a free account, and whether CSV import is free-tier and counts against the 15-cap.

Design exploration for discussion — not an implementation commitment. Ship Phase 1 behind the existing candidate flow; nothing here requires paying Senja.