Go é a linguagem em que esta integração exige menos: nenhum módulo além da biblioteca padrão, um binário estático de poucos megabytes e um servidor de webhook que sobe em dez linhas. Se o seu serviço já é um main.go num contêiner pequeno, ligar WhatsApp a ele não muda nada na sua topologia.
Esta página vai do token ao fluxo de duas vias: envio de texto, imagem e botões, verificação de número, servidor de webhook e tratamento de erro por código HTTP. Os exemplos são idiomáticos — erro devolvido em vez de exceção, defer para fechar o corpo da resposta, context em toda chamada — e compilam em Go 1.21 ou superior com go build, sem go get nenhum.
O que você precisa antes da primeira linha
O que preciso para chamar a API do zapon em Go?
Go 1.21 ou superior, uma conta no zapon e um número conectado. Da conexão sai o token, que vai no header de toda chamada — um token por número. Não há módulo do zapon para importar: net/http e encoding/json resolvem a integração inteira, e o go.mod do seu projeto não ganha nenhuma dependência. Para o webhook você precisa de uma URL pública que aceite POST.
- Toolchain. Qualquer Go recente serve. Os exemplos usam
any em vez de interface{} e os.ReadFile, ambos disponíveis desde o 1.18. - Conexão e token. No painel, crie a conexão e leia o QR Code com o WhatsApp do número (Aparelhos conectados → Conectar aparelho). Copie o
token: ele autentica as chamadas daquele número e só daquele. - Base da API.
https://api.zapon.dev, sempre por HTTPS.
Onde guardar o token num serviço em Go?
Em variável de ambiente, lida com os.LookupEnv na inicialização — não com os.Getenv espalhado pelo código, que devolve string vazia quando a variável não existe e transforma um erro de configuração num 401 no meio da madrugada:
falhe na subida, não no primeiro envio
token, ok := os.LookupEnv("ZAPON_TOKEN")
if !ok || token == "" {
log.Fatal("ZAPON_TOKEN não definido")
}
A autenticação é um header só, de nome token. Não é Authorization, não é Bearer e não há OAuth. Quem tem o token envia em nome daquele número, então trate-o como credencial de banco de dados: fora do repositório, fora da imagem do contêiner, fora do log.
Primeiro envio de texto
Como enviar uma mensagem de WhatsApp com Go?
Um POST em https://api.zapon.dev/chat/send/text, com o header token, o Content-Type: application/json e dois campos no corpo: Phone, só dígitos com país e DDD, e Body, o texto. Volta em data.Id o identificador da mensagem no WhatsApp — grave-o junto do registro que originou o envio.
O pacote abaixo é o cliente completo. Ele tem um http.Client reaproveitado, um tipo de erro que carrega o status e um método Chamar que recebe context.Context — todo o resto da página usa esse método:
zapon/zapon.go — cliente com net/http
package zapon
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
const base = "https://api.zapon.dev"
// Erro carrega o status HTTP. Status 0 = nem chegou na API (rede ou prazo esgotado).
type Erro struct {
Status int
Detalhe string
}
func (e *Erro) Error() string { return fmt.Sprintf("zapon %d: %s", e.Status, e.Detalhe) }
type Cliente struct {
token string
http *http.Client
}
func Novo(token string) *Cliente {
return &Cliente{
token: token,
// Nunca use http.DefaultClient: ele não tem timeout e uma chamada pode
// ficar pendurada para sempre, segurando a goroutine que a fez.
http: &http.Client{Timeout: 25 * time.Second},
}
}
func (c *Cliente) Chamar(ctx context.Context, rota string, payload any) (json.RawMessage, error) {
corpo, err := json.Marshal(payload)
if err != nil {
return nil, &Erro{0, "payload inválido: " + err.Error()}
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, base+rota, bytes.NewReader(corpo))
if err != nil {
return nil, &Erro{0, err.Error()}
}
req.Header.Set("token", c.token)
req.Header.Set("Content-Type", "application/json")
res, err := c.http.Do(req)
if err != nil {
return nil, &Erro{0, err.Error()}
}
defer func() {
io.Copy(io.Discard, res.Body) // drene antes de fechar: sem isso a conexão não volta ao pool
res.Body.Close()
}()
bruto, err := io.ReadAll(io.LimitReader(res.Body, 4<<20))
if err != nil {
return nil, &Erro{0, err.Error()}
}
var envelope struct {
Success bool `json:"success"`
Error string `json:"error"`
Data json.RawMessage `json:"data"`
}
// Erro de servidor pode não vir em JSON: ignore a falha de parse aqui.
_ = json.Unmarshal(bruto, &envelope)
if res.StatusCode != http.StatusOK || !envelope.Success {
detalhe := envelope.Error
if detalhe == "" {
detalhe = string(bruto[:min(200, len(bruto))])
}
return nil, &Erro{res.StatusCode, detalhe}
}
return envelope.Data, nil
}
type Enviada struct {
Details string `json:"Details"`
ID string `json:"Id"`
Timestamp string `json:"Timestamp"`
}
func (c *Cliente) EnviarTexto(ctx context.Context, phone, body string) (Enviada, error) {
data, err := c.Chamar(ctx, "/chat/send/text",
map[string]string{"Phone": phone, "Body": body})
if err != nil {
return Enviada{}, err
}
var e Enviada
return e, json.Unmarshal(data, &e)
}
E a chamada, no ponto em que o pedido acaba de ser confirmado:
main.go — primeiro envio
cli := zapon.Novo(token)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() // sempre: sem o cancel, o contexto vaza um timer
msg, err := cli.EnviarTexto(ctx, "5511999999999",
"Pedido 4821 confirmado. Assim que o pacote sair, mandamos o rastreio por aqui.")
if err != nil {
log.Printf("falhou: %v", err)
return
}
log.Printf("mensagem enviada: %s", msg.ID)
A resposta de sucesso vem sempre neste envelope, que é exatamente o que a struct do cliente lê:
resposta da API
{
"code": 200,
"success": true,
"data": {
"Details": "Sent",
"Id": "90B2F8B13FAC8A9CF6B06E99C7834DC5",
"Timestamp": "2026-09-03T09:12:08-03:00"
}
}
Por que drenar o corpo antes de fechar?
Porque o Transport do Go só devolve a conexão ao pool quando o corpo da resposta foi lido até o fim e fechado. Um defer res.Body.Close() sozinho fecha, mas descarta a conexão — e num laço de mil envios isso significa mil aperturas de mão TLS em vez de uma. O io.Copy(io.Discard, res.Body) antes do Close custa nada e mantém o keep-alive funcionando. O io.LimitReader ao lado é a outra metade do cuidado: você não controla o tamanho do que o outro lado devolve, e ler sem limite é abrir a porta para um consumo de memória que você não previu.
Por que passar context em vez de confiar no Timeout do cliente?
Porque os dois cobrem coisas diferentes. O Timeout do http.Client é o teto absoluto de cada requisição; o context propaga o prazo — e o cancelamento — da operação inteira. Se o envio nasce dentro de um handler HTTP, passar o r.Context() faz a chamada ao zapon morrer junto quando o cliente desiste, em vez de continuar segurando recursos por uma resposta que ninguém vai ler. E há a armadilha do defer cancel(): esquecê-lo vaza o timer do contexto, o que só aparece como crescimento lento de memória num serviço de vida longa.
Preciso de resty, req ou de outro cliente HTTP?
Não. A biblioteca padrão faz tudo o que esta integração exige, e a comunidade Go tende a preferir assim — menos dependência é menos superfície para auditar e atualizar. Um cliente externo só se paga se você quiser retentativa e middleware prontos; a função de repetição mais adiante nesta página tem doze linhas e resolve o caso.
Imagem e botões
Como enviar uma imagem pela API do zapon em Go?
POST /chat/send/image com Phone, Image e, opcionalmente, Caption. O campo Image é o arquivo em base64 no formato data URI — data:image/jpeg;base64,.... Em Go, isso é os.ReadFile mais base64.StdEncoding.EncodeToString, ambos da biblioteca padrão.
enviando uma imagem
bytesImg, err := os.ReadFile("etiquetas/4821.jpg")
if err != nil {
return err
}
// DetectContentType olha os primeiros 512 bytes: mais confiável que a extensão.
tipo := http.DetectContentType(bytesImg)
imagem := "data:" + tipo + ";base64," + base64.StdEncoding.EncodeToString(bytesImg)
_, err = cli.Chamar(ctx, "/chat/send/image", map[string]string{
"Phone": "5511999999999",
"Image": imagem,
"Caption": "Etiqueta do pedido 4821. Cole na caixa antes de postar.",
})
Use StdEncoding, não URLEncoding: a variante para URL troca + e / por - e _ e gera um data URI que a API não reconhece. Sobre memória, base64 cresce o arquivo em cerca de um terço e o os.ReadFile traz tudo para a heap; em contêiner com limite apertado, um punhado de goroutines convertendo fotos grandes ao mesmo tempo é caminho curto para o processo ser morto pelo sistema. Redimensione antes e trate um arquivo por vez.
Como mandar botões de resposta rápida?
O POST /chat/send/buttons aceita quantos botões você precisar — três ou quatro é o que se lê num toque. Cada um tem um id escolhido por você e um text curto — até 20 caracteres, ou o WhatsApp trunca o rótulo. O id volta inteiro no webhook, e é a melhor peça do fluxo: carimbe nele a chave do seu registro. Além da resposta rápida, o mesmo endpoint monta botão que abre um link ("type": "cta_url"), botão que abre o discador ("type": "cta_call") e botão que copia um código ("type": "copy") — e os tipos convivem na mesma mensagem. O toque em resposta rápida volta no webhook; os outros três agem no aparelho de quem recebe.
botões com o pedido carimbado no id
type Botao struct {
ID string `json:"id"` // a tag define o nome no JSON
Text string `json:"text"` // sem a tag, sairia "Text" e a API recusaria
}
type EnvioBotoes struct {
Phone string `json:"Phone"`
Body string `json:"Body"`
Footer string `json:"Footer,omitempty"`
Buttons []Botao `json:"Buttons"`
}
_, err := cli.Chamar(ctx, "/chat/send/buttons", EnvioBotoes{
Phone: "5511999999999",
Body: "Pedido 4821 sai para entrega hoje. Tem alguém para receber à tarde?",
Footer: "Loja Exemplo",
Buttons: []Botao{
{ID: "ped4821|hoje", Text: "Pode entregar"}, // 13 caracteres
{ID: "ped4821|amanha", Text: "Prefiro amanhã"},
{ID: "ped4821|humano", Text: "Falar com alguém"},
},
})
As tags de struct são a diferença entre funcionar e tomar 400. Go só exporta campo com inicial maiúscula, e sem a tag o encoding/json escreve exatamente esse nome — ID e Text em vez de id e text. Repare também que Buttons não leva omitempty: se levasse, uma fatia vazia sumiria do JSON e o erro ficaria ainda mais difícil de ler.
Sem o carimbo no id, a resposta chega só com o telefone e você adivinha de qual pedido ela fala. Com ele, o webhook faz strings.Cut(botao, "|") e vai direto ao registro. Para mais de três opções, o caminho é o /chat/send/list, que abre um menu.
Verificar o número antes de enviar
Como saber se um número tem WhatsApp antes de disparar?
POST /user/check leva a lista no campo Phone e traz IsInWhatsapp para cada item. Filtrar antes do lote evita chamada perdida com telefone fixo ou cadastro velho, e evita também a sequência de envios para números inexistentes — padrão que aumenta o risco do seu número.
filtrando a lista antes do disparo
candidatos := []string{"5511999999999", "5511888888888", "551133334444"}
data, err := cli.Chamar(ctx, "/user/check", map[string][]string{"Phone": candidatos})
if err != nil {
return err
}
var r struct {
Users []struct {
Query string `json:"Query"`
IsInWhatsapp bool `json:"IsInWhatsapp"`
} `json:"Users"`
}
if err := json.Unmarshal(data, &r); err != nil {
return err
}
var validos, fora []string
for _, u := range r.Users {
if u.IsInWhatsapp {
validos = append(validos, u.Query)
} else {
fora = append(fora, u.Query)
}
}
log.Printf("com WhatsApp: %v", validos)
marcarSemWhatsapp(fora) // corrija a origem, não só o disparo de hoje
Persista o resultado em vez de reconsultar a cada campanha, e trate fora como dívida de cadastro: quase sempre é o nono dígito faltando ou o DDD digitado no campo errado.
Receber mensagens: o servidor de webhook
Como receber em Go as respostas que chegam no WhatsApp?
Cadastre no painel, por conexão, uma URL pública e escolha os eventos. Cada evento vira um POST para essa URL com a mensagem inteira no corpo — não há segunda chamada para buscar conteúdo. Em Go, o handler deve escrever WriteHeader(200) na primeira linha útil e passar o processamento para uma goroutine ou um canal.
O receptor precisa aceitar duas formas de corpo: JSON puro e formulário codificado, com o evento dentro do campo jsonData. Aceitar as duas é meia dúzia de linhas e evita o sintoma clássico de "o webhook não chega" — ele chega, e o seu json.Unmarshal é que recebeu um corpo de formulário.
webhook.go — servidor com http.HandleFunc
func main() {
cli := zapon.Novo(os.Getenv("ZAPON_TOKEN"))
// Canal com folga: o handler só escreve, o worker processa.
eventos := make(chan []byte, 500)
go processar(cli, eventos)
http.HandleFunc("/whatsapp/eventos", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
defer r.Body.Close()
var cru []byte
ct := r.Header.Get("Content-Type")
if strings.HasPrefix(ct, "application/x-www-form-urlencoded") {
if err := r.ParseForm(); err == nil {
cru = []byte(r.PostFormValue("jsonData"))
}
} else {
cru, _ = io.ReadAll(http.MaxBytesReader(w, r.Body, 10<<20)) // eventos de mídia são grandes
}
w.WriteHeader(http.StatusOK) // responda ANTES de qualquer trabalho
select {
case eventos <- cru:
default:
log.Print("fila cheia: evento descartado") // melhor que travar o handler
}
})
srv := &http.Server{
Addr: ":8080",
ReadHeaderTimeout: 5 * time.Second, // sem isto o servidor fica exposto a conexão lenta
WriteTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
func processar(cli *zapon.Cliente, eventos <-chan []byte) {
for cru := range eventos {
if err := tratar(cli, cru); err != nil {
log.Printf("webhook: %v", err)
}
}
}
func tratar(cli *zapon.Cliente, cru []byte) error {
var ev struct {
Type string `json:"type"`
Event struct {
Info struct {
ID string `json:"ID"`
Chat string `json:"Chat"`
IsFromMe bool `json:"IsFromMe"`
IsGroup bool `json:"IsGroup"`
PushName string `json:"PushName"`
} `json:"Info"`
Message struct {
Conversation string `json:"conversation"`
ExtendedTextMessage struct {
Text string `json:"text"`
} `json:"extendedTextMessage"`
ButtonsResponseMessage struct {
SelectedButtonID string `json:"selectedButtonID"`
} `json:"buttonsResponseMessage"`
} `json:"Message"`
} `json:"event"`
}
if err := json.Unmarshal(cru, &ev); err != nil {
return err
}
if ev.Type != "Message" {
return nil // ignore o que o seu fluxo não usa
}
info := ev.Event.Info
if info.IsFromMe || info.IsGroup {
return nil // o que você mesmo enviou volta como evento
}
if jaVisto(info.ID) {
return nil // reentrega acontece: guarde o ID
}
telefone, _, _ := strings.Cut(info.Chat, "@")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if botao := ev.Event.Message.ButtonsResponseMessage.SelectedButtonID; botao != "" {
pedido, acao, _ := strings.Cut(botao, "|")
registrarEscolha(pedido, acao)
resposta := "Certo, remarcamos para amanhã."
if acao == "hoje" {
resposta = "Combinado. A entrega sai hoje à tarde."
}
_, err := cli.EnviarTexto(ctx, telefone, resposta)
return err
}
texto := ev.Event.Message.Conversation
if texto == "" {
texto = ev.Event.Message.ExtendedTextMessage.Text
}
return filaDeAtendimento(telefone, strings.TrimSpace(texto))
}
Qual é o formato do evento de mensagem recebida?
O corpo do POST traz o tipo do evento e a mensagem inteira. Quem respondeu por escrito chega assim:
payload do evento Message
{
"type": "Message",
"event": {
"Info": {
"ID": "3EB0C767D26A1B5F7C83",
"Chat": "[email protected]",
"Sender": "[email protected]",
"IsFromMe": false,
"IsGroup": false,
"PushName": "Ana Souza",
"Timestamp": "2026-09-03T18:04:22-03:00"
},
"Message": { "conversation": "pode entregar sim" }
}
}
// quando ele toca num botão, muda só o bloco Message:
"Message": { "buttonsResponseMessage": {
"selectedButtonID": "ped4821|hoje",
"Response": { "SelectedDisplayText": "Pode entregar" }
} }
Um ponto a favor do Go aqui: o encoding/json ignora campos que você não declarou, então a struct anônima do exemplo pode listar só o que o seu fluxo usa e continua funcionando quando o evento traz mais coisas. Declare apenas o necessário e evolua a struct quando precisar de um campo novo.
Por que responder 200 antes de processar?
Porque a entrega do evento tem janela curta: um handler que consulta banco, chama outra API e só então escreve o status transforma cada evento numa espera. Em Go é fácil acertar — w.WriteHeader(http.StatusOK) e depois empurrar para o canal. O que vale evitar é o go tratar(...) solto para cada evento: sem limite, uma rajada vira milhares de goroutines competindo pelo banco, e um panic dentro de qualquer uma delas derruba o processo inteiro. O canal com um worker resolve os dois problemas de uma vez. Vale ainda registrar quem enviou o POST: as entregas chegam com o cabeçalho User-Agent: zapon-webhook/1.
Tratamento de erro por código HTTP
O que fazer em cada erro da API?
400 significa corpo inválido: arrume o payload em vez de repetir. 401 é token vazio ou de outra conexão. 404 é rota errada. Repetir só faz sentido em falha de rede e 500, com espera crescente — insistir num 400 ou num 401 é chamada jogada fora.
| Código | Significa | O que fazer em Go |
|---|
200 | Requisição aceita | Grave o campo ID junto do seu registro e siga |
400 | Corpo inválido | Loga o JSON serializado e corrige: campo faltando, Phone fora do formato ou tag de struct ausente |
401 | Token ausente ou inválido | os.Getenv de variável inexistente devolve string vazia — valide na subida com os.LookupEnv |
404 | Endpoint inexistente | Confira a rota; um /chat/send/txt cai aqui |
500 | Erro ao processar | Repita com espera crescente e, se persistir, veja o estado da conexão no painel |
Em código, isso vira uma função que usa errors.As para olhar o status e decidir:
repetição seletiva com espera crescente
func ChamarComRetentativa(ctx context.Context, cli *zapon.Cliente,
rota string, payload any, tentativas int) (json.RawMessage, error) {
var ultimo error
for i := 1; i <= tentativas; i++ {
data, err := cli.Chamar(ctx, rota, payload)
if err == nil {
return data, nil
}
ultimo = err
var ze *zapon.Erro
temporario := errors.As(err, &ze) && (ze.Status == 0 || ze.Status >= 500)
if !temporario || i == tentativas {
return nil, err // 400 e 401 não melhoram com insistência
}
select {
case <-time.After(time.Duration(1<<(i-1)) * time.Second): // 1s, 2s, 4s
case <-ctx.Done():
return nil, ctx.Err() // respeite o cancelamento durante a espera
}
}
return nil, ultimo
}
Repare no select com ctx.Done(): um time.Sleep puro ignoraria o cancelamento e seguraria a goroutine por quatro segundos depois de o serviço já ter começado a desligar. É a diferença entre um encerramento limpo e um contêiner morto pelo orquestrador no meio do lote.
Ritmo de envio e preservação do número
Go torna trivial disparar mil envios ao mesmo tempo, e é exatamente por isso que o cuidado precisa ser explícito. Um for com go enviar(...) manda tudo em segundos — padrão que não se parece com nenhum uso humano. Serialize com um time.Ticker, que é a forma idiomática de impor cadência:
um por vez, com intervalo
tick := time.NewTicker(4 * time.Second)
defer tick.Stop()
for _, cliente := range pendentesDeHoje() {
if cliente.OptOut {
continue // quem pediu para sair, sai de todas as rotinas
}
msg, err := cli.EnviarTexto(ctx, cliente.Telefone, textoPara(cliente))
if err != nil {
registrarFalha(cliente.ID, err)
} else {
registrarEnvio(cliente.ID, msg.ID) // guardar o Id evita mandar duas vezes
}
// espera do ticker mais uma variação aleatória, para o ritmo não ficar cronométrico
select {
case <-tick.C:
time.Sleep(time.Duration(rand.Intn(3000)) * time.Millisecond)
case <-ctx.Done():
return ctx.Err()
}
}
- Fale com quem espera. Mensagem sobre um pedido que a pessoa fez é esperada; lista comprada é denúncia esperando acontecer, e denúncia é o que pesa.
- Intervalo com variação. Um ticker exato mil vezes é tão artificial quanto o disparo em rajada; a variação aleatória custa nada.
- Aqueça número novo. Comece com poucos envios por dia e aumente ao longo de semanas.
- Descadastro que funciona. Guarde o
OptOut no cadastro e faça toda rotina consultá-lo antes de enviar. O pedido chega em qualquer palavra, então alguém precisa olhar a fila de exceções.
Sobre bloqueio de número, sem promessa mágica. Nenhuma API — nem a oficial da Meta — impede que o WhatsApp aja contra um número denunciado. O risco não se elimina, se administra: falar com quem espera, manter volume e ritmo compatíveis com o seu negócio, responder quem responde e parar de enviar para quem pediu para sair. Quem promete que o número nunca será bloqueado não está sendo honesto.
Sobre custo, o cálculo em Go é o mesmo de qualquer linguagem: R$ 27 por mês por número conectado, sem cobrança por mensagem, dentro da cota de 300 mensagens por dia por número (cerca de 9.000 por mês) — ao estourar, a API devolve 429, e é essa trava que mantém o número fora do bloqueio do WhatsApp. O teste é de 14 dias sem cartão e só começa a contar na primeira conexão — dá para escrever a integração inteira, com testes, antes de gastar o primeiro dia.
Perguntas frequentes
Preciso de algum módulo além da biblioteca padrão?
Não. net/http, encoding/json, encoding/base64 e context cobrem tudo o que esta página mostra, e o seu go.mod continua sem dependência nova. Se o projeto já usa um roteador como o chi, use-o no webhook: a lógica do handler é idêntica.
Como faço testes sem chamar a API de verdade?
Aponte a base para um httptest.NewServer que devolve o envelope de resposta desta página. Para isso, extraia a constante base para um campo do Cliente — é a única mudança necessária no código mostrado, e ela deixa o pacote testável sem rede.
Dá para enviar em paralelo com goroutines?
Tecnicamente sim, e é justamente o que você não quer para envio em massa: o objetivo é parecer uso humano, e paralelismo trabalha contra isso. Onde a concorrência ajuda de verdade é no receptor de webhook, com um número pequeno e fixo de workers lendo do canal. Se precisar de vários envios simultâneos por motivo real, limite com um semáforo — um canal com capacidade — em vez de soltar goroutines sem teto.
Como faço o desligamento limpo do servidor de webhook?
Use srv.Shutdown(ctx) ao receber SIGTERM, feche o canal de eventos depois que o servidor parar de aceitar requisições e espere o worker terminar com um sync.WaitGroup. Sem isso, o contêiner morre com eventos ainda no canal e você perde mensagens já confirmadas com 200.
Como testo o webhook sem publicar o serviço?
Rode go run . local, exponha a porta com um túnel HTTP e cadastre a URL do túnel na conexão. Para testar só a sua regra, chame tratar() direto num teste com o payload de exemplo desta página — é o mesmo formato que chega em produção, e assim a lógica fica coberta sem depender de mensagem real.
Um cliente atende vários números?
Não: um token é um número. Crie um *zapon.Cliente por conexão e guarde-os num mapa protegido por sync.RWMutex se o serviço atende vários clientes. Cada número conectado custa R$ 27 por mês, sem cobrança por mensagem, e cada um tem a sua própria cota de 300 mensagens por dia (cerca de 9.000 por mês).
Preciso da API oficial da Meta ou de aprovação de template?
Não. A conexão é por QR Code ou código de pareamento, sem aprovação prévia e sem template para cadastrar. Em troca, a conduta do número fica por conta de quem integra: a seção de ritmo desta página pesa tanto quanto o cliente HTTP.