SoftZap.Client 0.0.1-preview.5

This is a prerelease version of SoftZap.Client.
dotnet add package SoftZap.Client --version 0.0.1-preview.5
                    
NuGet\Install-Package SoftZap.Client -Version 0.0.1-preview.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SoftZap.Client" Version="0.0.1-preview.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SoftZap.Client" Version="0.0.1-preview.5" />
                    
Directory.Packages.props
<PackageReference Include="SoftZap.Client" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SoftZap.Client --version 0.0.1-preview.5
                    
#r "nuget: SoftZap.Client, 0.0.1-preview.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SoftZap.Client@0.0.1-preview.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SoftZap.Client&version=0.0.1-preview.5&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=SoftZap.Client&version=0.0.1-preview.5&prerelease
                    
Install as a Cake Tool

SoftZap.Client

SDK em C# para integração com a SoftZap — API para gerenciamento de instâncias do WhatsApp e envio de mensagens.

NuGet .NET


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 Number aceita 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 em client.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) ou VerifiedName (contas business); PushName só é 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. O Jid de cada participante é canônico: telefone @s.whatsapp.net quando conhecido, @lid só quando o contato não expõe o número (aí Number vem vazio — use Lid).


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.upsert também dispara para as mensagens enviadas pela própria instância (FromMe = true), inclusive as enviadas pelo celular. Ignore FromMe nas automações, ou crie a instância com WebhookSelf = 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, Async de MessageOptionsBaseRequest.

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, Async de MessageOptionsBaseRequest.

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, Async de MessageOptionsBaseRequest.

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/MimeType sã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: limit padrão 100, máx 500. Chats e mensagens de chat: limit padrã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 reply ou 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 prefira FullName ou VerifiedName.
  • Mídia recebida — media.url é auto-autenticado (token assinado), validade 24h (depois 410), 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), com offer e terminate; answered não traz duração. Chamadas feitas pelo celular não chegam.
  • Status (stories) — não geram webhook. Canais (newsletter) vêm com IsChannel = true e IsGroup = false.
  • Chats — leem histórico persistido; não exigem instância conectada. Mensagens/contatos/grupos exigem (409 caso contrário).
  • Enviar para grupo — o campo Number aceita 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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