Candidate Flow
OTP Verification
How candidates verify their identity via a one-time password before joining an interview.
When a candidate opens their interview link, they must verify email ownership before accessing the interview. A 6-digit OTP is sent to the email registered with the interview request, confirming the candidate is who they claim to be.
Verification Steps
| Step | Action | API Endpoint |
|---|---|---|
| 1 | Page loads — fetch interview info to display name and position | GET /candidate-access/:token/info |
| 2 | Candidate clicks Send Code — OTP emailed to masked address | POST /candidate-access/:token/request-otp |
| 3 | Candidate enters 6 digits — receives session token on success | POST /candidate-access/:token/verify-otp |
Page: /interview/join/:token
The join page renders in three states as the candidate progresses:
| UI State | What Candidate Sees |
|---|---|
| Loading | Spinner while fetching interview info |
| Info loaded | Name, position, masked email (j***@e***.com), Send Code button |
| Code sent | 6-digit input, resend countdown, Verify button |
| Verified | Redirect to /interview/pre-check/:token |
text
/interview/join/abc123token
│
├── GET /candidate-access/abc123token/info
│ └── Shows: "Welcome, Jane Smith — Senior Engineer"
│
├── POST /candidate-access/abc123token/request-otp
│ └── "Verification code sent to j***@e***.com"
│
├── POST /candidate-access/abc123token/verify-otp { otp: "123456" }
│ └── sessionToken stored in sessionStorage
│
└── Navigate → /interview/pre-check/abc123tokenOTP Constraints
| Property | Value |
|---|---|
| Length | 6 digits |
| Validity | 10 minutes from generation |
| Rate limit | 5 requests per 10 minutes per token |
| Attempts | 3 invalid attempts triggers cooldown |
| Resend | Available after 60 second countdown |
Session Token
On successful OTP verification, the frontend stores these values in sessionStorage:
typescript
// Frontend stores after successful OTP verify
sessionStorage.setItem('interview_session_token', data.sessionToken);
sessionStorage.setItem('interview_token', token); // URL token
sessionStorage.setItem('interview_candidate_name', data.candidateName);
sessionStorage.setItem('interview_position', data.position);The sessionToken is a JWT valid for the duration of the interview. It is passed as the x-session-token header in the subsequent start call.
Error Handling
| Error | Cause | UI Response |
|---|---|---|
| 404 | Token expired or invalid | Full-page error: link expired message |
| 401 | Invalid OTP code | Inline error: incorrect code, n attempts remaining |
| 429 | Rate limit exceeded | Show cooldown timer, disable send button |
The masked email (
j***@e***.com) is shown before sending the OTP to help the candidate confirm they are at the right interview link without exposing the full email address.Was this page helpful?