Enviar WhatsApp com C# (.NET) via API REST
Para enviar WhatsApp com C# e .NET, use um HttpClient com a URL base https://api.zapzapapi.com/api/v1/ e os cabeçalhos x-api-key e x-api-secret, e faça um POST em {instanceId}/send/text com number e text. Este guia mostra o cliente com injeção de dependência, o envio de texto e mídia, a validação de números e um endpoint ASP.NET Core que recebe webhooks com checagem de assinatura HMAC. A ZapZap é uma API não oficial: o número é conectado por QR code, e esse uso não é previsto nos Termos do WhatsApp.
Publicado em · 5 min de leitura
O que você precisa antes de começar?
Uma conta, uma instância conectada por QR code, chave e segredo de API e o ID da instância (início rápido e autenticação). Guarde tudo em user secrets ou variáveis de ambiente, nunca no repositório. O exemplo usa .NET 8 ou superior, System.Net.Http.Json e System.Text.Json, que já vêm no framework. O registro com AddHttpClient em um aplicativo de console exige o pacote Microsoft.Extensions.Http, que já vem em projetos ASP.NET Core.
O número de destino vai com DDI e DDD, só dígitos (5511999999999). A resposta de envio traz messageId e status. Os campos opcionais do texto incluem delay em milissegundos, replyid e digitando, que vem ligado por padrão.
Como configurar o HttpClient com injeção de dependência?
Não crie um HttpClient novo a cada envio: isso esgota sockets sob carga. Registre um cliente tipado com AddHttpClient, que gerencia o ciclo de vida das conexões, e coloque a URL base e os cabeçalhos de autenticação no registro. A barra final da BaseAddress importa: sem ela, o caminho relativo pode ser montado de forma errada.
Program.cs (registro do cliente)
using Microsoft.Extensions.DependencyInjection;
var services = new ServiceCollection();
services.AddHttpClient<ZapZapClient>(http =>
{
http.BaseAddress = new Uri("https://api.zapzapapi.com/api/v1/");
http.Timeout = TimeSpan.FromSeconds(30);
http.DefaultRequestHeaders.Add("x-api-key", Environment.GetEnvironmentVariable("ZAPZAP_API_KEY"));
http.DefaultRequestHeaders.Add("x-api-secret", Environment.GetEnvironmentVariable("ZAPZAP_API_SECRET"));
});
var provider = services.BuildServiceProvider();
var zap = provider.GetRequiredService<ZapZapClient>();
var id = await zap.SendTextAsync("5511999999999", "Olá! Seu pedido saiu para entrega.");
Console.WriteLine("texto enviado: " + id);
var idDoc = await zap.SendMediaAsync("5511999999999", "document", "https://exemplo.com/nota.pdf", "Segue a sua nota fiscal.");
Console.WriteLine("documento enviado: " + idDoc);Como enviar texto e mídia em C#?
A classe abaixo envolve os endpoints enviar texto e enviar mídia, mais a verificação de número. Ela usa PostAsJsonAsync para serializar o corpo e EnsureSuccessStatusCode apenas depois de ler o corpo do erro, para que a mensagem da API não se perca. Em enviar mídia, type aceita image, video, audio, ptt, document e sticker, e file recebe a URL pública do arquivo.
ZapZapClient.cs
using System.Net.Http.Json;
using System.Text.Json;
public class ZapZapClient
{
private readonly HttpClient _http;
private readonly string _instance = Environment.GetEnvironmentVariable("INSTANCE_ID")!;
public ZapZapClient(HttpClient http) => _http = http;
public Task<string?> SendTextAsync(string number, string text) =>
PostAsync($"{_instance}/send/text", new { number, text });
public Task<string?> SendMediaAsync(string number, string type, string fileUrl, string? caption = null)
{
var body = new Dictionary<string, object> { ["number"] = number, ["type"] = type, ["file"] = fileUrl };
if (!string.IsNullOrEmpty(caption)) body["text"] = caption;
return PostAsync($"{_instance}/send/media", body);
}
// devolve apenas os numeros que existem no WhatsApp
public async Task<List<string>> ExistentesAsync(IEnumerable<string> numbers)
{
using var resp = await _http.PostAsJsonAsync($"{_instance}/chat/check", new { numbers });
var json = await resp.Content.ReadAsStringAsync();
if (!resp.IsSuccessStatusCode)
throw new HttpRequestException($"zapzap {(int)resp.StatusCode}: {json}");
var ok = new List<string>();
using var doc = JsonDocument.Parse(json);
foreach (var item in doc.RootElement.EnumerateArray())
{
if (item.GetProperty("isInWhatsapp").GetBoolean())
ok.Add(item.GetProperty("query").GetString()!);
}
return ok;
}
private async Task<string?> PostAsync(string path, object body)
{
using var resp = await _http.PostAsJsonAsync(path, body);
var json = await resp.Content.ReadAsStringAsync();
if (!resp.IsSuccessStatusCode)
throw new HttpRequestException($"zapzap {(int)resp.StatusCode}: {json}");
using var doc = JsonDocument.Parse(json);
return doc.RootElement.TryGetProperty("messageId", out var id) ? id.GetString() : null;
}
}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 | 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 .NET, a forma idiomática é uma política de resiliência no AddHttpClient (a biblioteca Microsoft.Extensions.Http.Resilience, que usa Polly) com retry exponencial para 429, 5xx e falhas de rede. Cuidado com envio: reenviar uma mensagem que já saiu duplica o texto para o cliente. Para o que é importante, guarde o messageId e confirme o status pelo webhook, em vez de repetir às cegas. A lista de endpoints está na referência da API e na página de API de WhatsApp.
Como receber o webhook no ASP.NET Core com assinatura?
Configure a URL em configurar webhook e guarde o webhook_secret. Cada entrega traz X-ZapZap-Signature (t=<unix>,v1=<hmac>), em que o HMAC é SHA-256 de t, ponto e corpo bruto. Leia o corpo como bytes antes de qualquer desserialização, calcule o HMAC e compare em tempo constante com CryptographicOperations.FixedTimeEquals. Responda 200 rápido e coloque o trabalho em uma fila (Channel, BackgroundService ou um broker). Idempotência e retry estão em webhook em produção.
WebhookApi/Program.cs (ASP.NET Core minimal API)
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
var app = WebApplication.CreateBuilder(args).Build();
var secret = Environment.GetEnvironmentVariable("ZAPZAP_WEBHOOK_SECRET")!;
app.MapPost("/webhooks/zapzap", async (HttpRequest req) =>
{
using var ms = new MemoryStream();
await req.Body.CopyToAsync(ms);
var raw = ms.ToArray();
if (!Valida(secret, raw, req.Headers["X-ZapZap-Signature"].ToString()))
return Results.Unauthorized();
var deliveryId = req.Headers["X-ZapZap-Id"].ToString(); // chave de deduplicacao
using var doc = JsonDocument.Parse(raw);
var root = doc.RootElement;
if (root.GetProperty("event").GetString() == "messages"
&& root.TryGetProperty("data", out var data)
&& data.TryGetProperty("message", out var msg)
&& !(msg.TryGetProperty("fromMe", out var fm) && fm.GetBoolean()))
{
Console.WriteLine($"{deliveryId}: {msg.GetProperty("chatid").GetString()} -> {msg.GetProperty("text").GetString()}");
// aqui: enfileirar o trabalho em vez de processar na requisicao
}
return Results.Ok();
});
static bool Valida(string secret, byte[] raw, string header)
{
long t = 0; string v1 = "";
foreach (var parte in header.Split(','))
{
var kv = parte.Trim().Split('=', 2);
if (kv.Length != 2) continue;
if (kv[0] == "t") long.TryParse(kv[1], out t);
if (kv[0] == "v1") v1 = kv[1];
}
if (t == 0 || v1.Length == 0) return false;
if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - t) > 300) return false;
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
var payload = Encoding.UTF8.GetBytes(t + ".").Concat(raw).ToArray();
var esperado = Convert.ToHexString(hmac.ComputeHash(payload)).ToLowerInvariant();
return CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(esperado), Encoding.UTF8.GetBytes(v1));
}
app.Run();Perguntas de produção: o que mais cuidar?
Ritmo: se você envia em lote, use uma pausa variável entre mensagens (Task.Delay com valor aleatório) e não dispare milhares de tarefas em paralelo. O ritmo e a qualidade da lista são o que mais pesa para o número, como descrito no guia anti-bloqueio. Conexão: consulte o status da instância antes de campanhas. Segredos: user secrets em desenvolvimento e cofre de segredos em produção.
Para o mesmo fluxo em outras linguagens, veja o guia de Node, Python e PHP e o de Go. Preços por número ativo estão em preços.
Como testar com curl antes de escrever código?
Antes de depurar C#, confirme que as credenciais, o ID e o número funcionam com um curl. Se o curl responde com messageId e status, o problema, se houver, está no código. Se o curl falha, o problema está na credencial, na instância ou no número. Esse teste de dois minutos evita horas de depuração no lugar errado.
Também vale conferir o estado da instância antes de qualquer teste: uma instância desconectada não envia. O guia de QR code que não aparece e o de WhatsApp que desconecta sozinho cobrem os casos mais comuns.
Teste de envio com curl
curl -X POST "https://api.zapzapapi.com/api/v1/$INSTANCE_ID/send/text" \
-H "x-api-key: $ZAPZAP_API_KEY" \
-H "x-api-secret: $ZAPZAP_API_SECRET" \
-H "Content-Type: application/json" \
-d '{"number":"5511999999999","text":"Teste de envio"}'
# resposta esperada: {"messageId":"...","status":"sent"}Resumo rápido
- Enviar WhatsApp em C# é um POST em {instanceId}/send/text com os cabeçalhos x-api-key e x-api-secret.
- Registre um HttpClient tipado com AddHttpClient e deixe a URL base com barra final.
- Valide os números com o endpoint de verificação antes de campanhas.
- Use retry exponencial só para 429, 5xx e falhas de rede, e confirme envios importantes pelo webhook.
- No webhook, valide X-ZapZap-Signature sobre os bytes do corpo com FixedTimeEquals e responda 200 rápido.
Continue lendo
Perguntas frequentes
Preciso de SDK para usar a API em C#?
Não. Um HttpClient com System.Net.Http.Json e System.Text.Json basta. Os exemplos não usam pacotes de terceiros (o AddHttpClient vem de Microsoft.Extensions.Http, já incluído em ASP.NET Core).
Como autenticar as chamadas em .NET?
Adicione os cabeçalhos x-api-key e x-api-secret ao DefaultRequestHeaders do HttpClient registrado, lendo os valores de variáveis de ambiente ou user secrets.
Como enviar imagem ou PDF em C#?
Use o endpoint de enviar mídia com number, type (image, document e outros) e file com a URL pública do arquivo. O campo text funciona como legenda.
Como validar a assinatura do webhook no ASP.NET Core?
Leia o corpo como bytes, calcule HMAC SHA-256 de t, ponto e corpo com o webhook_secret e compare com o v1 do cabeçalho X-ZapZap-Signature usando CryptographicOperations.FixedTimeEquals. Rejeite timestamps com mais de 5 minutos.
Qual o risco de usar a API não oficial a partir do meu sistema?
O uso não oficial não é previsto nos Termos do WhatsApp e o número pode ser restringido, independentemente da linguagem. Ritmo, consentimento e lista limpa reduzem o risco, mas não o eliminam.
Rode o primeiro envio em C# 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.
