Como Integrar Videochamadas com Next.js, React ou Angular em 30 Minutos
Guia prático com código pronto para copiar e colar. Do iframe mais simples até a integração completa com gravação, transcrição e webhooks — escolha o nível certo para o seu projeto.
Introdução
Adicionar videochamadas ao seu app não precisa ser um projeto de meses. Se você trabalha com WebRTC diretamente, sabe que envolve servidores STUN/TURN, sinalização, gerenciamento de mídia, compatibilidade entre navegadores, reconexão automática e uma lista interminável de edge cases. Mas com uma API pronta, o processo se resume a uma chamada REST e um iframe.
Neste tutorial, vamos cobrir três abordagens para integrar videochamadas da videochamada.com.br no seu projeto, independente do framework que você usa:
Iframe Simples
Crie a chamada via API, coloque a URL num iframe. Funciona em qualquer framework. Ideal para MVPs e ferramentas internas.
API + Iframe
Backend proxy para proteger a API key, componente dedicado, ciclo de vida da chamada. A abordagem recomendada.
Integração Completa
Tudo acima + gravação, transcrição, webhooks e personalização. Para apps de produção que precisam de controle total.
Cada seção inclui código completo e funcional para Next.js, React (Vite) e Angular. Você pode alternar entre os frameworks em cada exemplo usando as abas de código.
Pré-requisitos
Antes de começar, verifique se você tem tudo o que precisa. A configuração é mínima — não há SDKs para instalar ou bibliotecas de WebRTC para configurar.
Conta na videochamada.com.br
Crie uma conta gratuita em app.videochamada.com.br/cadastrar. Não precisa de cartão de crédito. Você recebe 2.000 minutos grátis por mês — suficiente para desenvolvimento, testes e até produção de projetos menores.
API Key
Após criar a conta, vá em Configurações > API Keys e gere sua chave. Você vai usá-la para autenticar todas as chamadas à API. Guarde-a em local seguro — ela dá acesso total à sua conta.
Projeto rodando em Next.js, React ou Angular
Qualquer projeto existente funciona. Se quiser começar do zero, use npx create-next-app@latest, npm create vite@latest ou ng new. A integração não depende de nenhuma biblioteca adicional — só HTTP requests e um iframe.
Documentação completa da API
Este tutorial cobre os endpoints essenciais. Para a referência completa com todos os parâmetros, status codes e exemplos, acesse documentacao.videochamada.com.br.
Método 1: Iframe Simples
A forma mais rápida de colocar videochamada no seu app. O fluxo é direto: crie uma chamada via API, pegue a URL retornada, e coloque num iframe. Funciona em qualquer framework — e até em HTML puro.
Passo 1: Crie uma chamada via API
Use curl para testar no terminal antes de integrar no código. A API retorna um objeto com o id da chamada e a url para acessá-la:
curl -X POST https://api.videochamada.com.br/calls \
-H "Authorization: Bearer SUA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"participantName": "Dr. Silva",
"participantRole": "host"
}'
# Resposta:
# {
# "id": "call-a1b2c3d4-e5f6-7890-abcd-ef1234567890",
# "url": "https://call.videochamada.com.br/sala-abc123",
# "status": "waiting",
# "createdAt": "2026-03-05T14:30:00.000Z"
# }O mesmo request via fetch no JavaScript:
const response = await fetch('https://api.videochamada.com.br/calls', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
participantName: 'Dr. Silva',
participantRole: 'host'
})
});
const call = await response.json();
console.log(call.url); // https://call.videochamada.com.br/sala-abc123Passo 2: Exiba no iframe
Com a URL da chamada em mãos, basta renderizar um iframe. Aqui vai o código para cada framework:
// components/VideoCall.tsx
import { useState } from 'react';
interface VideoCallProps {
callUrl: string;
onClose: () => void;
}
export default function VideoCall({ callUrl, onClose }: VideoCallProps) {
const [isLoading, setIsLoading] = useState(true);
return (
<div style={{
position: 'fixed',
inset: 0,
zIndex: 50,
backgroundColor: 'rgba(0,0,0,0.95)',
display: 'flex',
flexDirection: 'column'
}}>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '12px 16px',
backgroundColor: '#111'
}}>
<span style={{ color: '#fff', fontWeight: 600 }}>
Videochamada em andamento
</span>
<button
onClick={onClose}
style={{
backgroundColor: '#ef4444',
color: '#fff',
border: 'none',
padding: '8px 16px',
borderRadius: '6px',
cursor: 'pointer',
fontWeight: 600
}}
>
Encerrar
</button>
</div>
{isLoading && (
<div style={{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#fff'
}}>
Carregando videochamada...
</div>
)}
<iframe
src={callUrl}
onLoad={() => setIsLoading(false)}
allow="camera; microphone; display-capture"
style={{
flex: 1,
width: '100%',
border: 'none'
}}
/>
</div>
);
}Atenção: API Key no frontend
No exemplo do fetch acima, a API Key está no cliente. Isso funciona para testes rápidos, mas nunca faça isso em produção. Qualquer usuário pode inspecionar o código e roubar sua chave. Use um backend proxy — é o que vamos cobrir nos próximos métodos.
Quando usar este método?
- MVPs e protótipos onde velocidade é prioridade
- Ferramentas internas onde a API key pode ficar em variável de ambiente no server
- Provas de conceito para mostrar ao cliente
- Ambientes de staging/teste controlados
Método 2: Integração com Next.js
O Next.js é o framework ideal para essa integração porque oferece API Routes nativamente — você não precisa de um backend separado. A API Key fica segura no servidor, e o frontend só recebe a URL da chamada.
Passo 1: Configure as variáveis de ambiente
# .env.local (NUNCA commite este arquivo!)
VIDEOCHAMADA_API_KEY=sua_api_key_aqui
NEXT_PUBLIC_APP_URL=http://localhost:3000Passo 2: Crie a API Route
Esta rota funciona como proxy seguro. O frontend chama /api/calls do seu domínio, e o servidor repassa para a API da videochamada.com.br com a chave de autenticação. A API Key nunca chega ao navegador.
// pages/api/calls.ts
import type { NextApiRequest, NextApiResponse } from 'next';
interface CreateCallBody {
participantName: string;
participantRole?: 'host' | 'participant';
recording?: boolean;
transcription?: boolean;
}
interface CallResponse {
id: string;
url: string;
status: string;
createdAt: string;
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const apiKey = process.env.VIDEOCHAMADA_API_KEY;
if (!apiKey) {
console.error('VIDEOCHAMADA_API_KEY not configured');
return res.status(500).json({ error: 'Server configuration error' });
}
try {
const body: CreateCallBody = req.body;
const response = await fetch('https://api.videochamada.com.br/calls', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
participantName: body.participantName,
participantRole: body.participantRole || 'host',
recording: body.recording || false,
transcription: body.transcription || false
})
});
if (!response.ok) {
const error = await response.json();
return res.status(response.status).json(error);
}
const data: CallResponse = await response.json();
return res.status(200).json(data);
} catch (error) {
console.error('Error creating call:', error);
return res.status(500).json({ error: 'Failed to create call' });
}
}Passo 3: Crie o componente de videochamada
O componente gerencia o ciclo de vida completo: criar a chamada, exibir o iframe, e limpar ao encerrar. Usamos dynamic import para garantir que o componente só renderize no cliente (o iframe precisa do DOM do navegador).
// components/VideoCallManager.tsx
import { useState, useCallback } from 'react';
type CallStatus = 'idle' | 'creating' | 'active' | 'error';
interface CallData {
id: string;
url: string;
}
export default function VideoCallManager() {
const [status, setStatus] = useState<CallStatus>('idle');
const [callData, setCallData] = useState<CallData | null>(null);
const [error, setError] = useState<string | null>(null);
const createCall = useCallback(async () => {
setStatus('creating');
setError(null);
try {
const response = await fetch('/api/calls', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
participantName: 'Usuário',
participantRole: 'host'
})
});
if (!response.ok) {
throw new Error('Erro ao criar chamada');
}
const data = await response.json();
setCallData(data);
setStatus('active');
} catch (err) {
setError(err instanceof Error ? err.message : 'Erro desconhecido');
setStatus('error');
}
}, []);
const endCall = useCallback(() => {
setCallData(null);
setStatus('idle');
}, []);
if (status === 'active' && callData) {
return (
<div style={{
position: 'fixed', inset: 0, zIndex: 50,
background: 'rgba(0,0,0,0.95)',
display: 'flex', flexDirection: 'column'
}}>
<div style={{
display: 'flex', justifyContent: 'space-between',
alignItems: 'center', padding: '12px 16px',
background: '#111'
}}>
<span style={{ color: '#fff', fontWeight: 600 }}>
Chamada: {callData.id.slice(0, 8)}...
</span>
<button
onClick={endCall}
style={{
background: '#ef4444', color: '#fff',
border: 'none', padding: '8px 16px',
borderRadius: '6px', cursor: 'pointer',
fontWeight: 600
}}
>
Encerrar Chamada
</button>
</div>
<iframe
src={callData.url}
allow="camera; microphone; display-capture"
style={{ flex: 1, width: '100%', border: 'none' }}
/>
</div>
);
}
return (
<div style={{ padding: '24px' }}>
<button
onClick={createCall}
disabled={status === 'creating'}
style={{
background: status === 'creating' ? '#666' : '#2563eb',
color: '#fff', border: 'none',
padding: '12px 24px', borderRadius: '8px',
cursor: status === 'creating' ? 'wait' : 'pointer',
fontWeight: 600, fontSize: '16px'
}}
>
{status === 'creating' ? 'Criando chamada...' : 'Iniciar Videochamada'}
</button>
{error && (
<p style={{ color: '#ef4444', marginTop: '12px' }}>{error}</p>
)}
</div>
);
}Passo 4: Use na página com dynamic import
// pages/chamada.tsx
import dynamic from 'next/dynamic';
// Dynamic import evita SSR — o iframe precisa do browser
const VideoCallManager = dynamic(
() => import('../components/VideoCallManager'),
{ ssr: false, loading: () => <p>Carregando...</p> }
);
export default function ChamadaPage() {
return (
<div>
<h1>Sala de Videochamada</h1>
<VideoCallManager />
</div>
);
}Dicas para Next.js
- Use
dynamic importcomssr: falsepara componentes que usam iframe — evita erros de hydration - A API Route roda no servidor, então a API Key nunca é exposta ao cliente
- No App Router (Next.js 13+), use
app/api/calls/route.tscomNextResponseao invés depages/api - Para o App Router, adicione
'use client'no componente de vídeo
Método 3: Integração com React (Vite)
Se o seu projeto React roda no Vite (ou CRA), você não tem API routes nativas como no Next.js. Você precisa de um backend separado para proteger a API Key. Vamos usar um servidor Express mínimo como proxy, mas você pode substituir por Supabase Edge Functions, Cloudflare Workers, ou qualquer backend que já tenha.
Passo 1: Crie o backend proxy
Um servidor Express com uma única rota. Pode rodar separado ou no mesmo repositório:
// server/index.ts
import express from 'express';
import cors from 'cors';
const app = express();
app.use(cors({ origin: 'http://localhost:5173' })); // URL do Vite
app.use(express.json());
const API_KEY = process.env.VIDEOCHAMADA_API_KEY;
app.post('/api/calls', async (req, res) => {
try {
const response = await fetch('https://api.videochamada.com.br/calls', {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
participantName: req.body.participantName,
participantRole: req.body.participantRole || 'host',
recording: req.body.recording || false,
transcription: req.body.transcription || false
})
});
const data = await response.json();
if (!response.ok) {
return res.status(response.status).json(data);
}
res.json(data);
} catch (error) {
console.error('Proxy error:', error);
res.status(500).json({ error: 'Failed to create call' });
}
});
app.listen(3001, () => {
console.log('Proxy server running on http://localhost:3001');
});Alternativa sem Express: Se você já usa Supabase, crie uma Edge Function (como no guia do Lovable). Se usa Vercel, crie uma api/calls.ts Serverless Function. O importante é que a API Key fique no servidor.
Passo 2: Configure o frontend
# .env (frontend - SEM api key aqui!)
VITE_API_URL=http://localhost:3001Passo 3: Crie o hook useVideoCall
Encapsulamos toda a lógica de criação e gerenciamento da chamada num hook customizado. Isso mantém os componentes limpos e permite reutilizar a lógica em diferentes páginas.
// src/hooks/useVideoCall.ts
import { useState, useCallback } from 'react';
type CallStatus = 'idle' | 'creating' | 'active' | 'ended' | 'error';
interface CallData {
id: string;
url: string;
status: string;
}
interface UseVideoCallOptions {
participantName: string;
participantRole?: 'host' | 'participant';
recording?: boolean;
transcription?: boolean;
}
export function useVideoCall() {
const [status, setStatus] = useState<CallStatus>('idle');
const [callData, setCallData] = useState<CallData | null>(null);
const [error, setError] = useState<string | null>(null);
const apiUrl = import.meta.env.VITE_API_URL;
const createCall = useCallback(async (options: UseVideoCallOptions) => {
setStatus('creating');
setError(null);
try {
const response = await fetch(`${apiUrl}/api/calls`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(options)
});
if (!response.ok) {
const err = await response.json();
throw new Error(err.error || 'Erro ao criar chamada');
}
const data: CallData = await response.json();
setCallData(data);
setStatus('active');
return data;
} catch (err) {
const message = err instanceof Error ? err.message : 'Erro desconhecido';
setError(message);
setStatus('error');
return null;
}
}, [apiUrl]);
const endCall = useCallback(() => {
setCallData(null);
setStatus('ended');
}, []);
const reset = useCallback(() => {
setCallData(null);
setStatus('idle');
setError(null);
}, []);
return {
status,
callData,
error,
createCall,
endCall,
reset,
isActive: status === 'active',
isCreating: status === 'creating'
};
}Passo 4: Crie o componente
// src/components/VideoCallRoom.tsx
import { useEffect } from 'react';
import { useVideoCall } from '../hooks/useVideoCall';
export function VideoCallRoom() {
const {
status, callData, error,
createCall, endCall, reset, isCreating
} = useVideoCall();
// Cleanup: se o componente desmontar durante uma chamada, encerra
useEffect(() => {
return () => {
// Cleanup ao desmontar o componente
};
}, []);
if (status === 'active' && callData) {
return (
<div style={{
position: 'fixed', inset: 0, zIndex: 50,
background: '#000',
display: 'flex', flexDirection: 'column'
}}>
<div style={{
display: 'flex', justifyContent: 'space-between',
alignItems: 'center', padding: '12px 16px',
background: '#111', borderBottom: '1px solid #333'
}}>
<div>
<span style={{ color: '#fff', fontWeight: 600 }}>
Chamada ativa
</span>
<span style={{
marginLeft: '12px', color: '#4ade80',
fontSize: '14px'
}}>
ID: {callData.id.slice(0, 8)}
</span>
</div>
<button
onClick={endCall}
style={{
background: '#ef4444', color: '#fff',
border: 'none', padding: '8px 20px',
borderRadius: '6px', cursor: 'pointer',
fontWeight: 600
}}
>
Encerrar
</button>
</div>
<iframe
src={callData.url}
allow="camera; microphone; display-capture"
style={{ flex: 1, width: '100%', border: 'none' }}
/>
</div>
);
}
return (
<div style={{ padding: '32px', maxWidth: '400px' }}>
<h2 style={{ marginBottom: '16px', fontSize: '20px' }}>
Videochamada
</h2>
<button
onClick={() => createCall({
participantName: 'Usuário',
participantRole: 'host'
})}
disabled={isCreating}
style={{
width: '100%',
background: isCreating ? '#555' : '#2563eb',
color: '#fff', border: 'none',
padding: '14px 24px', borderRadius: '8px',
cursor: isCreating ? 'wait' : 'pointer',
fontWeight: 600, fontSize: '16px'
}}
>
{isCreating ? 'Criando chamada...' : 'Iniciar Videochamada'}
</button>
{error && (
<div style={{
marginTop: '12px', padding: '12px',
background: '#fef2f2', borderRadius: '6px',
color: '#dc2626', fontSize: '14px'
}}>
{error}
<button
onClick={reset}
style={{
marginLeft: '8px', textDecoration: 'underline',
background: 'none', border: 'none',
color: '#dc2626', cursor: 'pointer'
}}
>
Tentar novamente
</button>
</div>
)}
{status === 'ended' && (
<div style={{
marginTop: '12px', padding: '12px',
background: '#f0fdf4', borderRadius: '6px',
color: '#16a34a', fontSize: '14px'
}}>
Chamada encerrada com sucesso.
<button
onClick={reset}
style={{
marginLeft: '8px', textDecoration: 'underline',
background: 'none', border: 'none',
color: '#16a34a', cursor: 'pointer'
}}
>
Nova chamada
</button>
</div>
)}
</div>
);
}Dicas para React (Vite)
- O hook
useVideoCallencapsula toda a lógica — os componentes ficam focados em UI - Use o
useEffectcleanup para lidar com unmount durante uma chamada ativa - Variáveis de ambiente no Vite usam o prefixo
VITE_— sem ele, a variável não é exposta ao frontend - Configure o proxy do Vite em
vite.config.tspara evitar CORS em desenvolvimento
Método 4: Integração com Angular
No Angular, a integração segue o padrão de services e components que você já conhece. Criamos um VideoCallService para a comunicação com a API e um VideoCallComponent para renderizar o iframe. Assim como no React, você precisa de um backend proxy — usamos o mesmo servidor Express ou qualquer backend que você já tenha.
Passo 1: Configure o environment
// src/environments/environment.ts
export const environment = {
production: false,
apiUrl: 'http://localhost:3001' // URL do seu backend proxy
};// src/environments/environment.prod.ts
export const environment = {
production: true,
apiUrl: 'https://api.seudominio.com.br' // URL de produção do backend
};Passo 2: Crie o VideoCallService
// src/app/services/video-call.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { environment } from '../../environments/environment';
export interface CallData {
id: string;
url: string;
status: string;
createdAt: string;
}
export interface CreateCallOptions {
participantName: string;
participantRole?: 'host' | 'participant';
recording?: boolean;
transcription?: boolean;
}
export type CallStatus = 'idle' | 'creating' | 'active' | 'ended' | 'error';
@Injectable({
providedIn: 'root'
})
export class VideoCallService {
private statusSubject = new BehaviorSubject<CallStatus>('idle');
private callDataSubject = new BehaviorSubject<CallData | null>(null);
private errorSubject = new BehaviorSubject<string | null>(null);
status$ = this.statusSubject.asObservable();
callData$ = this.callDataSubject.asObservable();
error$ = this.errorSubject.asObservable();
constructor(private http: HttpClient) {}
createCall(options: CreateCallOptions): Observable<CallData> {
this.statusSubject.next('creating');
this.errorSubject.next(null);
return this.http
.post<CallData>(`${environment.apiUrl}/api/calls`, options)
.pipe(
tap((data) => {
this.callDataSubject.next(data);
this.statusSubject.next('active');
}),
catchError((err) => {
this.errorSubject.next(
err.error?.message || 'Erro ao criar chamada'
);
this.statusSubject.next('error');
throw err;
})
);
}
endCall(): void {
this.callDataSubject.next(null);
this.statusSubject.next('ended');
}
reset(): void {
this.callDataSubject.next(null);
this.statusSubject.next('idle');
this.errorSubject.next(null);
}
}Passo 3: Crie o VideoCallComponent
// src/app/components/video-call/video-call.component.ts
import { Component, OnDestroy } from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { Subscription } from 'rxjs';
import {
VideoCallService,
CallData,
CallStatus
} from '../../services/video-call.service';
@Component({
selector: 'app-video-call',
template: `
<!-- Botão para iniciar -->
<div *ngIf="status !== 'active'" class="call-controls">
<h2>Videochamada</h2>
<button
(click)="startCall()"
[disabled]="status === 'creating'"
class="start-btn"
[class.loading]="status === 'creating'"
>
{{ status === 'creating' ? 'Criando chamada...' : 'Iniciar Videochamada' }}
</button>
<div *ngIf="error" class="error-msg">
{{ error }}
<button (click)="reset()" class="retry-btn">Tentar novamente</button>
</div>
<div *ngIf="status === 'ended'" class="success-msg">
Chamada encerrada com sucesso.
<button (click)="reset()" class="retry-btn">Nova chamada</button>
</div>
</div>
<!-- Iframe da videochamada -->
<div *ngIf="status === 'active' && safeUrl" class="call-overlay">
<div class="call-toolbar">
<span class="call-id">
Chamada ativa
<small *ngIf="callData">ID: {{ callData.id | slice:0:8 }}</small>
</span>
<button (click)="endCall()" class="end-btn">Encerrar</button>
</div>
<iframe
[src]="safeUrl"
allow="camera; microphone; display-capture"
class="call-frame"
></iframe>
</div>
`,
styles: [`
.call-controls {
padding: 32px; max-width: 400px;
}
.call-controls h2 {
margin-bottom: 16px; font-size: 20px;
}
.start-btn {
width: 100%; background: #2563eb; color: #fff;
border: none; padding: 14px 24px; border-radius: 8px;
cursor: pointer; font-weight: 600; font-size: 16px;
}
.start-btn.loading {
background: #555; cursor: wait;
}
.error-msg {
margin-top: 12px; padding: 12px; background: #fef2f2;
border-radius: 6px; color: #dc2626; font-size: 14px;
}
.success-msg {
margin-top: 12px; padding: 12px; background: #f0fdf4;
border-radius: 6px; color: #16a34a; font-size: 14px;
}
.retry-btn {
margin-left: 8px; text-decoration: underline;
background: none; border: none; cursor: pointer;
color: inherit;
}
.call-overlay {
position: fixed; inset: 0; z-index: 50;
background: #000; display: flex;
flex-direction: column;
}
.call-toolbar {
display: flex; justify-content: space-between;
align-items: center; padding: 12px 16px;
background: #111; border-bottom: 1px solid #333;
}
.call-id { color: #fff; font-weight: 600; }
.call-id small {
margin-left: 12px; color: #4ade80; font-size: 14px;
}
.end-btn {
background: #ef4444; color: #fff; border: none;
padding: 8px 20px; border-radius: 6px;
cursor: pointer; font-weight: 600;
}
.call-frame {
flex: 1; width: 100%; border: none;
}
`]
})
export class VideoCallComponent implements OnDestroy {
status: CallStatus = 'idle';
callData: CallData | null = null;
error: string | null = null;
safeUrl: SafeResourceUrl | null = null;
private subscriptions = new Subscription();
constructor(
private videoCallService: VideoCallService,
private sanitizer: DomSanitizer
) {
this.subscriptions.add(
this.videoCallService.status$.subscribe(s => this.status = s)
);
this.subscriptions.add(
this.videoCallService.callData$.subscribe(data => {
this.callData = data;
this.safeUrl = data
? this.sanitizer.bypassSecurityTrustResourceUrl(data.url)
: null;
})
);
this.subscriptions.add(
this.videoCallService.error$.subscribe(e => this.error = e)
);
}
startCall(): void {
this.videoCallService.createCall({
participantName: 'Usuário',
participantRole: 'host'
}).subscribe();
}
endCall(): void {
this.videoCallService.endCall();
}
reset(): void {
this.videoCallService.reset();
}
ngOnDestroy(): void {
this.subscriptions.unsubscribe();
}
}Passo 4: Registre no módulo
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { VideoCallComponent } from './components/video-call/video-call.component';
@NgModule({
declarations: [
AppComponent,
VideoCallComponent
],
imports: [
BrowserModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
// No template do AppComponent ou qualquer outro:
// <app-video-call></app-video-call>Dicas para Angular
- Use
DomSanitizer.bypassSecurityTrustResourceUrl()para URLs no iframe — Angular bloqueia URLs dinâmicas por segurança - Sempre use
ngOnDestroypara fazer unsubscribe dos Observables e evitar memory leaks - Use
environment.tspara a URL da API — o Angular substitui automaticamente em build de produção - Para lazy loading, crie um
VideoCallModuleseparado e carregue-o sob demanda na rota
Recursos Avançados
Com a integração básica funcionando, você pode ativar recursos avançados com poucas linhas de código. Todos os recursos abaixo são ativados via parâmetros na criação da chamada — não requerem mudanças no frontend.
Gravação
R$ 0,075/min de gravação
Ative a gravação passando recording: true na criação da chamada. A gravação começa automaticamente quando o primeiro participante entra e para quando o último sai. Você recebe o arquivo via webhook.
{
"participantName": "Dr. Silva",
"participantRole": "host",
"recording": true
}Transcrição
R$ 0,0225/min de transcrição
A transcrição usa IA para converter o áudio da chamada em texto. Ative com transcription: true. O resultado é entregue via webhook no formato JSON com timestamps por fala.
{
"participantName": "Dr. Silva",
"participantRole": "host",
"recording": true,
"transcription": true
}Webhooks
Notificações em tempo real
Configure uma URL de webhook nas configurações da sua conta para receber eventos automaticamente. Os eventos disponíveis são:
call.startedQuando o primeiro participante entra na chamada
call.endedQuando o último participante sai da chamada
recording.readyQuando a gravação termina de processar
transcription.readyQuando a transcrição está pronta
// pages/api/webhooks/videochamada.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).end();
}
const event = req.body;
switch (event.type) {
case 'call.started':
console.log('Chamada iniciada:', event.data.callId);
// Atualize o status no seu banco de dados
break;
case 'call.ended':
console.log('Chamada encerrada:', event.data.callId);
console.log('Duração:', event.data.durationMinutes, 'min');
// Salve a duração, atualize billing, etc.
break;
case 'recording.ready':
console.log('Gravação pronta:', event.data.recordingUrl);
// Salve a URL, notifique o usuário, faça download
break;
case 'transcription.ready':
console.log('Transcrição pronta:', event.data.transcriptionUrl);
// Salve o texto, indexe para busca, etc.
break;
}
// Responda 200 para confirmar recebimento
res.status(200).json({ received: true });
}Personalização Whitelabel
Incluso em todos os planos
Personalize a interface de videochamada com as cores e logo da sua marca. A configuração é feita no painel da videochamada.com.br em Configurações > Whitelabel. As opções incluem:
- Cor primária e secundária da interface
- Logo da sua empresa na sala de chamada
- Textos customizados (sala de espera, botões)
- Background virtual padrão da empresa
Metadados do Participante
Dados adicionais na chamada
Passe informações adicionais sobre o participante que serão incluídas nos webhooks e relatórios. Útil para associar chamadas a usuários, agendamentos ou sessões no seu sistema.
{
"participantName": "Dr. Silva",
"participantRole": "host",
"metadata": {
"userId": "usr_12345",
"appointmentId": "apt_67890",
"specialty": "cardiologia",
"source": "meu-app-saude"
}
}Segurança
A segurança da integração depende de como você trata a API Key e valida o acesso às chamadas. Siga estas práticas para evitar problemas:
NUNCA exponha a API Key no frontend
A API Key dá acesso total à sua conta: criar chamadas, acessar gravações, gerenciar configurações. Se um usuário mal-intencionado capturar sua chave via DevTools, pode usá-la para consumir seus minutos, acessar gravações de outros usuários, ou gerar custos na sua conta.
ERRADO
// No React/Angular/frontend
const API_KEY = 'sk_live_abc123';
fetch(url, { headers: {
Authorization: `Bearer ${API_KEY}`
}});CERTO
// No frontend
fetch('/api/calls', {
method: 'POST',
body: JSON.stringify({ name })
});
// API Key fica no servidorSempre use um backend proxy
Toda comunicação com a API da videochamada.com.br deve passar pelo seu backend. No Next.js, use API Routes. No React (Vite) e Angular, use um servidor Express, NestJS, Supabase Edge Functions, ou Cloudflare Workers. O frontend só precisa chamar o seu backend — a API Key nunca cruza a rede do lado do cliente.
Valide o acesso antes de criar chamadas
No seu backend proxy, verifique se o usuário autenticado tem permissão para criar uma chamada. Se você tem um sistema de agendamentos, valide que o agendamento existe, pertence ao usuário, e está no horário correto antes de gerar o link. Isso evita que qualquer pessoa autenticada crie chamadas ilimitadas.
Use URLs de curta duração
Os links de chamada gerados pela API expiram automaticamente após o uso. Não armazene links no frontend por longos períodos — gere um novo link no momento que o usuário clica em "Iniciar Chamada". Isso garante que links compartilhados acidentalmente não fiquem acessíveis para sempre.
Comparação dos Métodos
Cada método descrito neste guia tem vantagens diferentes. Use esta tabela para escolher o que melhor se encaixa no momento do seu projeto — você sempre pode evoluir depois sem reescrever o que já fez.
| Critério | Iframe Simples | API + Iframe | Integração Completa |
|---|---|---|---|
| Tempo de implementação | 5 min | 15 min | 30 min |
| Segurança da API Key | Exposta no frontend | Protegida no servidor | Protegida no servidor |
| Complexidade | Mínima | Baixa | Média |
| Personalização visual | Whitelabel | Whitelabel | Whitelabel + wrapper UI |
| Gravação e transcrição | Via painel | Via API + webhooks | Via API + webhooks |
| Controle do ciclo de vida | Nenhum | Total | Total |
| Ideal para | MVPs, protótipos | Maioria dos projetos | Apps de produção robustos |
Recomendação:
Comece com o Método 2 (API + Iframe) — oferece o melhor equilíbrio entre segurança, velocidade de implementação e controle. Se estiver em modo protótipo urgente, use o Método 1 e migre depois. Adicione os recursos avançados (gravação, webhooks) conforme a demanda aparecer.
Perguntas Frequentes
Dúvidas comuns de desenvolvedores sobre a integração
Sobre Este Guia
Este guia foi escrito em Março de 2026 e testado com Next.js 14 (Pages e App Router), React 18 (Vite 5), e Angular 17. Os exemplos de código são completos e funcionais — você pode copiar e colar diretamente no seu projeto. A lógica de integração (backend proxy + iframe) é estável e não muda entre versões dos frameworks.
videochamada.com.br, Next.js, React, Vite e Angular são produtos independentes. Este guia é uma documentação técnica para ajudar desenvolvedores que usam esses frameworks a integrar videochamadas rapidamente.
