---
title: "API do WhatsApp em C# (.NET): envio e webhook"
description: "Tutorial de API do WhatsApp em C# e .NET: HttpClient tipado, envio de texto, imagem e lista, webhook em ASP.NET Core com fila em background e tratamento de 429."
url: "https://api-wa.me/blog/api-whatsapp-csharp-dotnet"
language: "pt-BR"
og:type: "article"
og:site_name: "WAME API"
---

[Início](https://api-wa.me/)/[Blog da API do WhatsApp](https://api-wa.me/blog)/API do WhatsApp em C# (.NET): enviar mensagens e receber webhook

Raphael Serafim· Publicado em 28 de setembro de 2026· 10 min de leitura

Compartilhar

# API do WhatsApp em C# (.NET): enviar mensagens e receber webhook

Tutorial de API do WhatsApp em C# e .NET: HttpClient tipado, envio de texto, imagem e lista, webhook em ASP.NET Core com fila em background e tratamento de 429.

Copiar para LLM[Ver como Markdown](https://api-wa.me/blog/api-whatsapp-csharp-dotnet.md)

**Para usar a API do WhatsApp em C# você não precisa de SDK: um `HttpClient` tipado envia mensagens para `https://us.api-wa.me/SUA_KEY/...` com JSON, e uma minimal API do ASP.NET Core recebe o webhook no envelope da Cloud API.** Neste tutorial você monta as duas pontas com .NET moderno — records, `System.Text.Json`, `IHttpClientFactory`, fila com `Channel<T>` e `BackgroundService` — usando a API não oficial da WAME, que conecta seu número por QR Code ou código de pareamento, sem aprovação na Meta.

> A camada não oficial não é afiliada, endossada ou suportada pelo WhatsApp ou pela Meta. O uso é de responsabilidade de quem envia.

## O que você precisa

- .NET 8 ou mais novo.
- Uma instância na WAME conectada ao seu número (veja [como conectar sem QR Code](https://api-wa.me/blog/conectar-whatsapp-codigo-pareamento-sem-qr-code) se preferir código de pareamento).
- Uma URL pública para o webhook. Em desenvolvimento, um túnel como ngrok resolve.

A autenticação é a própria `key` na URL. Guarde-a em configuração, nunca no código:

json

Copiar

```
{
  "Wame": {
    "BaseUrl": "https://us.api-wa.me/",
    "Key": "SUA_KEY"
  }
}
```

## Um cliente tipado com HttpClient

O erro clássico em .NET é criar `new HttpClient()` a cada envio e esgotar sockets. Com `IHttpClientFactory` e um cliente tipado, o ciclo de vida fica por conta do framework:

csharp

Copiar

```
using System.Net;
using System.Net.Http.Json;

public sealed class WameOptions
{
    public string BaseUrl { get; init; } = "https://us.api-wa.me/";
    public string Key { get; init; } = "";
}

public sealed class WameClient(HttpClient http)
{
    public Task SendTextAsync(string to, string text, CancellationToken ct = default) =>
        PostAsync("message/text", new { to, text }, ct);

    public Task SendImageAsync(string to, string url, string? caption = null, CancellationToken ct = default) =>
        PostAsync("message/image", new { to, url, caption }, ct);

    public Task SendListAsync(string to, string text, string buttonText,
        IEnumerable<ListSection> sections, CancellationToken ct = default) =>
        PostAsync("message/list", new { to, text, buttonText, sections }, ct);

    public Task SetTypingAsync(string to, CancellationToken ct = default) =>
        PostAsync("message/presence", new { to, status = "composing" }, ct);

    private async Task PostAsync(string path, object body, CancellationToken ct)
    {
        using var resp = await http.PostAsJsonAsync(path, body, ct);

        if (resp.StatusCode == HttpStatusCode.TooManyRequests)
            throw new WameRateLimitException(await resp.Content.ReadAsStringAsync(ct));

        resp.EnsureSuccessStatusCode();
    }
}

public sealed record ListRow(string Title, string RowId, string? Description = null);
public sealed record ListSection(string Title, IReadOnlyList<ListRow> Rows);

public sealed class WameRateLimitException(string message) : Exception(message);
```

Dois detalhes que evitam dor de cabeça:

- **`BaseAddress` precisa terminar com a key e uma barra.** Sem a barra final, o `HttpClient` descarta o último segmento ao combinar com `message/text`.
- **O `System.Text.Json` com `PostAsJsonAsync` usa camelCase por padrão**, então `RowId` vira `rowId` e `ButtonText` vira `buttonText` — exatamente o que a API espera.

O registro no `Program.cs`:

csharp

Copiar

```
var builder = WebApplication.CreateBuilder(args);

var wame = builder.Configuration.GetSection("Wame").Get<WameOptions>()!;

builder.Services.AddHttpClient<WameClient>(c =>
{
    c.BaseAddress = new Uri($"{wame.BaseUrl}{wame.Key}/");
    c.Timeout = TimeSpan.FromSeconds(30);
});
```

## Enviar texto, imagem e lista

Com o cliente injetado, o envio fica em uma linha. O número vai sempre no formato internacional, só dígitos: código do país, DDD e número.

csharp

Copiar

```
await wame.SendTextAsync("5511999999999", "Olá! Seu pedido foi confirmado.");

await wame.SendImageAsync("5511999999999",
    "https://exemplo.com/comprovante.png", "Seu comprovante");

await wame.SendListAsync("5511999999999",
    text: "Como posso ajudar?",
    buttonText: "Ver opções",
    sections:
    [
        new ListSection("Atendimento",
        [
            new ListRow("Segunda via de boleto", "boleto"),
            new ListRow("Falar com atendente", "humano", "Horário comercial")
        ])
    ]);
```

Uma observação sobre listas e botões: em conversa **fria** — contato que nunca trocou mensagem com o seu número — o WhatsApp do destinatário pode não exibir a mensagem interativa. Abra com um texto e mande o menu em seguida. O motivo está em [como aquecer um número](https://api-wa.me/blog/aquecer-numero-whatsapp-api-nao-oficial).

Todos os envios da camada não oficial já saem com comportamento humano (online, "digitando…" proporcional ao texto, envio). O `SetTypingAsync` acima é útil quando o seu processamento demora — uma consulta ao ERP, uma chamada de IA — e você quer sinalizar ao cliente que a resposta está vindo.

## Receber o webhook no formato meta

Configure o webhook da instância apontando para a sua aplicação, no formato `meta`:

bash

Copiar

```
curl -X PUT "https://us.api-wa.me/SUA_KEY/instance" \
  -H "Content-Type: application/json" \
  -d '{
    "allowWebhook": true,
    "allowNumber": "all",
    "webhookMessage": "https://seu-dominio.com/webhook/wame",
    "webhookConnection": "https://seu-dominio.com/webhook/wame",
    "webhookFormat": "meta"
  }'
```

Cada mensagem chega no envelope padrão da Cloud API. Records modelam só o que interessa — o resto é ignorado na desserialização:

csharp

Copiar

```
using System.Text.Json.Serialization;

public sealed record WebhookEnvelope(string? Provider, List<Entry>? Entry);
public sealed record Entry(string? Id, List<Change>? Changes);
public sealed record Change(string? Field, ChangeValue? Value);
public sealed record ChangeValue(List<IncomingMessage>? Messages);

public sealed record IncomingMessage(
    string From,
    string Id,
    string Type,
    TextBody? Text,
    Interactive? Interactive,
    [property: JsonPropertyName("group_id")] string? GroupId,
    [property: JsonPropertyName("from_me")] bool FromMe = false);

public sealed record TextBody(string Body);

public sealed record Interactive(
    string Type,
    [property: JsonPropertyName("list_reply")] ReplyId? ListReply,
    [property: JsonPropertyName("button_reply")] ReplyId? ButtonReply);

public sealed record ReplyId(string Id, string? Title);
```

Repare em três campos:

- **`group_id`** só aparece quando a mensagem veio de um grupo; aí `from` é quem enviou dentro dele.
- **`from_me`** marca o eco das mensagens enviadas pelo próprio número. Ignore, ou o bot responde a si mesmo.
- **`interactive.list_reply.id`** traz o `rowId` que você definiu na lista.

## Responder 200 na hora e processar em background

O endpoint do webhook não pode esperar o processamento. Webhook lento é reenviado, e o cliente recebe a mesma resposta duas vezes. A solução idiomática em .NET é um `Channel<T>` como fila em memória e um `BackgroundService` consumindo:

csharp

Copiar

```
using System.Threading.Channels;

builder.Services.AddSingleton(Channel.CreateBounded<IncomingMessage>(
    new BoundedChannelOptions(1_000) { FullMode = BoundedChannelFullMode.Wait }));
builder.Services.AddHostedService<MessageWorker>();

var app = builder.Build();

app.MapPost("/webhook/wame", async (WebhookEnvelope env, Channel<IncomingMessage> queue) =>
{
    var messages = env.Entry?
        .SelectMany(e => e.Changes ?? [])
        .Where(c => c.Field == "messages")
        .SelectMany(c => c.Value?.Messages ?? [])
        .Where(m => !m.FromMe) ?? [];

    foreach (var m in messages)
        await queue.Writer.WriteAsync(m);

    return Results.Ok();
});

app.Run();
```

E o worker, que decide o que responder:

csharp

Copiar

```
public sealed class MessageWorker(
    Channel<IncomingMessage> queue,
    WameClient wame,
    ILogger<MessageWorker> log) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        await foreach (var msg in queue.Reader.ReadAllAsync(ct))
        {
            try
            {
                await HandleAsync(msg, ct);
            }
            catch (WameRateLimitException ex)
            {
                log.LogWarning("429 ao responder {From}: {Msg}", msg.From, ex.Message);
                await Task.Delay(TimeSpan.FromSeconds(30), ct);
            }
            catch (Exception ex)
            {
                log.LogError(ex, "Falha ao processar {Id}", msg.Id);
            }
        }
    }

    private Task HandleAsync(IncomingMessage msg, CancellationToken ct)
    {
        if (msg.GroupId is not null) return Task.CompletedTask; // este bot só atende 1:1

        var escolha = msg.Interactive?.ListReply?.Id;
        return escolha switch
        {
            "boleto" => wame.SendTextAsync(msg.From, "Vou gerar sua segunda via.", ct),
            "humano" => wame.SendTextAsync(msg.From, "Já chamo alguém do time.", ct),
            _ when msg.Type == "text" => wame.SendTextAsync(msg.From,
                $"Recebi: {msg.Text?.Body}", ct),
            _ => Task.CompletedTask
        };
    }
}
```

O `WameClient` é registrado como transient pelo `AddHttpClient`, e o `BackgroundService` é singleton. Funciona porque o cliente tipado só segura um `HttpClient` vindo da factory; se o seu worker precisar de serviços scoped (um `DbContext`, por exemplo), injete `IServiceScopeFactory` e crie um escopo por mensagem.

## Idempotência: o mesmo evento pode chegar duas vezes

Mesmo respondendo rápido, reentrega acontece — rede instável, deploy no meio do caminho. Guarde o `id` de cada mensagem processada e descarte repetições. Em produção, um `SETNX` no Redis com expiração de algumas horas resolve; em teste, um `ConcurrentDictionary` serve. O padrão completo, com assinatura e retry, está em [webhook em produção](https://api-wa.me/blog/webhook-producao-assinatura-retry-idempotencia).

## Tratando o 429 do jeito certo

O `429` da WAME não é instabilidade: é um freio deliberado. Ele aparece quando a instância fala com **números novos demais em pouco tempo** ou manda **o mesmo texto para números demais** em poucos minutos — o desenho clássico de disparo em massa. Por isso, a política certa não é retry imediato com Polly em loop:

- **Espere e espalhe.** Reduza o ritmo da fila; não reenvie a mesma mensagem na hora.
- **Personalize.** Texto idêntico para muita gente é exatamente o que o freio procura.
- **Revise o fluxo.** Se um bot de atendimento está batendo em 429, algo está errado — um loop, um eco de `from_me` não filtrado, uma campanha disfarçada.

A WAME não apoia spam. Esses freios existem para que atendimento, notificação e bots legítimos rodem com taxa de bloqueio muito baixa para quem usa do jeito certo. As regras estão em [uso responsável da API não oficial](https://api-wa.me/blog/uso-responsavel-api-whatsapp-nao-oficial-spam).

## Saúde do número no mesmo webhook

Como o webhook de conexão aponta para o mesmo endpoint, você também recebe o evento de saúde da instância (`field: "health"`), com o estado atual, o anterior e um indicador `should_pause`. Vale acrescentar um `Change` com esse campo ao modelo e, quando `should_pause` vier verdadeiro, parar de escrever na fila de envios ativos. Os sinais e o que fazer com cada um estão em [sinais de que o número está em risco](https://api-wa.me/blog/sinais-numero-whatsapp-risco-bloqueio).

## Um código para oficial e não oficial

Se você imagina migrar para a API oficial um dia — ou rodar as duas —, existe um atalho: o endpoint `POST /{key}/message` aceita exatamente o corpo da WhatsApp Cloud API. Combinado com o webhook `meta`, a mesma camada C# atende os dois tipos de instância. O desenho está em [um código só para a API oficial e a não oficial](https://api-wa.me/blog/mesmo-codigo-api-oficial-e-nao-oficial-whatsapp).

## Conclusão

Em .NET, integrar WhatsApp é HTTP e JSON: um `HttpClient` tipado registrado com `IHttpClientFactory` para enviar, records do `System.Text.Json` para ler o envelope da Cloud API, uma minimal API que responde 200 na hora e um `BackgroundService` com `Channel<T>` para processar. Somando idempotência pelo `id` da mensagem e respeito ao `429`, você tem uma integração de produção sem SDK e sem self-host. Se preferir outra linguagem, há o [tutorial em Python](https://api-wa.me/blog/api-whatsapp-python-nao-oficial) e o [SDK JavaScript](https://api-wa.me/blog/sdk-javascript-typescript-whatsapp); a referência completa dos endpoints está na [documentação](https://api-wa.me/docs).

### Pronto para automatizar seu WhatsApp?

Crie sua conta gratuita e comece a enviar mensagens pela API em minutos.

[Começar grátis](https://portal.api-wa.me/sign-up)

## Perguntas frequentes

Existe SDK oficial da WAME para C#?+

Não é preciso. A API é REST pura: a instância é identificada por uma key na própria URL (https://us.api-wa.me/SUA\_KEY/...) e o corpo é JSON. Com HttpClient e System.Text.Json você cobre texto, mídia, listas, grupos e o webhook sem dependência extra.

Como recebo mensagens do WhatsApp numa aplicação ASP.NET Core?+

Configure o webhook da instância com PUT /{key}/instance apontando para um endpoint público da sua aplicação e escolha webhookFormat meta. Cada mensagem chega no envelope da Cloud API (entry, changes, value.messages). Responda 200 na hora e processe numa fila em background.

Por que responder 200 antes de processar o webhook?+

Webhook que demora a responder é reenviado. Se você chama uma IA, um banco ou outra API antes de responder, o mesmo evento pode chegar duas ou três vezes e o cliente recebe respostas duplicadas. Enfileire com Channel<T> e processe num BackgroundService.

O que significa o erro 429 da API?+

É um freio de envio: a instância falou com números novos demais em pouco tempo ou mandou o mesmo texto para números demais em poucos minutos. Não repita na hora; espere, espalhe os envios na fila e revise se o fluxo não está parecendo disparo em massa.

Posso usar o mesmo código C# com a API oficial?+

Sim, se você usar o endpoint POST /{key}/message, que aceita exatamente o corpo da WhatsApp Cloud API, e o webhook no formato meta. Assim a mesma camada de envio serve instâncias oficiais e não oficiais, mudando só a key.

## Continue lendo

[### Agente de voz no WhatsApp: latência, interrupção (barge-in) e silêncio

Como deixar um agente de voz no WhatsApp natural: latência, streaming, detecção de fala, interrupção (barge-in), silêncio e eco, com exemplos em Node.js.](https://api-wa.me/blog/agente-voz-whatsapp-latencia-interrupcao)[### Anti-detecção na API não oficial do WhatsApp: como a WAME protege seu número

Como funciona a camada de anti-detecção da API não oficial da WAME: identidade de dispositivo, tempo humano, ritmo de envio, reconexão e monitor de saúde.](https://api-wa.me/blog/anti-deteccao-api-whatsapp-nao-oficial)[### API do WhatsApp para delivery e restaurante: pedido, entrega e avaliação no mesmo chat

Como usar a API não oficial do WhatsApp no delivery: cardápio em lista, confirmação, endereço por localização, entregador em tempo real, Pix e avaliação.](https://api-wa.me/blog/api-whatsapp-delivery-restaurante)

[Voltar ao blog](https://api-wa.me/blog)

## Structured data

```json
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "WAME API",
  "alternateName": "API Oficial e Não Oficial de WhatsApp, Instagram, Messenger e Telegram",
  "url": "https://api-wa.me",
  "inLanguage": "pt-BR",
  "publisher": {
    "@id": "https://api-wa.me/#organization",
    "@type": "Organization",
    "name": "WAME API",
    "url": "https://api-wa.me"
  }
}
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "@id": "https://api-wa.me/#organization",
  "name": "WAME API",
  "alternateName": [
    "WAME",
    "Wame API",
    "wame.api.br",
    "api-wa.me"
  ],
  "url": "https://api-wa.me",
  "logo": {
    "@type": "ImageObject",
    "url": "https://api-wa.me/images/web-app-manifest-512x512.png",
    "width": 512,
    "height": 512
  },
  "disambiguatingDescription": "WAME API é uma empresa brasileira de software, fundada em 2017 e parceira oficial da Meta (Meta Business Partner), que fornece APIs de WhatsApp, Instagram Direct e Messenger. Não tem relação com o wa.me, que é o encurtador de links operado pela WhatsApp LLC.",
  "identifier": {
    "@type": "PropertyValue",
    "propertyID": "INPI-BR",
    "name": "Pedido de registro de marca (INPI, classe NCL 42)",
    "value": "944724159"
  },
  "foundingDate": "2017",
  "slogan": "Parceira Oficial da Meta — WhatsApp, Instagram, Messenger e Telegram numa instância só. Desde 2017.",
  "description": "Plataforma brasileira e Parceira Oficial da Meta (Meta Business Partner) para as APIs oficiais de WhatsApp (Cloud API), Instagram Direct e Messenger — os três numa única instância, com os mesmos endpoints e um único formato de webhook. Também oferece a API não oficial via QR Code e o Telegram, na mesma plataforma. No mercado desde 2017, com mais de 50 mil instâncias criadas, 99,9% de uptime e suporte humano 24/7 em português. SDKs oficiais para Node.js/TypeScript e PHP.",
  "knowsAbout": [
    "WhatsApp Cloud API oficial (Meta)",
    "API oficial de Instagram (Direct)",
    "API oficial de Messenger",
    "API multicanal Meta",
    "Meta Business Partner",
    "WhatsApp API",
    "API não oficial de WhatsApp",
    "automação de WhatsApp",
    "números virtuais",
    "webhooks"
  ],
  "sameAs": [
    "https://github.com/wame-api",
    "https://www.linkedin.com/company/wameapi",
    "https://www.instagram.com/wame.api/",
    "https://www.youtube.com/@wameapi"
  ],
  "contactPoint": {
    "@type": "ContactPoint",
    "contactType": "customer support",
    "url": "https://api-wa.me/contact",
    "availableLanguage": [
      "Portuguese"
    ]
  }
}
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "API do WhatsApp em C# (.NET): enviar mensagens e receber webhook",
  "description": "Tutorial de API do WhatsApp em C# e .NET: HttpClient tipado, envio de texto, imagem e lista, webhook em ASP.NET Core com fila em background e tratamento de 429.",
  "image": "https://api-wa.me/blog/api-whatsapp-csharp-dotnet/opengraph-image",
  "datePublished": "2026-09-28",
  "dateModified": "2026-09-28",
  "author": {
    "@type": "Person",
    "name": "Raphael Serafim",
    "url": "https://github.com/raphaelvserafim",
    "sameAs": [
      "https://github.com/raphaelvserafim"
    ]
  },
  "publisher": {
    "@type": "Organization",
    "name": "api-wa.me",
    "logo": {
      "@type": "ImageObject",
      "url": "https://api-wa.me/images/screenshot.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://api-wa.me/blog/api-whatsapp-csharp-dotnet"
  },
  "keywords": "api whatsapp não oficial, api não oficial whatsapp, api whatsapp c#, api whatsapp .net, whatsapp asp.net core webhook, enviar mensagem whatsapp c#, httpclient whatsapp api",
  "inLanguage": "pt-BR"
}
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://api-wa.me"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Blog da API do WhatsApp",
      "item": "https://api-wa.me/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "API do WhatsApp em C# (.NET): enviar mensagens e receber webhook",
      "item": "https://api-wa.me/blog/api-whatsapp-csharp-dotnet"
    }
  ]
}
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Existe SDK oficial da WAME para C#?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Não é preciso. A API é REST pura: a instância é identificada por uma key na própria URL (https://us.api-wa.me/SUA_KEY/...) e o corpo é JSON. Com HttpClient e System.Text.Json você cobre texto, mídia, listas, grupos e o webhook sem dependência extra."
      }
    },
    {
      "@type": "Question",
      "name": "Como recebo mensagens do WhatsApp numa aplicação ASP.NET Core?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Configure o webhook da instância com PUT /{key}/instance apontando para um endpoint público da sua aplicação e escolha webhookFormat meta. Cada mensagem chega no envelope da Cloud API (entry, changes, value.messages). Responda 200 na hora e processe numa fila em background."
      }
    },
    {
      "@type": "Question",
      "name": "Por que responder 200 antes de processar o webhook?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Webhook que demora a responder é reenviado. Se você chama uma IA, um banco ou outra API antes de responder, o mesmo evento pode chegar duas ou três vezes e o cliente recebe respostas duplicadas. Enfileire com Channel<T> e processe num BackgroundService."
      }
    },
    {
      "@type": "Question",
      "name": "O que significa o erro 429 da API?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "É um freio de envio: a instância falou com números novos demais em pouco tempo ou mandou o mesmo texto para números demais em poucos minutos. Não repita na hora; espere, espalhe os envios na fila e revise se o fluxo não está parecendo disparo em massa."
      }
    },
    {
      "@type": "Question",
      "name": "Posso usar o mesmo código C# com a API oficial?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Sim, se você usar o endpoint POST /{key}/message, que aceita exatamente o corpo da WhatsApp Cloud API, e o webhook no formato meta. Assim a mesma camada de envio serve instâncias oficiais e não oficiais, mudando só a key."
      }
    }
  ]
}
```
