Web Integration
The easiest way to add live classrooms to your web app is through iframe embedding. You get the full classroom experience—video, controls, everything—with minimal code.
Basic Embedding
Once you have a join URL (from the Create Room API), embed it like this:
<iframe
id="verriflo-classroom"
src="https://live.verriflo.com/iframe/live?token=YOUR_TOKEN"
allow="camera; microphone; fullscreen; display-capture"
allowfullscreen
style="width: 100%; height: 600px; border: none;"
></iframe>
That's it. The classroom will load with all controls and functionality.
Required Permissions
The allow attribute is important. Here's what each permission does:
| Permission | Purpose |
|---|---|
camera | For instructor video |
microphone | For instructor audio |
fullscreen | For fullscreen mode |
display-capture | For screen sharing |
Without these, some features won't work.
Handling Events
The classroom sends events to the parent page via postMessage. Set up a listener:
window.addEventListener("message", (event) => {
// Security check
if (!event.origin.includes("verriflo.com")) return;
const { type, action } = event.data;
// Handle classroom lifecycle
if (type === "verriflo_classroom") {
switch (action) {
case "class_ended":
// Teacher ended the class
handleClassEnded();
break;
case "participant_removed":
// User was kicked
handleKicked();
break;
case "participant_left":
case "close":
// User left voluntarily
handleUserLeft();
break;
}
}
// Handle fullscreen requests
if (type === "verriflo_fullscreen") {
const iframe = document.getElementById("verriflo-classroom");
if (action === "enter") {
iframe.requestFullscreen();
} else if (action === "exit") {
document.exitFullscreen();
}
}
});
See Iframe Events for the complete event reference.
Responsive Sizing
Fixed Aspect Ratio (16:9)
For video content, maintaining aspect ratio looks best:
.classroom-container {
position: relative;
width: 100%;
padding-bottom: 56.25%; /* 16:9 aspect ratio */
}
.classroom-container iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: none;
}
<div class="classroom-container">
<iframe
src="https://live.verriflo.com/iframe/live?token=YOUR_TOKEN"
allow="camera; microphone; fullscreen; display-capture"
allowfullscreen
></iframe>
</div>
Full Height
Fill the available viewport:
.classroom-fullheight {
width: 100%;
height: calc(100vh - 64px); /* Account for your navbar */
border: none;
}
Mobile Responsive
Adjust for different screen sizes:
.classroom-responsive {
width: 100%;
height: 400px;
border: none;
}
@media (min-width: 768px) {
.classroom-responsive {
height: 500px;
}
}
@media (min-width: 1024px) {
.classroom-responsive {
height: 600px;
}
}
Modal/Dialog Pattern
Common pattern: open the classroom in a modal when a user clicks "Join Class":
<!-- Trigger button -->
<button onclick="openClassroom()">Join Live Class</button>
<!-- Modal -->
<div id="classroom-modal" class="modal hidden">
<div class="modal-content">
<button onclick="closeClassroom()" class="close-btn">×</button>
<iframe
id="classroom-iframe"
allow="camera; microphone; fullscreen; display-capture"
allowfullscreen
></iframe>
</div>
</div>
<style>
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal.hidden {
display: none;
}
.modal-content {
position: relative;
width: 90%;
max-width: 1200px;
height: 80vh;
background: #000;
border-radius: 12px;
overflow: hidden;
}
.modal-content iframe {
width: 100%;
height: 100%;
border: none;
}
.close-btn {
position: absolute;
top: 10px;
right: 10px;
z-index: 10;
background: rgba(0, 0, 0, 0.5);
color: white;
border: none;
width: 36px;
height: 36px;
border-radius: 50%;
font-size: 24px;
cursor: pointer;
}
</style>
<script>
async function openClassroom() {
// Get token from your backend
const response = await fetch("/api/join-class", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
roomId: "current-class-id",
studentName: "Current User",
studentEmail: "user@example.com",
}),
});
const { iframeUrl } = data.data;
// Set iframe source and show modal
document.getElementById("classroom-iframe").src = iframeUrl;
document.getElementById("classroom-modal").classList.remove("hidden");
}
function closeClassroom() {
document.getElementById("classroom-modal").classList.add("hidden");
document.getElementById("classroom-iframe").src = "";
}
// Listen for classroom events
window.addEventListener("message", (event) => {
if (!event.origin.includes("verriflo.com")) return;
if (event.data.type === "verriflo_classroom") {
// Auto-close modal when class ends or user leaves
closeClassroom();
if (event.data.action === "class_ended") {
alert("Class has ended!");
}
}
});
</script>
React Integration
import { useEffect, useRef, useState } from "react";
function VerrifloClassroom({ token, onClassEnded, onKicked }) {
const iframeRef = useRef(null);
useEffect(() => {
function handleMessage(event) {
if (!event.origin.includes("verriflo.com")) return;
const { type, action } = event.data;
if (type === "verriflo_classroom") {
if (action === "class_ended") onClassEnded?.();
if (action === "participant_removed") onKicked?.();
}
if (type === "verriflo_fullscreen") {
if (action === "enter") {
iframeRef.current?.requestFullscreen();
} else {
document.exitFullscreen();
}
}
}
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, [onClassEnded, onKicked]);
return (
<iframe
ref={iframeRef}
src={`https://live.verriflo.com/iframe/live?token=${token}`}
allow="camera; microphone; fullscreen; display-capture"
allowFullScreen
style={{
width: "100%",
height: "600px",
border: "none",
borderRadius: "8px",
}}
/>
);
}
// Usage
function ClassPage() {
const [token, setToken] = useState(null);
useEffect(() => {
// Fetch token from your backend
fetchJoinToken().then(setToken);
}, []);
if (!token) return <div>Loading...</div>;
return (
<VerrifloClassroom
token={token}
onClassEnded={() => {
alert("Class ended!");
window.location.href = "/feedback";
}}
onKicked={() => {
alert("You were removed from the class");
window.location.href = "/dashboard";
}}
/>
);
}
Next.js Integration
"use client";
import { useEffect, useRef } from "react";
import { useRouter } from "next/navigation";
interface ClassroomProps {
token: string;
}
export function Classroom({ token }: ClassroomProps) {
const iframeRef = useRef<HTMLIFrameElement>(null);
const router = useRouter();
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (!event.origin.includes("verriflo.com")) return;
const { type, action } = event.data;
if (type === "verriflo_classroom") {
switch (action) {
case "class_ended":
router.push("/class-ended");
break;
case "participant_removed":
router.push("/dashboard?removed=true");
break;
case "participant_left":
case "close":
router.back();
break;
}
}
if (type === "verriflo_fullscreen") {
if (action === "enter") {
iframeRef.current?.requestFullscreen();
} else {
document.exitFullscreen();
}
}
};
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, [router]);
return (
<div className="aspect-video w-full">
<iframe
ref={iframeRef}
src={`https://live.verriflo.com/iframe/live?token=${token}`}
allow="camera; microphone; fullscreen; display-capture"
allowFullScreen
className="w-full h-full border-0 rounded-lg"
/>
</div>
);
}
Troubleshooting
Iframe Blocked by Browser
Some browsers block iframes with camera/microphone access if they're on a different origin. Make sure you're serving your page over HTTPS.
Fullscreen Not Working
The fullscreen API requires user interaction. It won't work if triggered automatically on page load.
Events Not Firing
- Check your origin verification logic
- Make sure the iframe has loaded before listening
- Check browser console for errors
Safari Issues
Safari is more restrictive. Ensure:
- You're on HTTPS
allowattribute includescamera; microphone- User has granted permissions at the OS level
Next: Backend Integration — Server-side setup and best practices.