Embed Widget
Embed TalentSprout interviews directly on your careers page, job postings, or ATS. Candidates complete interviews without leaving your site.
Quick Start
Get your interview ID
Find your interview ID in the dashboard, or copy the embed code from the Share modal.
Add the iframe to your page
Paste the embed code on your careers page or job posting. The allow attribute is required for camera and microphone access.
<iframe
src="https://www.talentsprout.ai/embed/interview/YOUR_INTERVIEW_ID"
allow="camera; microphone; autoplay; fullscreen; display-capture"
width="100%"
height="700"
style="border: none; border-radius: 12px;"
></iframe>Important: The allow="camera; microphone; autoplay" attribute is required. Without it, candidates won't be able to use their camera or microphone for the interview.
Pre-fill Candidate Data
If you already have candidate information (from your ATS or application form), you can pre-fill it to skip the candidate info collection step.
<iframe
src="https://www.talentsprout.ai/embed/interview/YOUR_INTERVIEW_ID?firstName=John&lastName=Doe&email=john@company.com&phone=%2B1234567890"
allow="camera; microphone; autoplay; fullscreen; display-capture"
width="100%"
height="700"
style="border: none; border-radius: 12px;"
></iframe>| Parameter | Description |
|---|---|
firstName | Candidate's first name |
lastName | Candidate's last name |
email | Email address (*required to auto-skip info step) |
phoneNumber | Phone number (any format, auto-normalized to E.164) |
externalUserId | Your internal candidate ID |
Tip: When firstName, lastName, and email are all provided, the candidate info step is automatically skipped.
Events
Listen for events from the embedded widget using the postMessage API. This allows you to react to interview progress on your parent page.
| Event | Data |
|---|---|
talentsprout:ready | interviewId, interviewTitle |
talentsprout:started | interviewId, sessionId |
talentsprout:ended | interviewId, sessionId, sessionToken, candidateId, duration |
talentsprout:interrupted | interviewId, sessionId, sessionToken, reason |
talentsprout:completed | interviewId, sessionId, sessionToken, candidateId, status, scores |
talentsprout:abandoned | interviewId, sessionId, sessionToken, candidateId, status, reason |
talentsprout:request_close | interviewId, sessionId, sessionToken, candidateId |
talentsprout:evaluation_timeout | interviewId, sessionId, sessionToken |
// Listen for events from the embedded interview
window.addEventListener('message', (event) => {
// Verify origin for security
if (event.origin !== 'https://www.talentsprout.ai') return;
const { type, data, timestamp } = event.data;
switch (type) {
case 'talentsprout:ready':
console.log('Widget loaded and ready');
break;
case 'talentsprout:started':
console.log('Interview started', data.sessionId);
break;
case 'talentsprout:ended':
// Interview conversation finished - evaluation in progress
console.log('Interview ended', data.sessionId);
// Show "evaluating" message to user if desired
break;
case 'talentsprout:completed':
// Interview evaluated and passed completion checks (matches webhook)
console.log('Interview completed!', data);
// Enable submit button, redirect, etc.
// data includes: sessionId, scores.interview, scores.overall
break;
case 'talentsprout:abandoned':
// Interview evaluated but failed completion checks (matches webhook)
console.log('Interview abandoned:', data.reason);
// Show retry option or appropriate message
break;
case 'talentsprout:interrupted':
// Connection lost during interview
console.log('Interview interrupted:', data.reason);
break;
case 'talentsprout:request_close':
// Candidate clicked Done (when configured to request close)
console.log('Close requested', data.sessionId);
break;
case 'talentsprout:evaluation_timeout':
// Evaluation taking longer than expected (~55s)
console.log('Evaluation timeout - check webhook for results');
break;
}
});Styling Options
The embed widget is designed to work on any page. Here are some common layout patterns.
Responsive Container
<!-- Responsive container with 16:9 aspect ratio -->
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
<iframe
src="https://www.talentsprout.ai/embed/interview/YOUR_INTERVIEW_ID"
allow="camera; microphone; autoplay; fullscreen; display-capture"
style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; border-radius: 12px;"
></iframe>
</div>Full Page Embed
<!-- Full-height embed for dedicated interview pages -->
<div style="height: 100vh; min-height: 700px;">
<iframe
src="https://www.talentsprout.ai/embed/interview/YOUR_INTERVIEW_ID"
allow="camera; microphone; autoplay; fullscreen; display-capture"
width="100%"
height="100%"
style="border: none;"
></iframe>
</div>Mobile App Integration
Embed TalentSprout interviews in your native iOS, Android, or React Native mobile apps using WebViews. The code examples below include full event handling so your app can react when interviews start and complete.
Camera & Mic
WebRTC works reliably in modern WebViews
Event Callbacks
Receive ready, started, and completed events
Copy & Paste
Production-ready code for each platform
Compatibility: iOS 14+, Android 10+, React Native 0.60+. The same talentsprout:* events from the web embed are available in mobile apps.
iOS (Swift / SwiftUI)
Use WKWebView with WKScriptMessageHandler for events. Add NSCameraUsageDescription and NSMicrophoneUsageDescription to your Info.plist.
import SwiftUI
import WebKit
struct InterviewWebView: UIViewRepresentable {
let interviewId: String
var onReady: (() -> Void)?
var onStarted: ((String) -> Void)?
var onCompleted: ((String, String?, Bool) -> Void)?
func makeUIView(context: Context) -> WKWebView {
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
config.mediaTypesRequiringUserActionForPlayback = []
// Set up message handler for events
let contentController = WKUserContentController()
contentController.add(context.coordinator, name: "talentsprout")
config.userContentController = contentController
// Inject script to forward postMessage events to native
let script = WKUserScript(source: """
window.addEventListener('message', function(event) {
if (event.data && event.data.type &&
event.data.type.startsWith('talentsprout:')) {
window.webkit.messageHandlers.talentsprout.postMessage(event.data);
}
});
""", injectionTime: .atDocumentEnd, forMainFrameOnly: false)
contentController.addUserScript(script)
let webView = WKWebView(frame: .zero, configuration: config)
let url = URL(string: "https://www.talentsprout.ai/embed/interview/\(interviewId)")!
webView.load(URLRequest(url: url))
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {}
func makeCoordinator() -> Coordinator { Coordinator(self) }
class Coordinator: NSObject, WKScriptMessageHandler {
var parent: InterviewWebView
init(_ parent: InterviewWebView) { self.parent = parent }
func userContentController(_ controller: WKUserContentController,
didReceive message: WKScriptMessage) {
guard let body = message.body as? [String: Any],
let type = body["type"] as? String else { return }
let data = body["data"] as? [String: Any]
switch type {
case "talentsprout:ready":
parent.onReady?()
case "talentsprout:started":
parent.onStarted?(data?["sessionId"] as? String ?? "")
case "talentsprout:completed":
parent.onCompleted?(
data?["sessionId"] as? String ?? "",
data?["candidateId"] as? String,
data?["success"] as? Bool ?? false
)
default: break
}
}
}
}
// Usage:
// InterviewWebView(
// interviewId: "YOUR_INTERVIEW_ID",
// onCompleted: { sessionId, candidateId, success in
// print("Interview completed: \(sessionId)")
// }
// )Android (Kotlin)
Use addJavascriptInterface to receive events and WebChromeClient for camera/mic permissions. Add permissions to your AndroidManifest.xml.
import android.webkit.*
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import org.json.JSONObject
class InterviewActivity : AppCompatActivity() {
private lateinit var webView: WebView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val interviewId = intent.getStringExtra("INTERVIEW_ID") ?: return
webView = WebView(this).apply {
settings.javaScriptEnabled = true
settings.mediaPlaybackRequiresUserGesture = false
settings.domStorageEnabled = true
// Add JavaScript interface for receiving events
addJavascriptInterface(TalentSproutBridge(), "TalentSproutNative")
webChromeClient = object : WebChromeClient() {
override fun onPermissionRequest(request: PermissionRequest) {
runOnUiThread { request.grant(request.resources) }
}
}
// Inject event forwarding script after page loads
webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
view?.evaluateJavascript("""
window.addEventListener('message', function(event) {
if (event.data && event.data.type &&
event.data.type.startsWith('talentsprout:')) {
TalentSproutNative.onEvent(JSON.stringify(event.data));
}
});
""".trimIndent(), null)
}
}
loadUrl("https://www.talentsprout.ai/embed/interview/$interviewId")
}
setContentView(webView)
}
inner class TalentSproutBridge {
@JavascriptInterface
fun onEvent(jsonData: String) {
val json = JSONObject(jsonData)
val type = json.getString("type")
val data = json.optJSONObject("data")
runOnUiThread {
when (type) {
"talentsprout:ready" -> onInterviewReady()
"talentsprout:started" -> onInterviewStarted(
data?.optString("sessionId") ?: ""
)
"talentsprout:completed" -> onInterviewCompleted(
data?.optString("sessionId") ?: "",
data?.optString("candidateId"),
data?.optBoolean("success") ?: false
)
}
}
}
}
private fun onInterviewReady() { /* Widget loaded */ }
private fun onInterviewStarted(sessionId: String) { /* Interview began */ }
private fun onInterviewCompleted(sessionId: String, candidateId: String?, success: Boolean) {
// Navigate to results or thank you screen
finish()
}
}
// AndroidManifest.xml - Add these permissions:
// <uses-permission android:name="android.permission.CAMERA" />
// <uses-permission android:name="android.permission.RECORD_AUDIO" />
// <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />⚛React Native
Use react-native-webview with onMessage for event callbacks. Request native permissions on Android before loading.
import React, { useRef, useEffect, useCallback } from 'react';
import { WebView } from 'react-native-webview';
import { Platform, PermissionsAndroid } from 'react-native';
interface InterviewScreenProps {
interviewId: string;
onReady?: () => void;
onStarted?: (sessionId: string) => void;
onCompleted?: (data: { sessionId: string; candidateId?: string; success: boolean }) => void;
}
export function InterviewScreen({
interviewId,
onReady,
onStarted,
onCompleted
}: InterviewScreenProps) {
const webViewRef = useRef(null);
// Request permissions on Android
useEffect(() => {
if (Platform.OS === 'android') {
PermissionsAndroid.requestMultiple([
PermissionsAndroid.PERMISSIONS.CAMERA,
PermissionsAndroid.PERMISSIONS.RECORD_AUDIO,
]);
}
}, []);
// Script to forward postMessage events to React Native
const injectedJavaScript = `
window.addEventListener('message', function(event) {
if (event.data && event.data.type && event.data.type.startsWith('talentsprout:')) {
window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
}
});
true;
`;
// Handle messages from WebView
const handleMessage = useCallback((event) => {
try {
const { type, data } = JSON.parse(event.nativeEvent.data);
switch (type) {
case 'talentsprout:ready':
onReady?.();
break;
case 'talentsprout:started':
onStarted?.(data?.sessionId);
break;
case 'talentsprout:completed':
onCompleted?.(data);
break;
}
} catch (e) {
console.warn('Failed to parse WebView message:', e);
}
}, [onReady, onStarted, onCompleted]);
return (
<WebView
ref={webViewRef}
source={{ uri: `https://www.talentsprout.ai/embed/interview/${interviewId}` }}
injectedJavaScript={injectedJavaScript}
onMessage={handleMessage}
mediaPlaybackRequiresUserAction={false}
allowsInlineMediaPlayback={true}
javaScriptEnabled={true}
domStorageEnabled={true}
allowsFullscreenVideo={true}
androidLayerType="hardware"
onPermissionRequest={(request) => request.grant()}
style={{ flex: 1 }}
/>
);
}
// Usage:
// <InterviewScreen
// interviewId="YOUR_INTERVIEW_ID"
// onCompleted={(data) => {
// navigation.navigate('ThankYou', { sessionId: data.sessionId });
// }}
// />How Events Work
The TalentSprout widget sends events via postMessage. The code examples above inject a script that forwards these to the native layer via each platform's JavaScript bridge.
Permissions: Your app must request camera and microphone permissions from the OS before the WebView can access these devices. Users will see the native permission dialog first.
Security
HTTPS Required
Camera and microphone access requires a secure context. Your page must be served over HTTPS.
Browser Permissions
Candidates will be prompted to allow camera/microphone access by their browser.
Origin Verification
When listening for postMessage events, always verify event.origin to prevent spoofing.
Interview Validation
The widget validates candidate eligibility server-side before starting interviews.
Examples
Use the embed widget for:
- Careers page with inline interviews
- Job posting pages on job boards
- ATS integration with embedded screening
- Application confirmation pages
Need help with your integration?
Our team is here to help you get set up.