Pular para o conteúdo principal

Exemplos de Frontend

Receitas prontas pra ir do webhook ao chat na tela. Todos os exemplos usam o envelope canônico (idêntico em Baileys, WABA e Instagram) e o SDK. Copie, cole e adapte.

💭 Bubbles de chat⋯ Menu de contexto🎙️ Áudio & mídia🔳 Conexão por QR / pairing🔘 Mensagens interativas🛠️ Painel de canais🟢 Embedded Signup👥 Grupos

💭 Bubbles de chat

O webhook message:received traz um type e um content normalizados. Um único componente faz o switch e renderiza cada tipo — sem saber se veio de Baileys ou WABA.

// MessageBubble.tsx — renderiza o envelope canônico (igual em Baileys/WABA/IG)
import type { MessageReceivedEvent } from '@paulorbj/wpp-gateway-sdk';

export function MessageBubble({ msg, mine }: { msg: MessageReceivedEvent; mine: boolean }) {
return (
<div className={mine ? 'bubble bubble--mine' : 'bubble'}>
{msg.quotedMessageId && <QuotedPreview messageId={msg.quotedMessageId} />}
<Content msg={msg} />
<time className="bubble__time">{new Date(msg.timestamp).toLocaleTimeString()}</time>
</div>
);
}

function Content({ msg }: { msg: MessageReceivedEvent }) {
const c = msg.content as any;
switch (msg.type) {
case 'text':
return <p>{c.text}</p>;

case 'image':
// mídia vem no evento media:available (downloadUrl); ou GET /v1/media/:id
return <img className="bubble__img" src={msg.media?.downloadUrl} alt={c.caption ?? ''} />;

case 'audio':
return <audio controls src={msg.media?.downloadUrl} />;

case 'document':
return <a href={msg.media?.downloadUrl} download>{c.filename ?? 'documento'}</a>;

case 'location':
return <a href={`https://maps.google.com/?q=${c.latitude},${c.longitude}`}>📍 {c.name ?? 'Localização'}</a>;

case 'contact':
return <div className="vcard">{c.contacts?.map((ct: any) => ct.name).join(', ')}</div>;

case 'reaction':
return <span className="reaction">{c.emoji}</span>; // reagiu a c.targetMessageId

case 'button_reply':
case 'list_reply':
return <p className="reply-chip">{c.text}</p>; // usuário tocou um botão/item

default:
return <em>[mensagem não suportada]</em>;
}
}

⋯ Menu de contexto (reagir, responder, encaminhar, editar, apagar)

Toda ação usa o messageId do gateway (o que você recebeu no webhook ou no retorno do envio) — nunca o id cru do canal. Editar/apagar valem pras mensagens que você enviou.

// MessageActions.tsx — menu de contexto por messageId do gateway
import { WppGateway } from '@paulorbj/wpp-gateway-sdk';
const gw = new WppGateway({ baseUrl, apiKey });

export function MessageActions({ phoneId, messageId, mine }: Props) {
return (
<Menu>
{/* Reagir — qualquer mensagem */}
<MenuItem onClick={() => gw.messages.react(messageId, { phoneId, emoji: '❤️' })}>
Reagir ❤️
</MenuItem>

{/* Responder com citação — manda nova msg referenciando este messageId */}
<MenuItem onClick={() => onReply(messageId)}>Responder</MenuItem>

{/* Encaminhar — envia o conteúdo pra outro contato */}
<MenuItem onClick={() => onForward(messageId)}>Encaminhar</MenuItem>

{/* Editar — só mensagens que VOCÊ enviou (Baileys) */}
{mine && (
<MenuItem onClick={() => gw.messages.edit(messageId, { phoneId, text: 'Texto corrigido' })}>
Editar
</MenuItem>
)}

{/* Apagar pra todos */}
{mine && (
<MenuItem danger onClick={() => gw.messages.delete(messageId, phoneId)}>
Apagar
</MenuItem>
)}
</Menu>
);
}

// Responder com citação = enviar nova mensagem com quotedMessageId
async function onReply(quotedMessageId: string) {
await gw.messages.send({
phoneId, to, type: 'text',
content: { text: 'Claro!' },
quotedMessageId, // o messageId do gateway que você recebeu
});
}

🎙️ Áudio, imagem, documento — enviar e baixar

No envio você passa uma mediaUrl pública (ou um mediaHandle da Meta). No recebimento, o gateway baixa e guarda a mídia (TTL 7 dias) e serve por URL assinada — você nunca lida com S3 nem com IDs da Meta.

// 1) Enviar voice note (bolha de áudio) — ptt: true
await gw.messages.send({
phoneId, to, type: 'audio',
content: { mediaUrl: 'https://cdn.seuapp.com/audio.ogg', ptt: true },
});

// 2) Enviar imagem com legenda
await gw.messages.send({
phoneId, to, type: 'image',
content: { mediaUrl: 'https://cdn.seuapp.com/foto.jpg', caption: 'Olha isso 👀' },
});

// 3) Enviar documento (PDF)
await gw.messages.send({
phoneId, to, type: 'document',
content: { mediaUrl: 'https://cdn.seuapp.com/nota.pdf', filename: 'nota-fiscal.pdf' },
});

// 4) Baixar mídia RECEBIDA — o gateway guarda no S3 e serve por URL assinada.
// O download é via API Key; o SDK resolve o redirect 302 -> URL temporária.
const url = await gw.media.downloadUrl(mediaId); // 10 min de validade
// <audio src={url} /> | <img src={url} /> | window.open(url)

// (WABA) Reusar mídia já enviada à Meta: faça upload uma vez e use o handle
const { mediaHandle } = await fetch(baseUrl + '/v1/media/upload', {
method: 'POST',
headers: { 'X-API-Key': apiKey, 'X-Phone-Id': phoneId, 'Content-Type': 'application/json' },
body: JSON.stringify({ mediaUrl: 'https://cdn.seuapp.com/foto.jpg', mimeType: 'image/jpeg' }),
}).then((r) => r.json());
await gw.messages.send({ phoneId, to, type: 'image', content: { mediaHandle } });

🔳 Conexão por QR code / pairing code

O endpoint do QR devolve um dataUrl (PNG base64) pronto pra <img>. Os hooks do SDK fazem o polling do QR e do status (PENDING_QR → CONNECTED) pra você. Quer push em tempo real sem polling? Use o endpoint SSE direto.

// QrConnect.tsx — renderiza o QR e faz polling do status (hooks do SDK)
import { useQrCode, useChannelStatus } from '@paulorbj/wpp-gateway-react';

export function QrConnect({ phoneId }: { phoneId: string }) {
const { dataUrl } = useQrCode(phoneId); // poll GET /v1/phones/:id/qrcode (PNG base64)
const { status } = useChannelStatus(phoneId); // poll do status; para em CONNECTED/FAILED

if (status === 'CONNECTED') return <p>✅ Conectado!</p>;

return (
<div>
{!dataUrl && <Spinner />}
{dataUrl && <img src={dataUrl} alt="Escaneie no WhatsApp" width={264} height={264} />}
<p>Abra o WhatsApp › Aparelhos conectados › Conectar aparelho</p>
<small>Status: {status}</small>
</div>
);
}

// Sem o SDK React? O dataUrl já vem pronto pra <img>:
const { dataUrl } = await fetch(`${baseUrl}/v1/phones/${phoneId}/qrcode`, {
headers: { 'X-API-Key': apiKey },
}).then((r) => r.json());
// document.querySelector('img').src = dataUrl

// Pairing code (alternativa ao QR):
const { code } = await fetch(`${baseUrl}/v1/phones/${phoneId}/pairing-code`, {
method: 'POST',
headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json' },
body: JSON.stringify({ phoneNumber: '5511999998888' }),
}).then((r) => r.json()); // ex: "XXXX-XXXX"

🔘 Botões, listas e replies

Envie botões e listas; quando o usuário toca, o reply volta como button_reply / list_reply com o selectedId que você definiu.

// Enviar botões (até 3) — WABA/Baileys
await gw.messages.send({
phoneId, to, type: 'button',
content: {
text: 'Como posso ajudar?',
buttons: [
{ id: 'support', text: 'Suporte' },
{ id: 'sales', text: 'Vendas' },
],
},
});

// Enviar lista (menu com seções)
await gw.messages.send({
phoneId, to, type: 'list',
content: {
text: 'Escolha um plano',
buttonText: 'Ver planos',
sections: [{
title: 'Planos',
rows: [
{ id: 'basic', title: 'Básico', description: 'R$ 49/mês' },
{ id: 'pro', title: 'Pro', description: 'R$ 99/mês' },
],
}],
},
});

// Tratar o REPLY do usuário no webhook (message:received)
function onInbound(evt: MessageReceivedEvent) {
if (evt.type === 'button_reply' || evt.type === 'list_reply') {
const selectedId = (evt.content as any).selectedId; // 'support' | 'pro' | ...
handleSelection(selectedId, evt.from.canonicalKey);
}
}

🛠️ Painel de canais (listar, criar, status, remover)

Com o @paulorbj/wpp-gateway-react: envolva o app no <WppGatewayProvider> e use <PhoneList /> pronto, ou monte sua UI com usePhones, useChannelStatus e o client (createPhone, deletePhone, reconnect).

import {
WppGatewayProvider, usePhones, useChannelStatus,
useGatewayClient, PhoneList, StatusBadge, Spinner,
} from '@paulorbj/wpp-gateway-react';

// 1) Provider no topo do app (uma vez só)
function App() {
return (
<WppGatewayProvider baseUrl="https://wpp.ogmma.com.br" apiKey={apiKey}>
<Channels />
</WppGatewayProvider>
);
}

// 2) Componente pronto: lista + status + remover
function Channels() {
return <PhoneList allowDelete filterTypes={['BAILEYS', 'WABA']} />;
}

// 3) Ou monte do seu jeito com os hooks + client
function CustomPanel() {
const client = useGatewayClient();
const { phones, loading, refresh } = usePhones({ autoload: true });

const newBaileys = async () => {
const phone = await client.createPhone({ type: 'BAILEYS', label: 'Atendimento' });
refresh(); // phone.status === 'PENDING_QR' -> mostre o QR (receita acima)
};

if (loading) return <Spinner />;
return (
<div>
<button onClick={newBaileys}>+ Canal Baileys</button>
{phones.map((p) => (
<Row key={p.id} phone={p} onDelete={() => client.deletePhone(p.id).then(refresh)} />
))}
</div>
);
}

// 4) Status ao vivo de um canal (poll a cada 3s; para em CONNECTED/FAILED)
function Row({ phone, onDelete }) {
const client = useGatewayClient();
const { status } = useChannelStatus(phone.id);
return (
<div className="row">
<span>{phone.label}</span>
<StatusBadge status={status ?? phone.status} />
<button onClick={() => client.reconnect(phone.id)}>Reconectar</button>
<button onClick={onDelete}>Remover</button>
</div>
);
}

🟢 Embedded Signup (onboarding WABA oficial)

O onboarding oficial da Meta dentro do seu app — sem o cliente sair da sua tela e sem você lidar com tokens. Use o componente pronto ou controle o fluxo manualmente (config pública → popup da Meta → finalizar).

// Jeito fácil — componente pronto abre o popup oficial da Meta
import { WabaEmbeddedSignup } from '@paulorbj/wpp-gateway-react';

<WabaEmbeddedSignup
defaultLabel="Minha Empresa"
onConnected={(phone) => router.push(`/canais/${phone.id}`)} // já vem CONNECTED
/>;

// Por baixo: o fluxo manual (se quiser controlar a UI você mesmo)
const client = useGatewayClient();

// 1) Config pública (appId, configId, graphVersion) — sem segredos
const cfg = await client.getEmbeddedSignupConfig();

// 2) Carregue o Meta JS SDK e abra o popup com o configId
FB.login((res) => onMetaResponse(res), {
config_id: cfg.configId,
response_type: 'code',
override_default_response_type: true,
});

// 3) A Meta devolve { code, phone_number_id, waba_id }. Finalize no gateway:
const phone = await client.finishEmbeddedSignup({
code, phoneNumberId, wabaId,
label: 'Minha Empresa',
webhook: {
url: 'https://api.seuapp.com/wpp',
secret: 'segredo-de-16+-chars',
events: ['message:received', 'message:status'],
},
});
// phone.status === 'CONNECTED' — pronto pra enviar

👥 Gestão de grupos (WABA)

Criar grupos, gerenciar participantes e admins, link de convite e solicitações de entrada. Veja a referência de Grupos para todos os endpoints.

// O SDK não tem resource de grupos — use HTTP direto (X-API-Key).
const api = (path, init) =>
fetch(`${baseUrl}${path}`, {
...init,
headers: { 'X-API-Key': apiKey, 'Content-Type': 'application/json', ...init?.headers },
}).then((r) => (r.ok ? r.json().catch(() => ({})) : Promise.reject(r)));

// Criar grupo (WABA)
const group = await api('/v1/groups', {
method: 'POST',
body: JSON.stringify({ phoneId, subject: 'Time de Vendas', joinApprovalMode: 'APPROVAL_REQUIRED' }),
});

// Listar grupos do canal
const { items } = await api(`/v1/groups?phoneId=${phoneId}`);

// Participantes (números E.164) — adicionar / remover
await api(`/v1/groups/${group.id}/participants`, { method: 'POST', body: JSON.stringify({ waIds: ['5511999998888'] }) });
await api(`/v1/groups/${group.id}/participants`, { method: 'DELETE', body: JSON.stringify({ waIds: ['5511999998888'] }) });

// Promover admin + (re)gerar link de convite
await api(`/v1/groups/${group.id}/admins`, { method: 'POST', body: JSON.stringify({ waIds: ['5511999998888'] }) });
const { inviteLink } = await api(`/v1/groups/${group.id}/invite-link/reset`, { method: 'POST' });

// Aprovar quem pediu pra entrar pelo link
const reqs = await api(`/v1/groups/${group.id}/join-requests`);
await api(`/v1/groups/${group.id}/join-requests/approve`, {
method: 'POST',
body: JSON.stringify({ joinRequestIds: reqs.map((r) => r.joinRequestId) }),
});

// Enviar mensagem no grupo (payload no formato Cloud API)
await api(`/v1/groups/${group.id}/messages`, {
method: 'POST',
body: JSON.stringify({ type: 'text', payload: { text: { body: 'Bem-vindos! 🎉' } } }),
});

Precisa de mais? Veja a referência de mensagens, webhooks e o SDK React.