If the Embed recorder is not working on your site, such as Wix or Webflow, please follow the following steps.
Usually, websites like Wix or Webflow remove the Microphone and Camera permission which creates an issue for a recorder on any browser.
Please include the below script to include the permission in your iFrame dynamically.
<script>
function addPermissions(iframe) {
const ALLOW_PERMISSIONS = 'camera; microphone; fullscreen; display-capture';
if (iframe.allow === ALLOW_PERMISSIONS) { return }
iframe.setAttribute('allow', ALLOW_PERMISSIONS)
iframe.allow = ALLOW_PERMISSIONS
iframe.src = iframe.src
}
document.querySelectorAll('iframe').forEach(addPermissions);
const observer = new MutationObserver((mutationsList, observer) => {
for (let mutation of mutationsList) {
if (mutation.addedNodes && mutation.addedNodes.length) {
for (let node of mutation.addedNodes) {
if (node.querySelectorAll) {
node.querySelectorAll('iframe').forEach(addPermissions)
}
}
}
}
})
observer.observe(document.body, { attributes: false, childList: true, subtree: true })
</script>Please ensure to include the above script to End of the Body to include the permission to all the iFrames on the page.
For Wix:
Here’re a few steps to follow for the Wix website.
Go to Settings and scroll to Advanced(the last section), and you can see Custom Code.
Include the script code under the body - END option.
That will ask you to apply on all the pages or specific pages.
Iframe Controls - Quick Start Guide
5-Minute Quick Start, Speak AI Recorder Embed
Step 1: Basic Iframe Embedding
<!-- Minimal Embedding -->
<iframe
src="https://recorder.speakai.co/iframe/YOUR_TOKEN_HERE"
allow="microphone; camera"
width="100%"
height="700px">
</iframe>Step 2: Add Query Parameters
<!-- With Query Parameters -->
<iframe
src="https://recorder.speakai.co/iframe/YOUR_TOKEN_HERE?hideWaveform=true&hideTitle=true&submitLabel=Send"
allow="microphone; camera"
width="100%"
height="700px">
</iframe>Step 3: Add PostMessage Control
// Get iframe reference
const iframe = document.querySelector('iframe');
// Start recording
iframe.contentWindow.postMessage({ action: 'start' }, 'https://recorder.speakai.co');
// Stop recording
iframe.contentWindow.postMessage({ action: 'stop' }, 'https://recorder.speakai.co');
// Listen for responses
window.addEventListener('message', (event) => {
if (event.origin !== 'https://recorder.speakai.co') return;
const response = JSON.parse(event.data);
console.log('Response:', response);
});⚙️ Query Parameters Reference
| Parameter | Type | Default | Description |
|---|---|---|---|
| hideWaveform | boolean | false | Hide audio waveform visualization |
| hideTitle | boolean | false | Hide title/header text |
| submitLabel | string | “Upload” | Custom submit button text |
| hideSubmit | boolean | false | Hide submit button |
| preselect | string | “audio” | “video” | “upload” | “screenshare” | Pre-select recording type |
| name | string | “” | Pre-fill name field |
| string | “” | Pre-fill email field | |
| folderId | string | “” | Pre-fill folder ID |
| field1 - field10 | string | “” | Pre-fill custom question answers (up to 10) |
Examples
Hide waveform:
?hideWaveform=true
Hide title:
?hideTitle=true
Custom button:
?submitLabel=Send%20Recording
All combined:
?hideWaveform=true&hideTitle=true&submitLabel=Complete
Pre-select recording type:
?preselect=video
Pre-fill name:
?name=John%20Doe
Pre-fill email:
?email=john.doe@example.com
Pre-fill folder ID:
?folderId=123456
Pre-fill custom question answers:
?field1=Answer%201&field2=Answer%202&field3=Answer%203🔄 PostMessage API Reference
Commands (Parent → Iframe)
// Start recording
{
action: 'start',
timestamp: Date.now() // optional
}
// Stop recording
{
action: 'stop',
timestamp: Date.now() // optional
}Responses (Iframe → Parent)
// Success
{
source: 'speak-embed-recorder',
status: 'success',
message: 'Recording started',
timestamp: '2025-10-09T10:30:00.000Z',
data: { action: 'start' }
}
// Error
{
source: 'speak-embed-recorder',
status: 'error',
message: 'Recording already in progress',
timestamp: '2025-10-09T10:30:00.000Z'
}💡 Common Patterns
Pattern 1: Minimal UI
<iframe
src="https://recorder.speakai.co/iframe/TOKEN?hideWaveform=true&hideTitle=true"
allow="microphone; camera"
style="width: 100%; height: 500px; border: none;">
</iframe>Pattern 2: Custom Branding
<iframe
src="https://recorder.speakai.co/iframe/TOKEN?submitLabel=Submit%20to%20Support"
allow="microphone; camera">
</iframe>Pattern 3: External Controls
<div class="recording-controls">
<button onclick="startRecording()">🔴 Start</button>
<button onclick="stopRecording()">⏹ Stop</button>
<div id="status">Ready</div>
</div>
<iframe id="recorder" src="https://recorder.speakai.co/iframe/TOKEN"></iframe>
<script>
const iframe = document.getElementById('recorder');
const status = document.getElementById('status');
function startRecording() {
iframe.contentWindow.postMessage({action: 'start'}, '*');
status.textContent = 'Recording...';
}
function stopRecording() {
iframe.contentWindow.postMessage({action: 'stop'}, '*');
status.textContent = 'Stopped';
}
window.addEventListener('message', (e) => {
const response = JSON.parse(e.data);
if (response.status === 'error') {
status.textContent = 'Error: ' + response.message;
}
});
</script>⚙️ Troubleshooting
Iframe Not Loading
const iframe = document.querySelector('iframe');
console.log('Iframe loaded:', iframe.contentWindow !== null);
iframe.addEventListener('load', () => {
console.log('Iframe loaded successfully');
});PostMessage Not Working
const iframe = document.querySelector('iframe');
console.log('Iframe found:', iframe !== null);
console.log('ContentWindow:', iframe.contentWindow);
function sendDebugMessage(action) {
console.log('Sending:', action);
iframe.contentWindow.postMessage({ action: action }, '*');
console.log('Message sent');
}
sendDebugMessage('start');Parameters Not Applied
const iframe = document.querySelector('iframe');
console.log('Iframe src:', iframe.src);
const url = new URL(iframe.src);
console.log('hideWaveform:', url.searchParams.get('hideWaveform'));
console.log('hideTitle:', url.searchParams.get('hideTitle'));
console.log('submitLabel:', url.searchParams.get('submitLabel'));⚛️ Framework Examples
React
import { useEffect, useRef } from 'react';
function RecorderEmbed({ token }) {
const iframeRef = useRef(null);
useEffect(() => {
const handleMessage = (event) => {
if (event.origin !== 'https://recorder.speakai.co') return;
const response = JSON.parse(event.data);
console.log('Recorder:', response);
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
const startRecording = () => {
iframeRef.current?.contentWindow.postMessage({ action: 'start' }, 'https://recorder.speakai.co');
};
const stopRecording = () => {
iframeRef.current?.contentWindow.postMessage({ action: 'stop' }, 'https://recorder.speakai.co');
};
return (
<div>
<div>
<button onClick={startRecording}>Start</button>
<button onClick={stopRecording}>Stop</button>
</div>
<iframe
ref={iframeRef}
src={`https://recorder.speakai.co/iframe/${token}?hideWaveform=true`}
allow="microphone; camera"
style=#{{ width: '100%', height: '700px', border: 'none' }}
/>
</div>
);
}Vue
<template>
<div>
<div>
<button @click="startRecording">Start</button>
<button @click="stopRecording">Stop</button>
</div>
<iframe
ref="recorder"
:src="iframeUrl"
allow="microphone; camera"
style="width: 100%; height: 700px; border: none"
/>
</div>
</template>
<script>
export default {
props: ['token'],
computed: {
iframeUrl() {
return `https://recorder.speakai.co/iframe/${this.token}?hideWaveform=true`;
}
},
mounted() {
window.addEventListener('message', this.handleMessage);
},
beforeUnmount() {
window.removeEventListener('message', this.handleMessage);
},
methods: {
handleMessage(event) {
if (event.origin !== 'https://recorder.speakai.co') return;
const response = JSON.parse(event.data);
console.log('Recorder:', response);
},
startRecording() {
this.$refs.recorder.contentWindow.postMessage(
{ action: 'start' },
'https://recorder.speakai.co'
);
},
stopRecording() {
this.$refs.recorder.contentWindow.postMessage(
{ action: 'stop' },
'https://recorder.speakai.co'
);
}
}
}
</script>Angular
import { Component, ElementRef, ViewChild, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'app-recorder',
template: `
<div>
<button (click)="startRecording()">Start</button>
<button (click)="stopRecording()">Stop</button>
</div>
<iframe
#recorder
[src]="iframeUrl"
allow="microphone; camera"
style="width: 100%; height: 700px; border: none">
</iframe>
`
})
export class RecorderComponent implements OnInit, OnDestroy {
@ViewChild('recorder') iframeElement: ElementRef;
iframeUrl = 'https://recorder.speakai.co/iframe/TOKEN?hideWaveform=true';
ngOnInit() {
window.addEventListener('message', this.handleMessage);
}
ngOnDestroy() {
window.removeEventListener('message', this.handleMessage);
}
handleMessage = (event: MessageEvent) => {
if (event.origin !== 'https://recorder.speakai.co') return;
const response = JSON.parse(event.data);
console.log('Recorder:', response);
};
startRecording() {
const iframe = this.iframeElement.nativeElement as HTMLIFrameElement;
iframe.contentWindow.postMessage({ action: 'start' }, 'https://recorder.speakai.co');
}
stopRecording() {
const iframe = this.iframeElement.nativeElement as HTMLIFrameElement;
iframe.contentWindow.postMessage({ action: 'stop' }, 'https://recorder.speakai.co');
}
}Support
For issues or questions:
- Check the troubleshooting section above
- Review the full test plan document
- Use the test embedder page to debug
- Check the browser console for error messages
- Contact us at success@speakai.co
New to Speak AI? Create a Speak AI account and work through Getting Started.
Iframe controls
5-Minute Quick Start, Speak AI Recorder Embed
Step 1: Basic Iframe Embedding
<!-- Minimal Embedding -->
<iframe
src="https://recorder.speakai.co/iframe/YOUR_TOKEN_HERE"
allow="microphone; camera"
width="100%"
height="700px">
</iframe>Step 2: Add Query Parameters
<!-- With Query Parameters -->
<iframe
src="https://recorder.speakai.co/iframe/YOUR_TOKEN_HERE?hideWaveform=true&hideTitle=true&submitLabel=Send"
allow="microphone; camera"
width="100%"
height="700px">
</iframe>Step 3: Add PostMessage Control
// Get iframe reference
const iframe = document.querySelector('iframe');
// Start recording
iframe.contentWindow.postMessage({ action: 'start' }, 'https://recorder.speakai.co');
// Stop recording
iframe.contentWindow.postMessage({ action: 'stop' }, 'https://recorder.speakai.co');
// Listen for responses
window.addEventListener('message', (event) => {
if (event.origin !== 'https://recorder.speakai.co') return;
const response = JSON.parse(event.data);
console.log('Response:', response);
});⚙️ Query Parameters Reference
| Parameter | Type | Default | Description |
|---|---|---|---|
| hideWaveform | boolean | false | Hide audio waveform visualization |
| hideTitle | boolean | false | Hide title/header text |
| submitLabel | string | “Upload” | Custom submit button text |
| hideSubmit | boolean | false | Hide submit button |
| preselect | string | “audio” | “video” | “upload” | “screenshare” | Pre-select recording type |
| name | string | “” | Pre-fill name field |
| string | “” | Pre-fill email field | |
| folderId | string | “” | Pre-fill folder ID |
| field1 - field10 | string | “” | Pre-fill custom question answers (up to 10) |
Examples
Hide waveform:
?hideWaveform=true
Hide title:
?hideTitle=true
Custom button:
?submitLabel=Send%20Recording
All combined:
?hideWaveform=true&hideTitle=true&submitLabel=Complete
Pre-select recording type:
?preselect=video
Pre-fill name:
?name=John%20Doe
Pre-fill email:
?email=john.doe@example.com
Pre-fill folder ID:
?folderId=123456
Pre-fill custom question answers:
?field1=Answer%201&field2=Answer%202&field3=Answer%203🔄 PostMessage API Reference
Commands (Parent → Iframe)
// Start recording
{
action: 'start',
timestamp: Date.now() // optional
}
// Stop recording
{
action: 'stop',
timestamp: Date.now() // optional
}Responses (Iframe → Parent)
// Success
{
source: 'speak-embed-recorder',
status: 'success',
message: 'Recording started',
timestamp: '2025-10-09T10:30:00.000Z',
data: { action: 'start' }
}
// Error
{
source: 'speak-embed-recorder',
status: 'error',
message: 'Recording already in progress',
timestamp: '2025-10-09T10:30:00.000Z'
}💡 Common Patterns
Pattern 1: Minimal UI
<iframe
src="https://recorder.speakai.co/iframe/TOKEN?hideWaveform=true&hideTitle=true"
allow="microphone; camera"
style="width: 100%; height: 500px; border: none;">
</iframe>Pattern 2: Custom Branding
<iframe
src="https://recorder.speakai.co/iframe/TOKEN?submitLabel=Submit%20to%20Support"
allow="microphone; camera">
</iframe>Pattern 3: External Controls
<div class="recording-controls">
<button onclick="startRecording()">🔴 Start</button>
<button onclick="stopRecording()">⏹ Stop</button>
<div id="status">Ready</div>
</div>
<iframe id="recorder" src="https://recorder.speakai.co/iframe/TOKEN"></iframe>
<script>
const iframe = document.getElementById('recorder');
const status = document.getElementById('status');
function startRecording() {
iframe.contentWindow.postMessage({action: 'start'}, '*');
status.textContent = 'Recording...';
}
function stopRecording() {
iframe.contentWindow.postMessage({action: 'stop'}, '*');
status.textContent = 'Stopped';
}
window.addEventListener('message', (e) => {
const response = JSON.parse(e.data);
if (response.status === 'error') {
status.textContent = 'Error: ' + response.message;
}
});
</script>⚙️ Troubleshooting
Iframe Not Loading
const iframe = document.querySelector('iframe');
console.log('Iframe loaded:', iframe.contentWindow !== null);
iframe.addEventListener('load', () => {
console.log('Iframe loaded successfully');
});PostMessage Not Working
const iframe = document.querySelector('iframe');
console.log('Iframe found:', iframe !== null);
console.log('ContentWindow:', iframe.contentWindow);
function sendDebugMessage(action) {
console.log('Sending:', action);
iframe.contentWindow.postMessage({ action: action }, '*');
console.log('Message sent');
}
sendDebugMessage('start');Parameters Not Applied
const iframe = document.querySelector('iframe');
console.log('Iframe src:', iframe.src);
const url = new URL(iframe.src);
console.log('hideWaveform:', url.searchParams.get('hideWaveform'));
console.log('hideTitle:', url.searchParams.get('hideTitle'));
console.log('submitLabel:', url.searchParams.get('submitLabel'));⚛️ Framework Examples
React
import { useEffect, useRef } from 'react';
function RecorderEmbed({ token }) {
const iframeRef = useRef(null);
useEffect(() => {
const handleMessage = (event) => {
if (event.origin !== 'https://recorder.speakai.co') return;
const response = JSON.parse(event.data);
console.log('Recorder:', response);
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
const startRecording = () => {
iframeRef.current?.contentWindow.postMessage({ action: 'start' }, 'https://recorder.speakai.co');
};
const stopRecording = () => {
iframeRef.current?.contentWindow.postMessage({ action: 'stop' }, 'https://recorder.speakai.co');
};
return (
<div>
<div>
<button onClick={startRecording}>Start</button>
<button onClick={stopRecording}>Stop</button>
</div>
<iframe
ref={iframeRef}
src={`https://recorder.speakai.co/iframe/${token}?hideWaveform=true`}
allow="microphone; camera"
style=#{{ width: '100%', height: '700px', border: 'none' }}
/>
</div>
);
}Vue
<template>
<div>
<div>
<button @click="startRecording">Start</button>
<button @click="stopRecording">Stop</button>
</div>
<iframe
ref="recorder"
:src="iframeUrl"
allow="microphone; camera"
style="width: 100%; height: 700px; border: none"
/>
</div>
</template>
<script>
export default {
props: ['token'],
computed: {
iframeUrl() {
return `https://recorder.speakai.co/iframe/${this.token}?hideWaveform=true`;
}
},
mounted() {
window.addEventListener('message', this.handleMessage);
},
beforeUnmount() {
window.removeEventListener('message', this.handleMessage);
},
methods: {
handleMessage(event) {
if (event.origin !== 'https://recorder.speakai.co') return;
const response = JSON.parse(event.data);
console.log('Recorder:', response);
},
startRecording() {
this.$refs.recorder.contentWindow.postMessage(
{ action: 'start' },
'https://recorder.speakai.co'
);
},
stopRecording() {
this.$refs.recorder.contentWindow.postMessage(
{ action: 'stop' },
'https://recorder.speakai.co'
);
}
}
}
</script>Angular
import { Component, ElementRef, ViewChild, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'app-recorder',
template: `
<div>
<button (click)="startRecording()">Start</button>
<button (click)="stopRecording()">Stop</button>
</div>
<iframe
#recorder
[src]="iframeUrl"
allow="microphone; camera"
style="width: 100%; height: 700px; border: none">
</iframe>
`
})
export class RecorderComponent implements OnInit, OnDestroy {
@ViewChild('recorder') iframeElement: ElementRef;
iframeUrl = 'https://recorder.speakai.co/iframe/TOKEN?hideWaveform=true';
ngOnInit() {
window.addEventListener('message', this.handleMessage);
}
ngOnDestroy() {
window.removeEventListener('message', this.handleMessage);
}
handleMessage = (event: MessageEvent) => {
if (event.origin !== 'https://recorder.speakai.co') return;
const response = JSON.parse(event.data);
console.log('Recorder:', response);
};
startRecording() {
const iframe = this.iframeElement.nativeElement as HTMLIFrameElement;
iframe.contentWindow.postMessage({ action: 'start' }, 'https://recorder.speakai.co');
}
stopRecording() {
const iframe = this.iframeElement.nativeElement as HTMLIFrameElement;
iframe.contentWindow.postMessage({ action: 'stop' }, 'https://recorder.speakai.co');
}
}Support
For issues or questions:
- Check the troubleshooting section above
- Review the full test plan document
- Use the test embedder page to debug
- Check the browser console for error messages
- Contact us at success@speakai.co
New to Speak AI? Create a Speak AI account and work through Getting Started.
Evaluating Speak AI for a team? Book a demo and see your own recordings analyzed.
Related: Embeddable recorder · Browser support