SoftZap.Client
0.0.1-preview.5
dotnet add package SoftZap.Client --version 0.0.1-preview.5
NuGet\Install-Package SoftZap.Client -Version 0.0.1-preview.5
<PackageReference Include="SoftZap.Client" Version="0.0.1-preview.5" />
<PackageVersion Include="SoftZap.Client" Version="0.0.1-preview.5" />
<PackageReference Include="SoftZap.Client" />
paket add SoftZap.Client --version 0.0.1-preview.5
#r "nuget: SoftZap.Client, 0.0.1-preview.5"
#:package SoftZap.Client@0.0.1-preview.5
#addin nuget:?package=SoftZap.Client&version=0.0.1-preview.5&prerelease
#tool nuget:?package=SoftZap.Client&version=0.0.1-preview.5&prerelease
SoftZap.Client
SDK em C# para integração com a SoftZap — API para gerenciamento de instâncias do WhatsApp e envio de mensagens.
Instalação
dotnet add package SoftZap.Client
Configuração
Informe apiKey (admin) e/ou token (instância) — ao menos um é obrigatório.
A URL é a URL base da API, incluindo o segmento
/api(ex:https://seu-servidor.com/api). A barra final é opcional.
Uso direto
using SoftZap.Client;
// Admin (gerenciar instâncias)
var client = new SoftZapClient("https://seu-servidor.com/api", "minha-api-key");
// Instância única (token padrão para mensagens/contatos/grupos/chats)
var client = new SoftZapClient("https://seu-servidor.com/api", token: "token-da-instancia");
// Ambos
var client = new SoftZapClient("https://seu-servidor.com/api", "minha-api-key", "token-da-instancia");
// Com log das requisições (passe um ILogger e ligue enableLogging)
var client = new SoftZapClient("https://seu-servidor.com/api", token: "token-da-instancia", logger: meuLogger, enableLogging: true);
Injeção de dependência
// Program.cs ou Startup.cs
services.AddSoftZap("https://seu-servidor.com/api", "minha-api-key");
// Com token padrão de instância
services.AddSoftZap("https://seu-servidor.com/api", token: "token-da-instancia");
// Ou via Options pattern (inclui EnableLogging)
services.AddSoftZap(options =>
{
options.ApiUrl = "https://seu-servidor.com/api";
options.ApiKey = "minha-api-key"; // ApiKey e/ou Token
options.Token = "token-da-instancia";
options.EnableLogging = true; // liga/desliga o log das requisições
});
// Ou a partir da configuração (seção "SoftZap" do appsettings.json)
services.AddSoftZap(builder.Configuration);
appsettings.json para o overload de configuração:
{
"SoftZap": {
"ApiUrl": "https://seu-servidor.com/api",
"ApiKey": "minha-api-key",
"Token": "token-da-instancia",
"EnableLogging": true
}
}
// No seu serviço ou controller
public class MeuServico(ISoftZapClient client)
{
// use client.Instances, client.Messages, etc.
}
Logging das requisições
Quando EnableLogging está ligado (e há um ILogger disponível — automático no DI), todas as requisições e respostas são logadas: método, URL, corpo, status HTTP e tempo de resposta.
info: SoftZap.Client.SoftZapClient[0]
SoftZap -> POST https://seu-servidor.com/api/messages/text
{"number":"5511999999999","message":"Olá!"}
info: SoftZap.Client.SoftZapClient[0]
SoftZap <- 200 POST https://seu-servidor.com/api/messages/text (243ms)
{"id":"3EB0...","status":"sent"}
- Request e resposta de sucesso → nível
Information; falhas (status ≥ 400) →Warning. EnableLogging = false(padrão) → nenhum log e zero overhead (os corpos nem são lidos).- O corpo das mensagens é logado; cuidado com dados sensíveis em produção — ligue só quando necessário.
Autenticação
A SoftZap utiliza dois escopos de autenticação:
| Escopo | Header | Vale para | Descrição |
|---|---|---|---|
| API Key global | apikey |
Rotas /instances/** |
Chave global do servidor. Administra instâncias. |
| Token da instância | token |
Demais rotas | Token específico de cada instância. Envia mensagens. |
Ambos são configurados no cliente (construtor/options) e enviados automaticamente como header.
Token padrão vs. token por chamada
Quando o token é configurado no cliente, os métodos token-scoped podem ser chamados sem informá-lo — usa-se o token padrão. Cada método tem também uma sobrecarga que recebe o token como primeiro argumento, útil para operar várias instâncias com o mesmo cliente:
// Usa o token padrão configurado
await client.Messages.SendText(request);
await client.Chats.List();
// Informa o token explicitamente (override)
await client.Messages.SendText("outro-token", request);
await client.Chats.List("outro-token");
Serviços Disponíveis
| Serviço | Escopo | Descrição |
|---|---|---|
client.Instances |
apikey |
Gerenciamento administrativo de instâncias |
client.Messages |
token |
Envio e manipulação de mensagens |
client.Contacts |
token |
Contatos e verificação de números |
client.Groups |
token |
Grupos do WhatsApp |
client.Chats |
token |
Chats e histórico de mensagens |
client.Instance |
token |
Self-service da própria instância |
InstanceService (client.Instances)
Gerenciamento administrativo do ciclo de vida das instâncias. Autenticação por apikey.
// Listar instâncias
var instancias = await client.Instances.List();
// Criar instância (guarde o Id e o Token)
var instancia = await client.Instances.Create(new CreateInstanceRequest
{
Name = "Minha Instância",
ExternalId = "seu-identificador", // opcional; ecoado no envelope de todo webhook
WebhookSelf = true, // opcional; false não dispara webhook das mensagens fromMe
Webhooks =
[
new WebhookRequest
{
Name = "Principal",
Url = "https://meu-webhook.com/hook",
Events = [WebhookEvent.MESSAGES_UPSERT]
}
]
});
var id = instancia.Id;
var token = instancia.Token;
// Buscar instância
var dados = await client.Instances.Get(id);
// Atualizar dados e webhooks (a lista substitui a anterior; vazia remove todos)
await client.Instances.Update(id, new UpdateInstanceRequest
{
Name = "Novo Nome",
ExternalId = "seu-identificador",
WebhookSelf = false,
Webhooks = []
});
// Conectar (reabre o socket; dispositivo pareado conecta sem QR)
var conexao = await client.Instances.Connect(id);
// conexao.Status (ex: "connecting")
// Iniciar pareamento / obter QR Code
var qr = await client.Instances.QrCode(id);
// qr.QrCode contém o data URL (PNG) do QR Code
// Polling do status
var status = await client.Instances.Status(id);
// status.Connected, status.LoggedIn
// Reiniciar / desconectar
await client.Instances.Restart(id);
await client.Instances.Disconnect(id);
// Excluir (exclusão lógica)
await client.Instances.Delete(id);
MessageService (client.Messages)
Envio e manipulação de mensagens. Autenticação por token. Exige a instância conectada.
Enviar para grupo: o campo
Numberaceita também o JID do grupo (...@g.us) ou o id do grupo (só dígitos, mais de 15 caracteres). Vale para todos os envios. Obtenha o JID emclient.Groups.List().
Enviar texto
var msg = await client.Messages.SendText(token, new SendTextRequest
{
Number = "5511999999999",
Message = "Olá!",
ReplyId = "ID-da-mensagem-a-responder", // opcional
ExternalId = "seu-identificador", // opcional; ecoado no webhook desta mensagem
Delay = 1000
});
// msg.Id - use para reply, reação, edição ou exclusão
Enviar botões
await client.Messages.SendButtons(token, new SendButtonsRequest
{
Number = "5511999999999",
Message = "Confirma o pedido?",
Title = "Pedido #123", // opcional; ignorado quando há MediaUrl
Footer = "Loja XPTO",
MediaUrl = "https://exemplo.com/banner.jpg", // opcional; mídia do cabeçalho
MediaType = MediaType.IMAGE, // opcional; image | video | document
Buttons =
[
new ButtonRequest { Type = ButtonType.REPLY, Text = "Sim", Id = "sim" },
new ButtonRequest { Type = ButtonType.REPLY, Text = "Não", Id = "nao" }
]
});
Regras: não misture REPLY com botões de ação (COPY/URL/CALL). Até 3 botões REPLY ou até 2 de ação. Botões de ação renderizam só no app do celular. O rótulo tem no máximo 20 caracteres (o excedente é cortado).
Tipos de botão (ButtonType):
| Constante | Valor | Campo extra | Descrição |
|---|---|---|---|
REPLY |
reply |
Id |
Resposta rápida (padrão) |
COPY |
copy |
Code |
Copia um código |
URL |
url |
Url |
Abre um link |
CALL |
call |
Phone |
Disca um número |
Enviar lista
await client.Messages.SendList(token, new SendListRequest
{
Number = "5511999999999",
Message = "Escolha uma opção:",
Title = "Cardápio", // opcional
Footer = "Loja XPTO", // opcional
ButtonText = "Ver opções", // opcional (padrão "Ver opções")
Sections =
[
new ListSectionRequest
{
Title = "Bebidas",
Rows =
[
new ListRowRequest { Id = "cafe", Title = "Café", Description = "Expresso 50ml" },
new ListRowRequest { Id = "cha", Title = "Chá", Description = "Camomila" }
]
},
new ListSectionRequest
{
Title = "Comidas",
Rows = [new ListRowRequest { Id = "pao", Title = "Pão de queijo" }]
}
]
});
A escolha do usuário chega no webhook como type: listReply, com o Id (RowID) e o Text da linha selecionada. A renderização depende do aparelho (listas não são oficialmente suportadas fora da Cloud API).
Enviar mídia
// Por URL (modo JSON)
await client.Messages.SendMedia(token, new SendMediaRequest
{
Number = "5511999999999",
Type = MediaType.IMAGE,
Url = "https://exemplo.com/foto.jpg",
Caption = "Olha isso"
});
// Upload de arquivo binário (modo multipart)
await client.Messages.SendMediaFile(token, new SendMediaFileRequest
{
Number = "5511999999999",
Type = MediaType.DOCUMENT,
File = File.ReadAllBytes("contrato.pdf"),
MimeType = "application/pdf",
FileName = "Contrato.pdf"
});
// Documento por URL - Name define o nome exibido no WhatsApp
await client.Messages.SendMedia(token, new SendMediaRequest
{
Number = "5511999999999",
Type = MediaType.DOCUMENT,
Url = "https://exemplo.com/contrato.pdf",
Name = "Contrato.pdf"
});
// Nota de voz (ptt) - convertida para OGG/Opus pelo servidor
await client.Messages.SendMedia(token, new SendMediaRequest
{
Number = "5511999999999",
Type = MediaType.PTT,
Url = "https://exemplo.com/audio.mp3"
});
Tipos de mídia (MediaType): IMAGE, VIDEO, AUDIO, PTT, DOCUMENT.
Reagir, editar e excluir
await client.Messages.SendReaction(token, new SendReactionRequest
{
Number = "5511999999999",
Id = "ID-da-mensagem",
Emoji = "👍" // vazio remove a reação
});
await client.Messages.UpdateMessage(token, new UpdateMessageRequest
{
Number = "5511999999999",
Id = "ID-da-mensagem",
Message = "Texto corrigido"
});
await client.Messages.DeleteMessage(token, new DeleteMessageRequest
{
Number = "5511999999999",
Id = "ID-da-mensagem"
});
Atualizar presença (digitando/gravando)
await client.Messages.SendPresence(token, new SendPresenceRequest
{
Number = "5511999999999",
Presence = Presence.COMPOSING, // composing | recording | paused
Duration = 3000 // ms (máx 20000); só para composing/recording
});
Estados de presença (Presence): COMPOSING (digitando), RECORDING (gravando áudio), PAUSED (parado).
Ressincronizar histórico do chat
Solicita ao celular as mensagens anteriores à mais recente já persistida do chat, preenchendo buracos deixados por falha de descriptografia. Exige a instância conectada e o celular online.
await client.Messages.Resync(token, new ResyncMessagesRequest
{
Number = "5511999999999",
Count = 50 // opcional; padrão 50
});
O resultado chega de forma assíncrona como backfill de histórico (gravado no banco e visível em client.Chats); não dispara novos eventos messages.upsert. Não alcança a última mensagem do chat em si. Responde 404 com SoftZapErrorCode.NO_CHAT_HISTORY quando o chat ainda não tem nenhuma mensagem persistida.
Envio assíncrono
Os envios de texto, botões, lista e mídia suportam Async = true: a API responde imediatamente (status: accepted) e envia em background; o status final chega via webhook. Reação, edição, exclusão, presença e resync são sempre síncronos.
var envio = await client.Messages.SendText(token, new SendTextRequest
{
Number = "5511999999999",
Message = "Mensagem em background",
Async = true
});
// envio.Status == "accepted"
ContactService (client.Contacts)
Contatos do WhatsApp. Autenticação por token. Exige a instância conectada.
// Verificar números no WhatsApp
var resultado = await client.Contacts.Check(token, ["5511999999999", "5511888888888"]);
foreach (var item in resultado)
{
Console.WriteLine($"{item.Number}: {(item.Exists ? "existe" : "não existe")}");
}
// Listar a agenda sincronizada (paginada)
var contatos = await client.Contacts.List(token, limit: 100, offset: 0);
// contatos.Data, contatos.Total, contatos.HasMore
// Detalhes de um contato
var detalhes = await client.Contacts.Get(token, "5511999999999");
// detalhes.FullName, detalhes.ProfilePicture, detalhes.Status
Para nome, prefira
FullName(sync da agenda) ouVerifiedName(contas business);PushNamesó é preenchido quando o contato envia mensagem.ProfilePictureé um link temporário: expira, baixe ao receber.
GroupService (client.Groups)
Grupos do WhatsApp. Autenticação por token. Exige a instância conectada.
// Listar os grupos que a instância participa (sem foto)
var grupos = await client.Groups.List(token);
foreach (var grupo in grupos)
{
Console.WriteLine($"{grupo.Name} ({grupo.Size}) - {grupo.Jid}");
}
// Detalhes de um grupo (foto, descrição, criador e participantes)
var detalhes = await client.Groups.Get(token, "120363401263198982@g.us");
// detalhes.Topic, detalhes.Owner, detalhes.Image, detalhes.Participants
foreach (var p in detalhes.Participants)
{
Console.WriteLine($"{p.Number} - admin: {p.IsAdmin}");
}
A foto do grupo (
Image) é um link temporário do WhatsApp: a URL expira, baixe ao receber. OJidde cada participante é canônico: telefone@s.whatsapp.netquando conhecido,@lidsó quando o contato não expõe o número (aíNumbervem vazio — useLid).
ChatService (client.Chats)
Histórico persistido dos chats. Autenticação por token. Não exige a instância conectada.
// Listar chats mais recentes (paginada: .Data, .Total, .HasMore)
var chats = await client.Chats.List(token, limit: 50, offset: 0);
foreach (var chat in chats.Data)
{
Console.WriteLine($"{chat.Name}: {chat.LastMessage?.Text}");
}
// Mensagens de um chat (o chatJid é codificado para a URL automaticamente)
var mensagens = await client.Chats.GetMessages(token, "5511999999999@s.whatsapp.net", limit: 50);
// mensagens.Data, mensagens.Total, mensagens.HasMore
Cada mensagem é idêntica ao objeto data do webhook messages.upsert (ver WEBHOOK.md) — reaproveite o mesmo parser. As chamadas recebidas também aparecem aqui (Type == MessageType.CALL, com o objeto Call). O histórico é alimentado ao vivo e pelo backfill do HistorySync entregue no connect.
Download de mídia
Mensagens de mídia (image, video, audio, document, sticker) trazem Media.Url autoautenticada. Os métodos de extensão baixam o arquivo já decriptado (GET simples, sem headers):
foreach (var msg in mensagens.Data)
{
// só baixa se for mídia; ignora os demais tipos
if (await msg.SaveMediaToFileAsync($"C:/midia/{msg.Id}"))
{
Console.WriteLine($"baixado: {msg.Media.FileName} ({msg.Media.MimeType})");
}
}
// Em memória (null se a mensagem não tiver mídia)
byte[] bytes = await msg.DownloadMediaAsync();
// Stream da mensagem (null se não tiver mídia)
Stream stream = await msg.OpenReadMediaAsync();
// Quando já tem o MessageMedia em mãos (lança se não houver URL)
byte[] b = await msg.Media.DownloadAsync();
Stream s = await msg.Media.OpenReadAsync(); // streaming, sem carregar tudo em memória
await msg.Media.SaveToFileAsync("arquivo.pdf");
// Checagem manual
bool tem = msg.HasDownloadableMedia();
| Método | Alvo | Sem mídia | Falha HTTP |
|---|---|---|---|
DownloadMediaAsync |
MessageResponse |
retorna null |
HttpRequestException |
OpenReadMediaAsync |
MessageResponse |
retorna null |
HttpRequestException |
SaveMediaToFileAsync |
MessageResponse |
retorna false |
HttpRequestException |
DownloadAsync |
MessageMedia |
InvalidOperationException |
HttpRequestException |
OpenReadAsync |
MessageMedia |
InvalidOperationException |
HttpRequestException |
SaveToFileAsync |
MessageMedia |
InvalidOperationException |
HttpRequestException |
DownloadAsync |
MessageButtonHeader |
InvalidOperationException |
HttpRequestException |
OpenReadAsync |
MessageButtonHeader |
InvalidOperationException |
HttpRequestException |
SaveToFileAsync |
MessageButtonHeader |
InvalidOperationException |
HttpRequestException |
Todos aceitam um HttpClient opcional (usa um interno compartilhado se omitido) e CancellationToken. O link vale 24h (depois 410) e exige a instância conectada, salvo via S3 (permanente).
A mídia do cabeçalho de uma mensagem com botões (msg.Button.Header) baixa do mesmo jeito:
if (msg.Button?.Header is { Url.Length: > 0 } header)
{
byte[] capa = await header.DownloadAsync();
}
SelfInstanceService (client.Instance)
Gestão da própria instância pelo token dela (sem id na URL). Não usa a apikey.
var dados = await client.Instance.Get(token);
var status = await client.Instance.Status(token);
var conexao = await client.Instance.Connect(token); // reabre o socket
var qr = await client.Instance.QrCode(token); // inicia o pareamento
await client.Instance.Restart(token);
await client.Instance.Disconnect(token);
// Substituir os webhooks (nome da instância preservado)
await client.Instance.UpdateWebhooks(token, new UpdateWebhooksRequest
{
Webhooks =
[
new WebhookRequest
{
Name = "Principal",
Url = "https://meu-webhook.com/hook",
Events = [WebhookEvent.MESSAGES_UPSERT, WebhookEvent.MESSAGES_UPDATE]
}
]
});
Webhooks
Configure os webhooks na criação/atualização da instância ou via client.Instance.UpdateWebhooks. Cada evento gera um POST JSON assinado (header X-Webhook-Signature: sha256=<hex>, HMAC-SHA256 do corpo bruto com o token da instância).
O pacote traz WebhookEnvelope (modelo do envelope) e WebhookValidator (validação + parsing):
using SoftZap.Client;
using SoftZap.Client.Models.Responses.Chat;
app.MapPost("/hook", async (HttpRequest req) =>
{
using var ms = new MemoryStream();
await req.Body.CopyToAsync(ms);
var rawBody = ms.ToArray(); // corpo BRUTO, antes de qualquer parse
var assinatura = req.Headers[WebhookValidator.SignatureHeader].ToString();
if (!WebhookValidator.TryParse(rawBody, assinatura, "TOKEN_DA_INSTANCIA", out var envelope))
{
return Results.Unauthorized();
}
if (envelope.Event == WebhookEvent.MESSAGES_UPSERT)
{
var mensagem = envelope.GetMessageUpsert();
if (mensagem.FromMe)
{
return Results.Ok(); // ignore o eco das próprias mensagens
}
Console.WriteLine($"{mensagem.SenderName}: {mensagem.Text}");
}
return Results.Ok();
});
Eventos (WebhookEvent): MESSAGES_UPSERT, MESSAGES_UPDATE, PRESENCE_UPDATE, CONNECTION_UPDATE, HISTORY_SYNC, CALL_UPDATE. Detalhes do payload de cada evento em WEBHOOK.md.
| Membro | Descrição |
|---|---|
WebhookValidator.Validate(rawBody, signature, token) |
Valida a assinatura (byte[]/string) → bool |
WebhookValidator.Parse(rawBody) |
Desserializa no WebhookEnvelope |
WebhookValidator.TryParse(...) |
Valida e, se ok, desserializa o envelope |
WebhookEnvelope.GetData<T>() |
Desserializa o data no tipo informado |
GetMessageUpsert() |
data de messages.upsert → MessageResponse |
GetMessageUpdate() |
data de messages.update → MessageUpdateData |
GetPresenceUpdate() |
data de presence.update → PresenceUpdateData |
GetConnectionUpdate() |
data de connection.update → ConnectionUpdateData |
GetHistorySync() |
data de history.sync → HistorySyncData |
GetCallUpdate() |
data de call.update → MessageResponse (.Call) |
Loop de eco:
messages.upserttambém dispara para as mensagens enviadas pela própria instância (FromMe = true), inclusive as enviadas pelo celular. IgnoreFromMenas automações, ou crie a instância comWebhookSelf = false.
Tratamento de Erros
Todas as chamadas lançam SoftZapErrorException quando a API retorna um status HTTP de erro:
try
{
var status = await client.Instances.Status(id);
}
catch (SoftZapErrorException ex)
{
// ex.Code é o código estável (ver SoftZapErrorCode); ex.Message é legível
Console.WriteLine($"Erro {ex.StatusCode} [{ex.Code}]: {ex.Message}");
if (ex.Code == SoftZapErrorCode.NUMBER_NOT_ON_WHATSAPP)
{
// tratar caso específico
}
// Em 429 (rate limit), respeite o Retry-After
if (ex.RetryAfter is { } espera)
{
await Task.Delay(espera);
}
}
O corpo de erro segue o formato { "error": { "code": "INVALID_NUMBER", "message": "..." } }. Use ex.Code (constantes em SoftZapErrorCode) para tratar; ex.Message é legível e pode mudar.
| Status | Quando |
|---|---|
400 |
Requisição malformada (JSON/form/parâmetro) |
401 |
API Key ou token ausente/inválido |
403 |
Token de mídia inválido (download) |
404 |
Instância, grupo ou histórico do chat não encontrado |
409 |
Instância não está conectada (envio de mensagem) |
410 |
Link de mídia expirado (download) |
422 |
Erro de validação/negócio |
429 |
Rate limit excedido — veja ex.RetryAfter |
500 |
Erro interno do servidor |
Fluxo típico
var client = new SoftZapClient("https://seu-servidor.com/api", "minha-api-key");
// 1. Criar instância
var instancia = await client.Instances.Create(new CreateInstanceRequest { Name = "Vendas" });
var (id, token) = (instancia.Id, instancia.Token);
// 2. Iniciar conexão e ler o QR Code
var qr = await client.Instances.QrCode(id);
// 3. Aguardar o pareamento
StatusResponse status;
do
{
await Task.Delay(2000);
status = await client.Instances.Status(id);
}
while (!status.LoggedIn);
// 4. Enviar mensagens
await client.Messages.SendText(token, new SendTextRequest { Number = "5511999999999", Message = "Olá!" });
// 5. Encerrar
await client.Instances.Disconnect(id);
Modelos de Requisição
MessageOptionsBaseRequest
Base comum a SendTextRequest, SendButtonsRequest, SendListRequest e SendMediaRequest.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Number |
string |
sim | Número destino (ou JID/id de grupo) |
ReplyId |
string |
não | ID da mensagem a responder (reply) |
ExternalId |
string |
não | Identificador externo; devolvido no webhook desta mensagem |
Delay |
int? |
não | Tempo de "digitando..." em ms antes do envio (máx 20000) |
Async |
bool? |
não | true: responde 202 (accepted) e envia em background. Padrão false |
SendTextRequest
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Message |
string |
sim | Texto da mensagem |
SendButtonsRequest
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Message |
string |
sim | Texto principal |
Title |
string |
não | Cabeçalho de texto acima da mensagem; ignorado quando há MediaUrl |
Footer |
string |
não | Texto do rodapé |
MediaUrl |
string |
não | URL da mídia do cabeçalho (imagem/vídeo/documento acima do texto) |
MediaType |
string |
não | Override image | video | document; só usado com MediaUrl |
Buttons |
List<ButtonRequest> |
sim | Lista de botões |
Herda também
Number,ReplyId,ExternalId,Delay,AsyncdeMessageOptionsBaseRequest.
ButtonRequest
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Type |
string |
não | reply (padrão) | copy | url | call (ver ButtonType) |
Text |
string |
sim | Rótulo do botão |
Id |
string |
cond. | reply: ID retornado ao tocar (usa Text se vazio) |
Code |
string |
cond. | copy: código copiado |
Url |
string |
cond. | url: link aberto |
Phone |
string |
cond. | call: número discado |
SendListRequest
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Message |
string |
sim | Texto principal (corpo da lista) |
Title |
string |
não | Cabeçalho de texto acima do corpo |
Footer |
string |
não | Texto do rodapé |
ButtonText |
string |
não | Rótulo do botão que abre a lista (padrão Ver opções) |
Sections |
List<ListSectionRequest> |
sim | Seções (ao menos uma com linhas válidas) |
Herda também
Number,ReplyId,ExternalId,Delay,AsyncdeMessageOptionsBaseRequest.
ListSectionRequest / ListRowRequest
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Title |
string |
não | Título da seção |
Rows |
List<ListRowRequest> |
sim | Opções da seção |
Rows[].Id |
string |
não | Valor retornado ao selecionar (usa Title se vazio) |
Rows[].Title |
string |
sim | Rótulo da opção |
Rows[].Description |
string |
não | Subtítulo da opção |
SendMediaRequest (modo JSON — por URL)
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Type |
string |
não | image | video | audio | ptt | document (ver MediaType) |
Url |
string |
sim | URL da mídia a baixar pelo servidor (até 100 MB) |
Caption |
string |
não | Legenda (ignorada para ptt) |
MimeType |
string |
não | Tipo MIME (detectado se vazio; ignorado para ptt) |
Name |
string |
não | Nome do documento exibido no WhatsApp (só document) |
Herda também
Number,ReplyId,ExternalId,Delay,AsyncdeMessageOptionsBaseRequest.
SendMediaFileRequest (modo multipart — upload de arquivo)
Informe File (binário) ou Url.
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Number |
string |
sim | Número destino (ou JID/id de grupo) |
Type |
string |
não | Tipo de mídia (ver MediaType) |
File |
byte[] |
cond. | Conteúdo binário do arquivo (alternativa a Url) |
Url |
string |
cond. | URL da mídia (alternativa a File) |
Caption |
string |
não | Legenda (ignorada para ptt) |
MimeType |
string |
não | Tipo MIME |
FileName |
string |
não | Nome do arquivo enviado (e do documento) |
ReplyId |
string |
não | ID da mensagem a responder |
ExternalId |
string |
não | Identificador externo; devolvido no webhook |
Delay |
int? |
não | Delay em ms antes do envio (máx 20000) |
Async |
bool? |
não | Envio em background |
ptt(nota de voz): o servidor converte o áudio (qualquer formato) para OGG/Opus via ffmpeg, calcula duração e waveform, e envia como nota de voz.Caption/MimeTypesão ignorados.
SendReactionRequest
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Number |
string |
sim | Número do chat (ou JID/id de grupo) |
Id |
string |
sim | ID da mensagem alvo |
Emoji |
string |
não | Emoji da reação (vazio remove) |
FromMe |
bool? |
não | true se a mensagem alvo foi enviada por mim |
UpdateMessageRequest / DeleteMessageRequest
UpdateMessageRequest: Number, Id, Message (todos obrigatórios).
DeleteMessageRequest: Number, Id (todos obrigatórios).
SendPresenceRequest
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Number |
string |
sim | Número do chat (ou JID/id de grupo) |
Presence |
string |
sim | composing | recording | paused (ver Presence) |
Duration |
int? |
não | ms para manter o indicador (máx 20000); só composing/recording |
ResyncMessagesRequest
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Number |
string |
sim | Número do chat (ou JID/id de grupo) |
Count |
int? |
não | Quantidade de mensagens a solicitar (padrão 50) |
CreateInstanceRequest
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Name |
string |
sim | Nome da instância |
Token |
string |
não | Token das mensagens; gerado se vazio |
ExternalId |
string |
não | Identificador externo; ecoado no envelope de webhook |
WebhookSelf |
bool? |
não | Padrão true; false não dispara webhook de fromMe |
Webhooks |
List<WebhookRequest> |
não | Webhooks a configurar |
UpdateInstanceRequest / UpdateWebhooksRequest
UpdateInstanceRequest (admin): Name (sim), ExternalId (não), WebhookSelf (não) + Webhooks (substitui a lista anterior; vazia remove todos).
UpdateWebhooksRequest (self-service): Webhooks (nome preservado; vazia remove todos).
WebhookRequest
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
Name |
string |
— | Nome do webhook |
Url |
string |
— | URL de destino |
Events |
List<string> |
não | Eventos (vazio/ausente = todos; ver WebhookEvent) |
Schemas de Resposta
InstanceResponse
Campos vazios são omitidos pela API.
| Campo | Tipo | Descrição |
|---|---|---|
Id |
string |
UUID da instância |
Name |
string |
Nome salvo da instância |
ExternalId |
string |
Identificador externo, ecoado no webhook |
WebhookSelf |
bool |
false não dispara webhook das mensagens fromMe |
Status |
string |
connected | connecting | disconnected |
Number |
string |
Número do WhatsApp |
ProfileName |
string |
Nome do perfil no WhatsApp |
ProfilePicture |
string |
URL da foto de perfil |
Jid |
string |
Identificador WhatsApp completo |
Token |
string |
Token de autenticação das mensagens |
Webhooks |
List<WebhookResponse> |
Webhooks configurados |
WebhookResponse
Id, Name, Url, Events (List<string>; omitido = todos).
StatusResponse
| Campo | Tipo | Descrição |
|---|---|---|
Status |
string |
connected | connecting | disconnected |
QrCode |
string |
Data URL (PNG) do QR Code, ao aguardar pareamento |
Number |
string |
Número do WhatsApp |
Name |
string |
Nome do perfil |
Picture |
string |
URL da foto de perfil |
Connected |
bool |
Socket WebSocket aberto com o WhatsApp |
LoggedIn |
bool |
Sessão autenticada e pareada (pronta para enviar) |
Connected sem LoggedIn = socket aberto aguardando QR/pareamento.
QrCodeResponse
Status (ex: connecting), QrCode (data URL PNG).
ConnectResponse
Success (bool), Status (ex: connecting). Retornado por Instances.Connect / Instance.Connect.
SendResponse
| Campo | Tipo | Descrição |
|---|---|---|
Success |
bool |
true quando a requisição é aceita |
Status |
string |
sent (síncrono) ou accepted (assíncrono) |
Id |
string |
ID da mensagem (use para reply/reação/edição/exclusão) |
Timestamp |
DateTimeOffset? |
Data/hora do envio (ISO 8601) |
PresenceResponse
Success (bool), Status (ex: sent). Retornado por Messages.SendPresence.
ResyncResponse
Success (bool), Status (ex: sent), Id (solicitação enviada ao celular), Timestamp (long?, Unix em segundos). Retornado por Messages.Resync.
CheckNumberResponse
Number, Exists (bool), Jid, VerifiedName.
ContactResponse
Jid, Number, FullName (agenda), PushName (autodeclarado; vazio se o contato nunca enviou mensagem).
ContactDetailsResponse
Jid, Number, Exists (bool), FullName, PushName, VerifiedName, Status (recado), ProfilePicture.
GroupResponse
Jid, Name, Size (int), Announce (bool; só admins enviam), Locked (bool; só admins editam).
GroupDetailsResponse
| Campo | Tipo | Descrição |
|---|---|---|
Jid |
string |
JID do grupo (...@g.us) |
Name |
string |
Nome do grupo |
Topic |
string |
Descrição do grupo |
Owner |
string |
Número do criador |
OwnerJid |
string |
JID completo do criador |
Image |
string |
URL da foto (preview); expira, baixe ao receber |
Created |
long? |
Data de criação (Unix, segundos) |
Announce |
bool |
true = só admins enviam mensagens |
Locked |
bool |
true = só admins editam os dados |
Size |
int |
Total de participantes |
Participants |
List<GroupParticipant> |
Participantes |
GroupParticipant: Jid, Number, Lid, IsAdmin (bool), IsSuperAdmin (bool).
ChatResponse
| Campo | Tipo | Descrição |
|---|---|---|
ChatJid |
string |
JID do chat (@s.whatsapp.net ou @g.us) |
Number |
string |
Número do chat |
IsGroup |
bool |
Se é grupo |
Name |
string |
Nome do grupo ou do contato (pushName da última msg recebida) |
Picture |
string |
URL da foto do chat (S3, durável; vazia se sem foto) |
Timestamp |
long |
Timestamp da última mensagem (Unix, segundos) |
LastMessage |
MessageResponse |
Última mensagem do chat |
MessageResponse
Idêntico ao objeto data do webhook messages.upsert.
| Campo | Tipo | Descrição |
|---|---|---|
Id |
string |
ID da mensagem |
Type |
string |
Ver MessageType |
Timestamp |
long |
Unix (segundos) |
FromMe |
bool |
Enviada pela própria instância |
IsGroup |
bool |
Mensagem de grupo (só @g.us) |
IsChannel |
bool |
Mensagem de canal (newsletter) |
From |
string |
Telefone do chat (pode vir vazio se só houver LID) |
ChatJid |
string |
Endereço completo do chat |
FromLid |
string |
LID do chat, quando houver |
GroupName |
string |
Nome do grupo (só quando IsGroup) |
GroupImage |
string |
URL da foto do grupo; estável com S3, senão expira |
ChannelName |
string |
Nome do canal (só quando IsChannel) |
ChannelImage |
string |
URL da foto do canal; estável com S3, senão expira |
Sender |
string |
Telefone do remetente |
SenderJid |
string |
Endereço completo do remetente |
SenderLid |
string |
LID do remetente (identificador estável) |
SenderName |
string |
Nome autodeclarado do remetente (pode ser vazio) |
SenderImage |
string |
URL da foto do remetente; estável com S3, senão expira |
ExternalId |
string |
Eco do externalId do envio (só mensagens enviadas) |
Text |
string |
Texto ou legenda; em contact, a contagem ("N contatos") |
Media |
MessageMedia |
Conteúdo de mídia (image/video/audio/document/sticker) |
Location |
MessageLocation |
Conteúdo de localização |
Contacts |
List<MessageContact> |
Contatos recebidos (sempre lista, 1 ou vários) |
Reaction |
MessageReaction |
Conteúdo de reação |
Button |
MessageButton |
Mensagem com botões enviada (button) |
List |
MessageList |
Mensagem com lista enviada (list) |
ButtonReply |
MessageButtonReply |
Botão clicado pelo destinatário (buttonReply) |
ListReply |
MessageListReply |
Linha de lista selecionada (listReply) |
Call |
MessageCall |
Chamada recebida (call) |
Quoted |
MessageQuoted |
Mensagem citada (resposta) |
MessageMedia: MimeType, Caption, FileName, Size (long?), Seconds (int?), Ptt (bool?), Url (link auto-autenticado, validade 24h ou permanente via S3).
MessageLocation: Latitude, Longitude (double), Name, Address.
MessageContact: DisplayName, Vcard.
MessageReaction: Text (emoji), MessageId.
MessageButton: Text, Footer, Header (MessageButtonHeader), Options (List<MessageButtonOption>).
MessageButtonHeader: Type (image/video/document), MimeType, Size (long?), Url (baixável com DownloadAsync/OpenReadAsync/SaveToFileAsync), FileName (só document).
MessageButtonOption: Id, Text, Type (ButtonType), Code, Url, Phone.
MessageList: Text, Title, Footer, ButtonText, Options (List<MessageListOption>).
MessageListOption: Id, Text, Description, Section.
MessageButtonReply: Id, Text, Index (int?, template), Name/Params (native_flow).
MessageListReply: Id (RowID), Text.
MessageCall: Status (CallStatus), Media (CallMediaType), Outcome (CallOutcome), Reason, Platform.
MessageQuoted: Id, Participant, Text.
Modelos dos webhooks
Detalhes de cada evento em WEBHOOK.md.
| Modelo | Evento | Campos |
|---|---|---|
WebhookEnvelope |
todos | Event, Instance, InstanceId, ExternalId (da instância), Data (JsonElement) + getters tipados |
MessageResponse |
messages.upsert |
Ver acima |
MessageUpdateData |
messages.update |
Action, Ids, Id, Text, ExternalId, Timestamp, FromMe, IsGroup, endereços (From/Chat/Sender) |
PresenceUpdateData |
presence.update |
Status, From, FromJid, FromLid, IsGroup, ChatJid, LastSeen (long?) |
ConnectionUpdateData |
connection.update |
Status, Number, Reason, Message, Expire (long?), Timestamp |
HistorySyncData |
history.sync |
Status, SyncType, Chats, Messages, Ms (long?), Timestamp |
MessageResponse |
call.update |
Mensagem com Type == MessageType.CALL e o objeto Call |
Constantes
| Classe | Constantes |
|---|---|
InstanceStatus |
DISCONNECTED, CONNECTING, CONNECTED |
MediaType |
IMAGE, VIDEO, AUDIO, PTT, DOCUMENT |
ButtonType |
REPLY, COPY, URL, CALL |
Presence |
COMPOSING, RECORDING, PAUSED |
WebhookEvent |
MESSAGES_UPSERT, MESSAGES_UPDATE, PRESENCE_UPDATE, CONNECTION_UPDATE, HISTORY_SYNC, CALL_UPDATE |
MessageType |
TEXT, IMAGE, VIDEO, AUDIO, DOCUMENT, STICKER, LOCATION, CONTACT, REACTION, BUTTON, LIST, BUTTON_REPLY, LIST_REPLY, CALL, UNKNOWN |
MessageAction |
DELIVERED, READ, PLAYED, EDITED, DELETED (campo action de messages.update) |
PresenceStatus |
AVAILABLE, UNAVAILABLE, TYPING, RECORDING, PAUSED (campo status de presence.update) |
ConnectionStatus |
CONNECTING, CONNECTED, DISCONNECTED, LOGOUT, CONNECT_FAILURE, STREAM_REPLACED, BANNED, CLIENT_OUTDATED, PAIR_ERROR (campo status de connection.update) |
HistorySyncStatus |
STARTED, FINISHED (campo status de history.sync) |
CallStatus |
OFFER, TERMINATE (campo status do objeto call) |
CallOutcome |
ANSWERED, MISSED, REJECTED, ENDED (campo outcome do objeto call) |
CallMediaType |
AUDIO, VIDEO (campo media do objeto call) |
SoftZapErrorCode |
Códigos estáveis de erro (ex.Code); ver tabela de erros |
Limites e observações
- Paginação — contatos:
limitpadrão 100, máx 500. Chats e mensagens de chat:limitpadrão 50, máx 200. - Mídia por URL — até 100 MB; baixada pelo servidor.
- Delay / Duration — máx 20000 ms.
- Botões — até 3
replyou até 2 de ação (copy/url/call); não misture. Rótulo com máx 20 caracteres. Botões de ação renderizam só no app do celular. - Listas — não são oficialmente suportadas fora da Cloud API; a renderização depende do aparelho. A escolha volta como
listReply. pushName— preenchido só quando o contato envia mensagem; para nome prefiraFullNameouVerifiedName.- Mídia recebida —
media.urlé auto-autenticado (token assinado), validade 24h (depois410), exige instância conectada; com S3 habilitado vira URL pública permanente. - Fotos de grupo/canal/remetente —
GroupDetailsResponse.Image,MessageResponse.GroupImage/ChannelImage/SenderImage: com S3 apontam para o bucket (estáveis); sem S3 são links temporários do WhatsApp que expiram, baixe ao receber. - Chamadas — só chamadas recebidas são captadas (
call.update), comoffereterminate;answerednão traz duração. Chamadas feitas pelo celular não chegam. - Status (stories) — não geram webhook. Canais (newsletter) vêm com
IsChannel = trueeIsGroup = false. - Chats — leem histórico persistido; não exigem instância conectada. Mensagens/contatos/grupos exigem (
409caso contrário). - Enviar para grupo — o campo
Numberaceita o JID (...@g.us) ou o id do grupo em qualquer envio. - Frameworks —
net8.0,net9.0,net10.0.
Referência da API
| Serviço | Método | HTTP | Endpoint | Auth |
|---|---|---|---|---|
| Instances | List |
GET | /instances |
apikey |
Create |
POST | /instances |
apikey |
|
Get |
GET | /instances/{id} |
apikey |
|
Update |
PUT | /instances/{id} |
apikey |
|
Delete |
DELETE | /instances/{id} |
apikey |
|
Connect |
POST | /instances/{id}/connect |
apikey |
|
QrCode |
POST | /instances/{id}/qrcode |
apikey |
|
Status |
GET | /instances/{id}/status |
apikey |
|
Restart |
POST | /instances/{id}/restart |
apikey |
|
Disconnect |
POST | /instances/{id}/disconnect |
apikey |
|
| Messages | SendText |
POST | /messages/text |
token |
SendButtons |
POST | /messages/buttons |
token |
|
SendList |
POST | /messages/list |
token |
|
SendMedia |
POST | /messages/media |
token |
|
SendMediaFile |
POST | /messages/media |
token |
|
SendReaction |
POST | /messages/reaction |
token |
|
UpdateMessage |
POST | /messages/update |
token |
|
DeleteMessage |
POST | /messages/delete |
token |
|
SendPresence |
POST | /messages/presence |
token |
|
Resync |
POST | /messages/resync |
token |
|
| Contacts | Check |
POST | /contacts/check |
token |
List |
GET | /contacts |
token |
|
Get |
GET | /contacts/{number} |
token |
|
| Groups | List |
GET | /groups |
token |
Get |
GET | /groups/{jid} |
token |
|
| Chats | List |
GET | /chats |
token |
GetMessages |
GET | /chats/{chatJid}/messages |
token |
|
| Instance | Get |
GET | /instance |
token |
Status |
GET | /instance/status |
token |
|
Connect |
POST | /instance/connect |
token |
|
QrCode |
POST | /instance/qrcode |
token |
|
Restart |
POST | /instance/restart |
token |
|
Disconnect |
POST | /instance/disconnect |
token |
|
UpdateWebhooks |
PUT | /instance |
token |
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Configuration.Binder (>= 10.0.10)
- Microsoft.Extensions.Http (>= 10.0.10)
-
net8.0
- Microsoft.Extensions.Configuration.Binder (>= 10.0.10)
- Microsoft.Extensions.Http (>= 10.0.10)
-
net9.0
- Microsoft.Extensions.Configuration.Binder (>= 10.0.10)
- Microsoft.Extensions.Http (>= 10.0.10)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.0.1-preview.5 | 64 | 7/31/2026 |
| 0.0.1-preview.4 | 66 | 6/16/2026 |
| 0.0.1-preview.3 | 59 | 6/9/2026 |
| 0.0.1-preview.2 | 62 | 6/9/2026 |
| 0.0.1-preview.1 | 61 | 6/9/2026 |