Enviar WhatsApp com Go via API REST: exemplo completo
Para enviar WhatsApp com Go você só precisa da biblioteca padrão: um POST em /api/v1/{instanceId}/send/text com os cabeçalhos x-api-key e x-api-secret e um JSON com number e text. Este guia monta um cliente pequeno e reutilizável com net/http e encoding/json, envia texto e mídia, valida números antes de enviar, trata erros com contexto e recebe webhooks com validação de assinatura. Os exemplos usam a ZapZap API, que é uma API não oficial: o número é conectado por QR code, e esse uso não está previsto nos Termos do WhatsApp.
Publicado em · 4 min de leitura
O que você precisa antes de começar?
Uma conta, uma instância conectada por QR code, a chave e o segredo de API e o ID da instância. O passo a passo está no início rápido e a autenticação em autenticação. Guarde as credenciais em variáveis de ambiente: ZAPZAP_API_KEY, ZAPZAP_API_SECRET e INSTANCE_ID.
O endereço base é https://api.zapzapapi.com/api/v1. Os endpoints de envio ficam sob o ID da instância (por exemplo, /{instanceId}/send/text), e todas as chamadas levam os cabeçalhos x-api-key e x-api-secret. O número de destino vai com DDI e DDD, só dígitos, como 5511999999999. Nenhuma dependência externa é necessária: o exemplo funciona com a biblioteca padrão do Go.
Como montar um cliente HTTP em Go?
Concentre a autenticação, o timeout e o tratamento de erro em um único método. Isso evita repetir cabeçalhos, garante que toda chamada tem prazo e transforma respostas com status 400 ou maior em erro com o corpo incluído, o que facilita muito a depuração. Use sempre um http.Client próprio com Timeout: o cliente padrão não tem limite de tempo.
zapzap/client.go
package zapzap
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
)
const base = "https://api.zapzapapi.com/api/v1"
type Client struct {
HTTP *http.Client
Key string
Secret string
Instance string
}
func FromEnv() *Client {
return &Client{
HTTP: &http.Client{Timeout: 30 * time.Second},
Key: os.Getenv("ZAPZAP_API_KEY"),
Secret: os.Getenv("ZAPZAP_API_SECRET"),
Instance: os.Getenv("INSTANCE_ID"),
}
}
// do executa a chamada, decodifica o JSON em out e devolve erro com o corpo se status >= 400.
func (c *Client) do(method, path string, in, out any) error {
var body io.Reader
if in != nil {
b, err := json.Marshal(in)
if err != nil {
return err
}
body = bytes.NewReader(b)
}
req, err := http.NewRequest(method, base+path, body)
if err != nil {
return err
}
req.Header.Set("x-api-key", c.Key)
req.Header.Set("x-api-secret", c.Secret)
if in != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, err := c.HTTP.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= 400 {
return fmt.Errorf("zapzap %s %s: status %d: %s", method, path, resp.StatusCode, raw)
}
if out != nil {
return json.Unmarshal(raw, out)
}
return nil
}Como enviar texto pelo Go?
O endpoint enviar texto recebe number e text. Campos opcionais úteis: delay (em milissegundos), replyid (para responder a uma mensagem) e digitando, que vem ligado por padrão e mostra 'digitando...' por um tempo proporcional ao tamanho do texto. A resposta traz messageId e status. Guarde o messageId se quiser correlacionar com os eventos de status do webhook.
zapzap/send.go
package zapzap
// SendText envia uma mensagem de texto e devolve o messageId.
func (c *Client) SendText(number, text string) (string, error) {
var out map[string]any
err := c.do("POST", "/"+c.Instance+"/send/text", map[string]any{
"number": number,
"text": text,
}, &out)
if err != nil {
return "", err
}
id, _ := out["messageId"].(string)
return id, nil
}
// SendMedia envia imagem, video, audio, documento etc. a partir de uma URL publica.
// mediaType: image, video, audio, ptt, document, sticker.
func (c *Client) SendMedia(number, mediaType, fileURL, caption string) (string, error) {
var out map[string]any
payload := map[string]any{
"number": number,
"type": mediaType,
"file": fileURL,
}
if caption != "" {
payload["text"] = caption
}
if err := c.do("POST", "/"+c.Instance+"/send/media", payload, &out); err != nil {
return "", err
}
id, _ := out["messageId"].(string)
return id, nil
}main.go
package main
import (
"fmt"
"log"
"example.com/app/zapzap"
)
func main() {
c := zapzap.FromEnv()
id, err := c.SendText("5511999999999", "Olá! Seu pedido saiu para entrega.")
if err != nil {
log.Fatal(err)
}
fmt.Println("texto enviado:", id)
id, err = c.SendMedia("5511999999999", "document", "https://exemplo.com/nota.pdf", "Segue a sua nota fiscal.")
if err != nil {
log.Fatal(err)
}
fmt.Println("documento enviado:", id)
}Como validar o número antes de enviar?
Enviar para número que não existe no WhatsApp gera falha e, em volume, é um padrão que o WhatsApp penaliza. O endpoint verificar número recebe uma lista em numbers e devolve, para cada item, query, isInWhatsapp e jid. Valide em lote antes de uma campanha e remova os inexistentes. A mesma lógica vale para o cuidado com ritmo e lista descrito no guia de anti-bloqueio.
zapzap/check.go
package zapzap
// Existentes devolve apenas os numeros que estao no WhatsApp.
func (c *Client) Existentes(numbers []string) ([]string, error) {
var out []map[string]any
err := c.do("POST", "/"+c.Instance+"/chat/check", map[string]any{"numbers": numbers}, &out)
if err != nil {
return nil, err
}
var ok []string
for _, item := range out {
if in, _ := item["isInWhatsapp"].(bool); in {
q, _ := item["query"].(string)
ok = append(ok, q)
}
}
return ok, nil
}Quais erros tratar e como fazer retry?
| Status | Significado provável | Ação no código |
|---|---|---|
| 400 | Requisição inválida (campo faltando, número malformado) | Não repetir. Corrigir o dado |
| 401 ou 403 | Chave, segredo ou permissão | Não repetir. Conferir credenciais |
| 402 | Falta de saldo | Alertar a operação. Não repetir |
| 404 | ID de instância errado | Conferir o ID |
| 409 | Operação em andamento (por exemplo, geração de QR) | Esperar e tentar de novo |
| 429 | Excesso de requisições | Retry com espera crescente |
| 5xx ou timeout | Falha momentânea | Retry com espera crescente e limite de tentativas |
Em Go, o padrão é um laço com número máximo de tentativas e espera exponencial, repetindo só para 429, 5xx e erros de rede. Para 4xx de validação, repetir é inútil. Se o envio for importante, guarde o messageId e confirme o status pelo webhook, em vez de reenviar às cegas, porque reenviar uma mensagem que já saiu duplica o texto para o cliente. A lista completa de endpoints está na referência da API e na página de API de WhatsApp.
Como receber o webhook em Go com assinatura?
Configure a URL em configurar webhook e guarde o webhook_secret. Cada entrega vem com X-ZapZap-Signature no formato t=<unix>,v1=<hmac>, onde o HMAC é SHA-256 de t, ponto e corpo bruto. Leia o corpo inteiro antes de decodificar, calcule o HMAC e compare com hmac.Equal. Responda 200 rápido e processe em uma goroutine ou fila. As regras de idempotência e retry estão em webhook em produção.
webhook.go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"io"
"log"
"math"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func assinaturaValida(secret string, raw []byte, header string) bool {
var t int64
var v1 string
for _, p := range strings.Split(header, ",") {
kv := strings.SplitN(strings.TrimSpace(p), "=", 2)
if len(kv) != 2 {
continue
}
switch kv[0] {
case "t":
t, _ = strconv.ParseInt(kv[1], 10, 64)
case "v1":
v1 = kv[1]
}
}
if t == 0 || v1 == "" || math.Abs(float64(time.Now().Unix()-t)) > 300 {
return false
}
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(strconv.FormatInt(t, 10) + "."))
mac.Write(raw)
esperado := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(esperado), []byte(v1))
}
func main() {
secret := os.Getenv("ZAPZAP_WEBHOOK_SECRET")
http.HandleFunc("/webhooks/zapzap", func(w http.ResponseWriter, r *http.Request) {
raw, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 2<<20))
if err != nil || !assinaturaValida(secret, raw, r.Header.Get("X-ZapZap-Signature")) {
w.WriteHeader(http.StatusUnauthorized)
return
}
var evento map[string]any
if json.Unmarshal(raw, &evento) != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK) // confirma primeiro
go func(id string, e map[string]any) { // processa depois
data, _ := e["data"].(map[string]any)
msg, _ := data["message"].(map[string]any)
if fromMe, _ := msg["fromMe"].(bool); fromMe || msg == nil {
return
}
log.Println("entrega", id, "mensagem de", msg["chatid"], ":", msg["text"])
}(r.Header.Get("X-ZapZap-Id"), evento)
})
log.Fatal(http.ListenAndServe(":3000", nil))
}Perguntas de produção: o que mais cuidar?
Concorrência: se você envia em loop, mantenha uma pausa variável entre mensagens e não dispare milhares em paralelo; o ritmo é parte do cuidado com o número, como explica o guia anti-bloqueio. Conexão: antes de campanhas, consulte o status da instância. Logs: registre o messageId, o número (mascarado) e o status, nunca a chave e o segredo.
Se você prefere ver o mesmo fluxo em Node, Python e PHP, há o guia de enviar mensagem por API em Node, Python e PHP. Para .NET, veja o guia de C# com HttpClient. Custos por número estão em preços.
Resumo rápido
- Enviar WhatsApp em Go é um POST com x-api-key e x-api-secret em /{instanceId}/send/text, com number e text.
- Centralize autenticação, timeout e erro em um método e use http.Client com Timeout.
- Valide a lista com o endpoint de verificação de número antes de campanhas.
- Repita só 429, 5xx e erros de rede. Erros 4xx de validação não adiantam repetir.
- No webhook, valide X-ZapZap-Signature sobre o corpo bruto, responda 200 e processe depois.
Continue lendo
Perguntas frequentes
Preciso de alguma biblioteca para usar a API em Go?
Não. A biblioteca padrão (net/http e encoding/json) basta. Os exemplos deste guia não usam dependências externas.
Como autenticar as chamadas em Go?
Com os cabeçalhos x-api-key e x-api-secret em toda requisição. Guarde os valores em variáveis de ambiente.
Como enviar imagem ou PDF pelo Go?
Use o endpoint de enviar mídia com number, type (image, document, etc.) e file com a URL pública do arquivo. O campo text vira a legenda.
Como validar a assinatura do webhook em Go?
Calcule o HMAC SHA-256 de t, ponto e corpo bruto com o webhook_secret, compare com hmac.Equal ao v1 do cabeçalho X-ZapZap-Signature e rejeite timestamps com mais de 5 minutos.
Posso mandar mensagem em massa pelo Go?
Tecnicamente sim, mas o ritmo e a qualidade da lista determinam o risco para o número. Use pausas variáveis, valide a lista e leia o guia de anti-bloqueio. Nenhuma ferramenta elimina o risco de restrição.
Rode o primeiro envio em Go hoje
Crie a conta, verifique o telefone e receba o crédito de boas-vindas, sem depositar (um por telefone, sujeito à campanha vigente). Conecte por QR code e copie o cliente deste guia.
