---
title: "API de WhatsApp em C# e .NET com ASP.NET Core | D-API"
description: "Integre a API de WhatsApp em C# com IHttpClientFactory no ASP.NET Core, receba o webhook em uma minimal API e processe os eventos em um BackgroundService."
url: https://d-api.cloud/api-whatsapp-csharp
---

Por linguagem

# API de WhatsApp em C#: IHttpClientFactory, minimal API e BackgroundService

No ASP.NET Core, a API de WhatsApp da D-API vira um typed client registrado com IHttpClientFactory, um endpoint de minimal API para o webhook e um BackgroundService que processa os eventos fora do request. Tudo com o que já vem no .NET 8, sem pacote extra.

Por Time de engenharia D-APIAtualizado em 24 de set de 20266 min de leitura

## Como as peças se encaixam

Times .NET costumam integrar WhatsApp em sistemas de atendimento, ERPs e portais internos, quase sempre em ASP.NET Core. A [API de WhatsApp](https://d-api.cloud/) da D-API não tem biblioteca NuGet: é REST, com JSON e autenticação pelo header `Authorization`. O desenho recomendado usa três recursos nativos:

1.  **Typed client** registrado com `AddHttpClient`, que concentra URL, header e timeout.
2.  **Endpoint de minimal API** que recebe o webhook, valida e coloca o evento em um `Channel`.
3.  **BackgroundService** que consome o canal e faz o trabalho demorado, inclusive responder o cliente pelo typed client.

## O typed client com IHttpClientFactory

O typed client encapsula as chamadas e transforma a resposta de erro da API, no formato `{ "success": false, "error": "...", "statusCode": 400 }`, em uma exceção com o status. O timeout fica na configuração do client, e o `CancellationToken` permite cancelar a chamada quando o request original é abortado.

```
// DApiClient.cs
public sealed class DApiClient(HttpClient http)
{
    public Task SendTextAsync(string sessionId, string to, string text, CancellationToken ct = default) =>
        PostAsync("/api/v1/messages/send/text", new { sessionId, to, text }, ct);

    public Task SendDocumentAsync(string sessionId, string to, string documentUrl, string fileName,
        CancellationToken ct = default) =>
        PostAsync("/api/v1/messages/send/document",
            new { sessionId, to, document = documentUrl, fileName }, ct);

    private async Task PostAsync(string path, object payload, CancellationToken ct)
    {
        using var res = await http.PostAsJsonAsync(path, payload, ct);
        if (res.IsSuccessStatusCode) return;

        var corpo = await res.Content.ReadAsStringAsync(ct);
        var erro = TryParse(corpo);
        throw new DApiException((int)res.StatusCode, erro?.Error ?? corpo);
    }

    private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web);

    private static DApiErro? TryParse(string json)
    {
        try { return JsonSerializer.Deserialize<DApiErro>(json, Json); }
        catch (JsonException) { return null; }
    }
}

public sealed record DApiErro(bool Success, string? Error, int StatusCode);
public sealed class DApiException(int status, string message) : Exception(message)
{
    public int Status { get; } = status;
}
```

```
// Program.cs
builder.Services.AddHttpClient<DApiClient>(client =>
{
    client.BaseAddress = new Uri("https://api.d-api.cloud");
    client.Timeout = TimeSpan.FromSeconds(10);
    client.DefaultRequestHeaders.TryAddWithoutValidation(
        "Authorization", builder.Configuration["DApi:ApiKey"]);
});
```

Dois detalhes que evitam dor de cabeça. Primeiro, `TryAddWithoutValidation`: a chave vai pura, sem Bearer, e o método `Add` pode rejeitar esse formato. Segundo, quando o timeout estoura, o `HttpClient` lança `TaskCanceledException` com um `TimeoutException` interno. Trate esse caso separado de erro 4xx: no timeout, a mensagem pode ter saído; no 4xx, o pedido foi recusado.

Se o projeto já usa o pacote `Microsoft.Extensions.Http.Resilience`, é tentador encadear um `AddStandardResilienceHandler` no mesmo registro. Pense antes: a política padrão repete requisições em falhas transitórias, e repetir um POST de envio pode entregar a mesma mensagem duas vezes ao cliente. Para rotas de envio, prefira desligar a repetição automática de métodos não idempotentes e deixar a decisão para o BackgroundService, que sabe o contexto de cada mensagem. Para envio de PDFs, áudios e imagens, veja os campos de cada tipo em [como enviar mídia pela API](https://d-api.cloud/api-whatsapp-enviar-midia).

## O webhook em uma minimal API

O endpoint não processa nada. Ele confere o token secreto na rota, escreve o evento em um `Channel` com capacidade limitada e responde. Se o canal estiver cheio, devolve 503: a D-API entende como falha temporária e tenta de novo mais tarde, com intervalo crescente, o que funciona como controle de pressão sem código extra.

```
public sealed record WebhookEvento(string Event, string SessionId, string Timestamp,
    string? TraceId, JsonElement Data);

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

var app = builder.Build();

app.MapPost("/webhooks/whatsapp/{token}",
    (string token, WebhookEvento evento, Channel<WebhookEvento> fila, IConfiguration cfg) =>
    {
        var esperado = cfg["DApi:WebhookToken"] ?? "";
        if (!CryptographicOperations.FixedTimeEquals(
                Encoding.UTF8.GetBytes(token), Encoding.UTF8.GetBytes(esperado)))
            return Results.Unauthorized();

        return fila.Writer.TryWrite(evento)
            ? Results.Ok()
            : Results.StatusCode(StatusCodes.Status503ServiceUnavailable);
    });
```

O webhook de WhatsApp não carrega assinatura, por isso a comparação do token em tempo constante é a verificação de origem. O model binding da minimal API já desserializa o envelope; o campo `Data` fica como `JsonElement` porque muda conforme o evento. Os tipos de evento estão em [webhook de WhatsApp](https://d-api.cloud/webhook-api-whatsapp).

## O BackgroundService que consome a fila

O serviço hospedado é singleton, e o typed client não deve ficar preso nele durante toda a vida da aplicação. A forma correta é abrir um escopo por evento e resolver o `DApiClient` ali dentro.

```
public sealed class ProcessadorWhatsApp(
    Channel<WebhookEvento> fila,
    IServiceScopeFactory scopes,
    ILogger<ProcessadorWhatsApp> log) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        await foreach (var evento in fila.Reader.ReadAllAsync(stoppingToken))
        {
            if (evento.Event != "messages.received") continue;
            if (evento.Data.GetProperty("fromMe").GetBoolean()) continue;

            using var scope = scopes.CreateScope();
            var dapi = scope.ServiceProvider.GetRequiredService<DApiClient>();
            var jid = evento.Data.GetProperty("from").GetProperty("jid").GetString()!;

            try
            {
                await dapi.SendTextAsync(evento.SessionId, jid.Split('@')[0],
                    "Recebemos sua mensagem. Um atendente responde em instantes.", stoppingToken);
            }
            catch (Exception ex)
            {
                log.LogError(ex, "Falha ao responder evento {TraceId}", evento.TraceId);
            }
        }
    }
}
```

O `TraceId` no log estruturado é o que liga o erro do seu lado a uma entrega específica da D-API. Deduplicação também entra aqui: guarde o `id` de cada mensagem e ignore repetições, porque um evento pode ser reentregue.

## Testar o typed client sem rede

Como o `DApiClient` recebe o `HttpClient` pelo construtor, basta entregar a ele um handler falso no teste. Assim você valida o tratamento de erro sem depender da API nem de um número conectado, e o teste roda igual no CI e na máquina de cada desenvolvedor.

```
sealed class HandlerFalso(HttpStatusCode status, string corpo) : HttpMessageHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage req, CancellationToken ct) =>
        Task.FromResult(new HttpResponseMessage(status) { Content = new StringContent(corpo) });
}

[Fact]
public async Task Erro_da_api_vira_DApiException_com_status()
{
    var http = new HttpClient(new HandlerFalso(HttpStatusCode.BadRequest,
        """{ "success": false, "error": "Session not connected", "statusCode": 400 }"""))
        { BaseAddress = new Uri("https://api.d-api.cloud") };

    var ex = await Assert.ThrowsAsync<DApiException>(() =>
        new DApiClient(http).SendTextAsync("suporte", "5511999999999", "oi"));

    Assert.Equal(400, ex.Status);
    Assert.Equal("Session not connected", ex.Message);
}
```

O mesmo padrão cobre o caso de corpo que não é JSON, como uma página de erro de proxy, garantindo que o client não quebre com `JsonException` e ainda devolva a mensagem original para o log.

## Quando trocar o Channel por uma fila de verdade

O `Channel` vive na memória do processo. Com uma instância só e volume moderado, ele resolve bem. Com várias réplicas atrás de um balanceador, ou quando perder um evento no deploy não é aceitável, o caminho é uma fila persistente. A D-API pode publicar os eventos da sessão direto em um RabbitMQ seu, o que elimina o endpoint HTTP e deixa o consumo com o MassTransit ou o client oficial do RabbitMQ. As opções de entrega estão em [integrações](https://d-api.cloud/integrations). Se o seu produto é uma plataforma de atendimento, o guia de [API de WhatsApp para helpdesk](https://d-api.cloud/api-whatsapp-para-helpdesk) mostra como organizar filas, atendentes e conexões por cliente.

## Perguntas frequentes

### Existe pacote NuGet oficial da D-API?

Não. O SDK oficial é para Node.js. Em .NET a integração é REST com o HttpClient do próprio framework, que já traz serialização JSON, timeout e integração com injeção de dependência.

### Por que não criar um new HttpClient em cada envio?

Criar e descartar HttpClient a cada chamada pode esgotar as portas do servidor sob carga. O IHttpClientFactory reaproveita os handlers e renova as conexões periodicamente, o que também respeita mudanças de DNS.

### Funciona no .NET Framework 4.8?

O envio funciona, porque é só HTTP com JSON. Os exemplos desta página usam recursos do .NET 8, como minimal APIs e construtores primários; no .NET Framework seria preciso adaptar para controllers e classes tradicionais.

### Por que o header Authorization dá erro de formato?

O .NET valida o formato do header Authorization ao adicionar pelo método Add. Como a D-API espera a chave pura, sem Bearer, use TryAddWithoutValidation para o valor ser enviado exatamente como está.

### A fila em memória perde eventos?

Perde se o processo cair com itens pendentes. Para volume baixo isso costuma ser aceitável. Com várias instâncias ou eventos críticos, use uma fila externa; a D-API também entrega eventos direto no RabbitMQ.

Teste a API de WhatsApp da D-API3 dias grátis, sem cartão.[Começar trial grátis](https://app.d-api.cloud/register)

## Continue lendo

-   [Por linguagemAPI de WhatsApp em Go](https://d-api.cloud/api-whatsapp-golang)
-   [Por linguagemAPI de WhatsApp em Node.js](https://d-api.cloud/api-whatsapp-nodejs)
-   [Por linguagemAPI de WhatsApp em Python](https://d-api.cloud/api-whatsapp-python)
-   [Por linguagemAPI de WhatsApp em PHP](https://d-api.cloud/api-whatsapp-php)

## Teste a API de WhatsApp da D-API

Trial de 3 dias com acesso completo. Sem cartão, sem fidelidade.

[Começar trial grátis](https://app.d-api.cloud/register)[Ver documentação](https://docs.d-api.cloud/)
