Dev

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

Este artigo também está disponível em: English · Español

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?

StatusSignificado provávelAção no código
400Requisição inválida (campo faltando, número malformado)Não repetir. Corrigir o dado
401 ou 403Chave, segredo ou permissãoNão repetir. Conferir credenciais
402Falta de saldoAlertar a operação. Não repetir
404ID de instância erradoConferir o ID
409Operação em andamento (por exemplo, geração de QR)Esperar e tentar de novo
429Excesso de requisiçõesRetry com espera crescente
5xx ou timeoutFalha momentâneaRetry 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.

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.