Pular para o conteúdo principal

Linhas telefônicas no seu app pela API

Ofereça linhas telefônicas — números fixos brasileiros para ativar contas do WhatsApp Business — sem tirar o seu cliente do seu produto. A sua página mostra os números, o botão de compra e o código de ativação; o seu backend fala com /v1/phone-lines usando a sua chave de tenant. Aqui não há página hospedada nem iframe: tudo passa pelo seu backend, porque o código de ativação é uma credencial e a chave ps_ nunca pode chegar ao browser.
Quer um ponto de partida pronto pra rodar? Baixe o demo (licença MIT) — backend Node + TypeScript (Express) que guarda a chave e frontend React + Vite ligados exatamente a este fluxo: phone-lines-demo.zip. Descompacte, ponha a sua chave ps_ em PILOT_TENANT_KEY (e, para o webhook, uma URL pública em PUBLIC_BASE_URL), npm run dev (ou docker compose up) — frontend em :5175, backend em :8789.
Esta página é o passo a passo: siga os cinco passos na ordem e você tem uma integração funcionando. A referência — todos os campos, estados e códigos de erro — está em API de Linhas Telefônicas. Esta página linka pra lá em vez de repetir, então você sempre lê uma única cópia.

Uma credencial só

Você precisa de exatamente uma coisa: uma chave de API ps_ com escopo de tenant. Pegue em /profile, aba API. A linha pertence ao workspace, não a um número de WhatsApp, então uma chave com escopo de número recebe 403 NUMBER_SCOPE_NOT_ALLOWED em todo endpoint /v1/phone-lines. Você não precisa de nenhum número de WhatsApp conectado para comprar uma linha. A chave ps_ fica no seu servidor — o browser nunca chama a API da Pilot Status, ele só chama o seu backend.

Arquitetura

Só o seu backend toca na API da Pilot Status. As rotas /api/... acima são as do demo — os nomes são seus; o que importa é que cada uma é uma chamada do seu servidor para /v1/phone-lines.

Os cinco passos

1

Confira se o workspace pode comprar

Alugar uma linha exige um titular da conta verificado (CNPJ ou CPF, o documento e a confirmação de contato), feito uma única vez, antes da primeira compra. Antes de mostrar o botão de compra, leia o estado:
cURL
Decida pelo canPurchase: ele é true para APPROVED e IN_REVIEW — em análise manual você já pode comprar. Com false, a compra responde 403 VERIFICATION_REQUIRED (ou VERIFICATION_REJECTED, quando foi recusada), então mostre ao seu cliente o caminho em vez do botão:
  • No painel — Linhas (/linhas) → Comprar linhas, que conduz a verificação. É o que o demo faz: ele só lê o estado e aponta para o painel.
  • Por código — se você quer conduzir a verificação dentro do seu produto, ela também existe na API: envie os dados e o documento, confirme os códigos de contato e leia a decisão em Verificação do titular.
Os dois caminhos são a mesma verificação: o que for feito num aparece no outro. As mudanças de estado também chegam pelo webhook phone_line.verification_updated (passo 5).
Comprar em IN_REVIEW depende da análise. Se ela for recusada, as linhas compradas durante a análise são devolvidas à operadora 3 dias depois, com as cobranças estornadas para a carteira — veja verificação do titular.
2

Busque números por DDD

cURL
Mostre o display ao seu cliente e guarde o number — é exatamente o valor que a compra recebe. price e currency são quanto uma linha custa ao seu workspace por mês — com o seu preço de linha próprio, se houver —, para você mostrar o preço antes da compra. Um ddd ausente ou malformado responde 400 INVALID_AREA_CODE, sem chamar a operadora.
  • Busque uma vez e reutilize. Toda busca chega à nossa operadora e consome uma cota compartilhada por toda a plataforma; quando ela se esgota, a resposta é 503 SUPPLIER_UNAVAILABLE. Não consulte este endpoint periodicamente.
  • Buscar não é reservar. Um número listado pode ser comprado por outra pessoa antes da sua compra; ele volta como NUMBER_UNAVAILABLE no resultado da compra.
Referência: Buscar números.
3

Compre com Idempotency-Key

POST /v1/phone-lines compra de 1 a 10 números e movimenta dinheiro, por isso exige o header Idempotency-Key. Gere a chave no seu backend, uma por compra, e guarde-a junto do pedido antes de enviar — é ela que você reenvia se a resposta não chegar.
cURL
Resposta (200)
Os números são independentes. Cada um é cobrado e adquirido separadamente, e um que falha não desfaz os outros. A requisição responde 200 quando foi processada — um 200 não significa que você comprou alguma coisa: leia results[i].ok de cada número e guarde o lineId dos que deram certo. A tabela completa dos code por número está em Comprar linhas.Pagamento. Cada número custa o preço mensal cheio, descontado primeiro da carteira pré-paga e, no que faltar, do cartão salvo. Não há link de checkout nesta chamada: sem créditos e sem cartão, o item falha com PAYMENT_FAILED e nada é cobrado. Antes de comprar, recarregue a carteira — pelo painel, ou pela API com Pix, página de cartão hospedada ou um formulário de cartão na sua própria página — ou salve um cartão. Veja Carteira e cartão pela API. Preço e ciclo de cobrança: Preço e pagamento.O padrão de retentativa:
  1. Em timeout, 5xx ou erro de rede, reenvie o mesmo corpo com a mesma chave. Um número que a primeira tentativa já tratou volta como REQUEST_ALREADY_PROCESSED e não é cobrado de novo; os que ela não alcançou são processados agora.
  2. Reconcilie com GET /v1/phone-lines, comparando por number. REQUEST_ALREADY_PROCESSED não diz se aquele número foi comprado ou falhou — a lista diz: um número comprado está nela.
  3. Refaça as falhas com uma chave nova. Depois de um PAYMENT_FAILED ou SUPPLIER_UNAVAILABLE, corrija a causa e envie esses números com uma chave nova — com a mesma, eles voltam como REQUEST_ALREADY_PROCESSED durante uma hora.
Um 500 ou um timeout não prova que nada aconteceu. Os números são processados um após o outro, e uma falha no meio pode vir depois que números anteriores já foram cobrados. Nunca gere uma chave nova para “tentar de novo” uma resposta que não chegou — isso pode comprar e cobrar o mesmo pedido de novo. E um PAYMENT_PROVIDER_ERROR pede outra coisa: o cartão pode ter sido cobrado, então confira a fatura antes de tentar de novo. As regras exatas estão em Idempotência.
O código completo deste passo — chave persistida, retentativa e reconciliação — está no Backend de exemplo, abaixo.
4

Obtenha o código de ativação do WhatsApp

O WhatsApp verifica um número fixo ligando para ele e falando um código de 6 dígitos. A ordem importa:
  1. Abra o pedido primeiro. POST /v1/phone-lines/{id}/activations (sem corpo), com o lineId da compra. Uma ligação que chegar antes de o pedido existir não é capturada.
  2. Só então peça ao WhatsApp para ligar. No WhatsApp Business, escolha receber o código por ligação (Me ligue; “Call me” no app em inglês). A linha é fixa e não recebe SMS — esperar por um SMS só consome a janela.
cURL
Resposta (201)
A janela é de 5 minutos (pollDeadline). Mostre ao seu cliente um contador e a instrução “toque em Me ligue agora”.Consulte o pedido a cada poucos segundos com GET /v1/phone-lines/activations/{activationId} — o status só muda depois da nossa próxima verificação da linha, feita a cada 15 segundos. Continue enquanto status for WAITING, ou CAPTURED com failureReason: null: CAPTURED é também o estado intermediário enquanto o código é lido, o que pode levar alguns minutos. Pare num estado definitivo e confira o code — TRANSCRIBED não garante um código. Todos os estados: Status.A gravação passa pelo seu backend. Quando hasAudio é true, o seu cliente pode ouvir a ligação — útil quando o código não pôde ser lido. GET /v1/phone-lines/activations/{activationId}/audio exige a sua chave, então o seu backend busca o arquivo e o repassa ao browser (no demo, GET /api/activations/:id/audio). Repasse também o Cache-Control: private, no-store: a gravação fala o código. Veja Baixar a gravação.Cancelar a espera. Só existe um pedido aguardando por linha. Se o seu cliente quiser recomeçar antes de a janela fechar, chame POST /v1/phone-lines/{id}/activations/{activationId}/cancel e abra um novo pedido na hora.
O código de ativação é uma credencial. Quem o tiver pode registrar uma conta do WhatsApp na linha. Mostre-o só ao seu cliente autenticado, não o registre em log e não o guarde em cache — as respostas da API já vêm com Cache-Control: no-store.
Referência completa: Códigos de ativação.
5

Cadastre o webhook das linhas pela API

Para o seu sistema saber na hora que um código chegou, que uma renovação falhou ou que uma linha foi suspensa, cadastre um webhook das linhas. É um cadastro separado dos webhooks de número: o POST /v1/webhooks dos números não recebe eventos phone_line.*. O cadastro das linhas é POST /v1/phone-lines/webhooks:
cURL
Resposta (201)
Neste cadastro, "*" significa todos os eventos de linha. A URL precisa ser https e pública: localhost, um IP privado ou um nome que resolva para um deles é recusado com 400 WEBHOOK_URL_REFUSED — em desenvolvimento, use um túnel com URL pública em PUBLIC_BASE_URL.
O segredo plwh_ aparece uma única vez, nesta resposta. Guarde-o no seu servidor na hora: nenhuma leitura o devolve depois e não há rotação. Para trocá-lo, crie um webhook novo, passe o seu receptor a usar o segredo dele e exclua o antigo. A criação não é idempotente: dois POST criam dois webhooks, com dois segredos — liste os existentes com GET /v1/phone-lines/webhooks antes de criar outro.
Verifique a assinatura e deduplique. Toda entrega traz x-pilot-status-signature: o HMAC-SHA256 do corpo bruto, em hexadecimal, com o segredo inteiro (incluindo plwh_) como chave. Calcule sobre os bytes exatamente como chegaram, antes do parse do JSON, e compare em tempo constante:
Backend — receptor do webhook (Express)
  • Deduplique pelo id. É o mesmo em todas as retentativas da mesma entrega (e vem também no header Idempotency-Key). A entrega é pelo menos uma vez, a ordem não é garantida e a assinatura não tem timestamp.
  • Responda 2xx em até 10 segundos. Qualquer outra coisa conta como falha; são 6 tentativas em cerca de 15 minutos, e um redirecionamento 3xx não é seguido.
  • Os eventos são notificações, não a fonte da verdade. Um phone_line.code_received evita a consulta do passo 4, mas, se o seu endpoint ficou fora do ar por mais tempo que as retentativas, leia o estado atual com GET /v1/phone-lines ou GET /v1/phone-lines/activations/{activationId}.
Editar (nome, URL, eventos), pausar com active: false, excluir e ler o log de entregas também são pela API — PATCH e DELETE /v1/phone-lines/webhooks/{id} e GET …/{id}/deliveries. Todos os eventos, payloads e endpoints: Webhooks das linhas.

Backend de exemplo

O passo que mais exige cuidado é a compra: é o único que movimenta dinheiro. O seu backend gera e guarda a Idempotency-Key antes de enviar, repete com a mesma chave quando a resposta não chega, e reconcilia pela lista de linhas.
Backend — Node.js / Express
Para os números que voltaram com PAYMENT_FAILED ou SUPPLIER_UNAVAILABLE, o seu cliente corrige a causa e faz uma nova compra — um purchaseId novo, portanto uma chave nova.
A chave ps_ é um segredo de servidor. Nunca a coloque numa variável exposta ao browser (VITE_..., NEXT_PUBLIC_...) nem a devolva num JSON que o frontend possa ler. O mesmo vale para o segredo plwh_ do webhook.

Cancelar uma linha

DELETE /v1/phone-lines/{id} responde com a linha como ela fica depois, e o resultado depende do status:
cURL
Mostre ao seu cliente qual dos dois casos aconteceu: pelo status e pelo cancelAtPeriodEnd da resposta, não pelo 200.
Um número devolvido deixa de ser seu, e o histórico é apagado. Nada desfaz uma devolução, não há garantia de comprar o mesmo número de volta, e a conta do WhatsApp ativada nele continua ligada a ele. Quando a linha chega a RETURNED ou CANCELED, os códigos e as gravações das ligações são apagados. Desfazer um cancelamento agendado só é possível no painel. Veja Cancelar uma linha.

Outros caminhos

A API é o caminho para quem quer as linhas dentro do próprio produto. Conforme o caso, há alternativas:
  • Pelo painel. Para comprar e ativar poucas linhas sem escrever código — e para verificar o titular —, siga Linhas telefônicas. Ficam só no painel: dar nome a uma linha, desfazer um cancelamento agendado e pagar linhas em atraso na hora.
  • Playground. Teste cada endpoint com a sua chave antes de escrever o backend: Playground → Linhas telefônicas. A compra e o cancelamento de lá são reais.
  • Construtores de IA. Se você usa Lovable, Replit, Bolt, v0 ou Cursor, há prompts prontos que já geram este mesmo fluxo em Linhas para builders de IA.
  • Agentes de IA (MCP). O servidor MCP tem as ferramentas da verificação do titular (phone_lines_verification_*) e da gravação (phone_lines_activation_audio), com as mesmas regras de credencial com escopo de tenant.

Próximos passos