TalentSprout
Integration Guide

Embed Widget

Embed TalentSprout interviews directly on your careers page, job postings, or ATS. Candidates complete interviews without leaving your site.

Quick Start

1

Get your interview ID

Find your interview ID in the dashboard, or copy the embed code from the Share modal.

2

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.

index.html
<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.

with-prefill.html
<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>
ParameterDescription
firstNameCandidate's first name
lastNameCandidate's last name
emailEmail address (*required to auto-skip info step)
phoneNumberPhone number (any format, auto-normalized to E.164)
externalUserIdYour 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.

EventData
talentsprout:readyinterviewId, interviewTitle
talentsprout:startedinterviewId, sessionId
talentsprout:endedinterviewId, sessionId, sessionToken, candidateId, duration
talentsprout:interruptedinterviewId, sessionId, sessionToken, reason
talentsprout:completedinterviewId, sessionId, sessionToken, candidateId, status, scores
talentsprout:abandonedinterviewId, sessionId, sessionToken, candidateId, status, reason
talentsprout:request_closeinterviewId, sessionId, sessionToken, candidateId
talentsprout:evaluation_timeoutinterviewId, sessionId, sessionToken
event-listener.js
// 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.html
<!-- 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-page.html
<!-- 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.

InterviewWebView.swift
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.

InterviewActivity.kt
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.

InterviewScreen.tsx
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.

Widgetwindow.parent.postMessage({ type: 'talentsprout:completed' })
WebViewInjected script catches event, forwards to native bridge
NativeYour callback receives the event data

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.

Contact Support
    Embed Widget Documentation - TalentSprout