
A página de conexão hospedada que seu cliente vê — logotipo, cores e título personalizáveis, com um único botão Conectar Dispositivo.
Esta página é a referência — todas as opções, os dois providers, o protocolo postMessage completo e a tabela cheia do
branding.button. Se você está ligando o onboarding da API oficial pela primeira vez, comece pelo passo a passo: Embedded Signup no seu app percorre as quatro chamadas de ponta a ponta, com código de backend e frontend pra copiar. Volte aqui para o detalhe campo por campo.Diferença principal em relação ao Chat: o Connect não usa o token de
POST /v1/embed/sessions. Ele usa o token de pareamento remoto gerado por POST /v1/numbers/remote-pairing, e esse token fica no caminho da URL do iframe (/connect/<token>), não em memória via postMessage.Formato e validade do token
O token devolvido depende doprovider — não são o mesmo objeto e não compartilham a mesma validade:
Gere o link Meta no momento em que o usuário vai clicar, não horas antes — 30 minutos é curto de propósito. Quando expira, o iframe emite
connect:expired; gere um novo.
Fluxo
1
O backend cria o link de pareamento
201 nos dois casos. remotePairingUrl é https://connect.pilotstatus.com.br/connect/<token>; o token é o último segmento do caminho. Encaminhe apenas o token (ou a URL) para o seu frontend — nunca a chave ps_.Para provider: "META", name/number não são obrigatórios: o usuário final escolhe o número dentro do diálogo do Facebook. Para PILOT_STATUS, ambos são obrigatórios.warnings no pareamento por QR. O externalRef e o redirectUrl viajam dentro da sessão assinada do Meta, e o link de QR não tem sessão nenhuma — então no PILOT_STATUS eles são aceitos, mas não têm como ser honrados. Cada um que você enviar volta relatado no array warnings (string[]) do 201, e o pareamento continua dando certo; não é um 400. O campo não aparece quando não há nada a relatar. Leia esse array nos seus testes de integração — é assim que você descobre que um valor enviado foi ignorado em vez de aplicado.linkToApiKey foi aposentado — e o linkedApiKeyId vem sempre null. A opção saiu das três rotas de provisionamento (POST /v1/numbers, POST /v1/numbers/meta, POST /v1/numbers/remote-pairing). Enviá-la continua sendo aceito, então quem já manda esse campo hoje não quebra, mas ela não faz mais nada: nenhuma chave de API é tocada e o linkedApiKeyId do 201 nunca traz um id. Ela costumava reapontar uma chave com escopo de número para o número recém-criado, tirando o escopo do número ao qual ela estava de fato vinculada.O campo de branding por link é
branding (não brandingOverride). externalRef (máx. 200 caracteres) é o id do cliente no seu sistema — ele é assinado dentro do token Meta e devolvido para você no connect:paired.2
O frontend incorpora
O SDK monta a URL do iframe a partir do token — você passa apenas o token:
ConnectOptions: token (obrigatório), baseUrl? (padrão https://connect.pilotstatus.com.br), mode? ("page" | "button" — omita para automático, veja Modo botão), onPaired(d), onError(e), onExpired().Modo botão
O modo botão renderiza apenas o botão do Meta Embedded Signup — sem cabeçalho, sem card, sem linha do tempo, fundo transparente e altura controlada pela mensagemresize já existente. Todo o resto (token, protocolo postMessage, callbacks) é idêntico ao modo página.
Use quando você já tem a sua própria tela de onboarding e o card hospedado seria uma segunda UI competindo dentro dela. Use o modo página quando quiser o fluxo explicativo completo, ou para pareamento por QR.
Qual modo um link renderiza
Um link gerado combranding.button renderiza o botão por padrão. Mandar esse objeto é o pedido do botão — não existe uma segunda flag pra lembrar, nem como estilizar um botão e ser surpreendido por um card. Três entradas decidem, nesta ordem de precedência:
O
branding.button só chega a um link pelo token assinado, então o modo automático só pode disparar num link cujo próprio backend pediu: não existe configuração no nível do tenant que vire os seus outros links, nem parâmetro de query que vire o link de outra pessoa.
No modo botão a página também limpa o próprio fundo (html e body). Isso é característica do mode=button em si, não de estar dentro de um iframe — vale independentemente de como a URL for aberta. No embed normal, o efeito é que a sua página aparece através do iframe e o botão pousa direto no seu layout, em vez de ficar sobre um retângulo sólido perdido ali. Nada a configurar do seu lado.
metaFlow: "embedded". Um token metaFlow: "credentials" ignora o modo botão — pedido explicitamente ou alcançado pelo automático — e cai na página completa — o formulário de credenciais da Cloud API não cabe em um botão. Tokens de QR (PILOT_STATUS) também renderizam a página completa.
Os estados de carregamento, erro de configuração, token expirado e sucesso continuam aparecendo no modo botão, em versão mínima e inline — nunca em tela cheia.
O Facebook valida a origem do iframe, não a da sua página de nível superior. É exatamente esse o ponto desta integração: o botão roda em
connect.pilotstatus.com.br, um domínio já registrado no app Meta da Pilot Status, então você nunca precisa registrar o seu domínio (nem o do seu cliente).Estilo do botão (somente via token)
A aparência do botão é definida na hora de gerar o link, dentro debranding.button, e viaja no token assinado. Mandar esse objeto também põe o link em modo botão por padrão, então esse único objeto é o pedido inteiro: estilize um botão e receba um botão.
remotePairingUrl devolvida no navegador para ver exatamente o que o seu cliente vai ver — a única diferença é o marcador “secured by”, que aparece em nível superior e se esconde dentro do seu iframe.
Todos os campos são opcionais. O rótulo interno é a string em português
Continuar com o Facebook — defina label explicitamente para qualquer outro idioma. Mantenha o objeto pequeno: ele viaja dentro do JWT que está na URL de conexão.
variant: "whatsapp"pinta o botão de verde WhatsApp (#25D366).iconé o glifo de marca desenhado antes do rótulo — um SVG inline, sem nenhuma requisição de rede, que herda a cor do texto. Se você omitir o campo, ele segue a variante:facebook→ glifo do Facebook,whatsapp→ glifo do WhatsApp, qualquer outra variante → nenhum glifo. Envieicon: "none"para removê-lo explicitamente.heighté aplicado como altura mínima — o rótulo nunca corta — e substitui o espaçamento vertical que osizedaria ao botão.widthsubstituifullWidth: se você mandarwidth, ofullWidthé ignorado. Semwidth, o botão ocupa toda a largura do contêiner por padrão; usefullWidth: falsepara encolhê-lo até o conteúdo.- Valide os valores antes de chamar. Valor fora da faixa ou fora do enum é recusado: o
POST /v1/numbers/remote-pairingresponde400 Validation errorcomdetails, e nada é criado — sem link, sem placeholder, sem slot consumido. Umheightde1000não cai no padrão, ele derruba a requisição inteira. (O sanitizador que roda no navegador realmente ignora campo a campo o que estiver fora da faixa e volta ao padrão, mas isso é defesa em profundidade para um valor antigo já assinado dentro de um token — pela API você nunca chega lá.)
O marcador “secured by pilotstatus.com.br”
Se o pequeno marcador de procedência aparece embaixo do botão depende de onde a página está rodando — no caso normal, embedado, você não precisa configurar nada. São exatamente três situações:- No modo botão dentro de um iframe ele some sozinho. A página verifica se está mesmo dentro de um frame (
window.parent !== window); se estiver, o marcador não é renderizado. Nenhum campo no token, nenhuma chave, nada a solicitar — é só embedar o botão e ele já não está lá. - Em nível superior o marcador fica, de propósito. Um botão de login sozinho, estilizado com a marca do tenant, numa URL
*.pilotstatus.com.bre sem nada que identifique de quem é a página, é exatamente a superfície que o Safe Browsing marca como enganosa. É o marcador que mantém essa URL atribuível, então ele permanece sempre que o botão não estiver embedado. - A página completa mantém o marcador sempre, inclusive com
hidePilotStatusBrandingligado.
branding.button.hideProvenance: true continua existindo como opt-out explícito para o caso do botão em nível superior, e continua atrelado a uma chave do lado do servidor controlada pela Pilot Status. Essa chave vem desligada por padrão, então definir hideProvenance sozinho não muda nada; fale com o suporte se você realmente precisar. Ele não pode ser definido por parâmetro de query, só no token gerado — e nunca afeta a página de conexão completa.
Protocolo postMessage (iframe → parent)
O Connect nunca recebe uma mensageminit — o token já está na URL. O SDK apenas escuta (validando origin === connect.pilotstatus.com.br e source === iframe.contentWindow):
Slots do plano e o padrão delete-on-abandon
Devolva o slot você mesmo, em vez de esperar o link caducar. Oconnect:error avisa que uma tentativa falhou — inclusive quando alguém fecha a janela do Facebook — mas ele não é o sinal para apagar: o botão continua na tela e a pessoa pode clicar de novo. Anote a tentativa e rode a limpeza quando a sessão realmente acabar sem um connect:paired (você fechou o modal, ela saiu da página, você desistiu):
DELETE /v1/numbers/{id} e a chave ps_ — o slot volta na hora, em vez de ficar preso até você perceber. A chamada de limpeza está escrita por extenso em Embedded Signup.
O {id} desse placeholder chega até você por dois caminhos, e só por esses dois: ele é a claim numberId dentro do próprio token Meta — decodifique o payload base64url do JWT (o segmento do meio) no seu backend, é leitura de metadado, não precisa validar assinatura — e ele também vem no numberId do evento connect:paired. Guarde o id que você decodificou no momento em que gerou o link, junto com o seu externalRef, e a limpeza fica trivial depois.
Só apague quando tiver certeza de que o cliente não vai tentar de novo — mas o motivo não é o link deixar de valer. O token de pareamento é verificado sem consultar o banco (assinatura, tipo e expiração, só isso), então ele continua válido com o placeholder apagado: um cliente que voltar e clicar depois do seu DELETE conecta normalmente, e como a linha original não existe mais, a conclusão cria um número novo — o limite do plano é reavaliado nesse instante e um slot novo é consumido. O risco de apagar cedo demais é ganhar um segundo número inesperado, não revogar o link.
Payload do connect:paired
numberIdé o id usado em todo o resto da API (GET /v1/numbers/{id}, webhooks, envio).externalRefé o valor que você passou ao gerar o link — cruze com o seu próprio cadastro de cliente. Ele é carregado pelos links Meta; no pareamento por QR chega comonull.- Todos os seis campos —
numberId,phone,displayName,provider,externalRef,redirectUrl— estão sempre presentes: um fluxo que não conhece algum envianull, nunca o omite, então você não precisa de ramificações por fluxo sobre eles. - O
redirectUrl, quando presente, não é seguido enquanto incorporado — o iframe entrega o valor para você e deixa a sua página decidir se e para onde navegar, em vez de estourar o frame.
White-label / branding
Precedência: parâmetros de query da URL >branding por link (do token gerado, ou capturado como snapshot no número nos links de QR) > branding do tenant (GET/PUT /v1/branding) > padrões da Pilot Status.
Os parâmetros de query são apenas cosméticos e deliberadamente restritos — primary, bg, logo, title, subtitle, support, hideBranding. Eles são aplicados somente depois que o token é resolvido como válido; com token inválido ou expirado a página cai nos padrões da Pilot Status e os ignora por completo. O ?logo precisa ser https e é restrito ao host de assets de primeira parte. O estilo do botão e o hideProvenance não fazem parte desse conjunto.
Modelo de segurança
- O
x-api-keypermanece no seu backend; somente ele chamaPOST /v1/numbers/remote-pairing. - O token de pareamento autoriza apenas os endpoints de pareamento daquele link e expira — 30 minutos para token Meta, 24 horas para token de pareamento por QR.
- As origens são validadas em ambos os lados; o iframe roda em
connect.*, então suas chamadas de API são same-origin (sem superfície de CORS para o seu app). - Tudo que muda a aparência da página para o usuário final é autenticado: vem de um token gerado com a sua chave, nunca de uma URL que um estranho possa digitar.