InícioDocumentação › API de WhatsApp em Go

Como enviar mensagem de WhatsApp com Go

Do token ao fluxo de duas vias usando só a biblioteca padrão: net/http e encoding/json para o cliente, contexto com prazo em toda chamada, envio de texto, imagem e botões, verificação de número e um servidor de webhook com http.HandleFunc que responde 200 antes de processar.

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.

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ódigoSignificaO que fazer em Go
200Requisição aceitaGrave o campo ID junto do seu registro e siga
400Corpo inválidoLoga o JSON serializado e corrige: campo faltando, Phone fora do formato ou tag de struct ausente
401Token ausente ou inválidoos.Getenv de variável inexistente devolve string vazia — valide na subida com os.LookupEnv
404Endpoint inexistenteConfira a rota; um /chat/send/txt cai aqui
500Erro ao processarRepita 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()
	}
}
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.

Leia também

Escreva a integração hoje, conecte o número quando quiser.

14 dias grátis, sem cartão — a contagem só começa na primeira conexão. R$ 27 por mês por número, sem cobrança por mensagem.