API de WhatsApp em Java: HttpClient nativo e Spring Boot
Para integrar a API de WhatsApp em Java você não precisa de biblioteca extra: o HttpClient do Java 11 ou o RestClient do Spring Boot fazem o envio, e um controller com PostMapping recebe o webhook. Este guia mostra os dois caminhos com timeout configurado e tratamento do corpo de erro da API.
O contrato da API em termos de Java
Do ponto de vista do seu código, a API de WhatsApp da D-API é um serviço REST comum. Cada operação é uma requisição para https://api.d-api.cloud, com corpo JSON e o header Authorization contendo a API Key sem prefixo. Não há SDK Java, e não faz falta: basta modelar dois ou três records e escolher o cliente HTTP.
| Operação | Método e caminho | Campos obrigatórios |
|---|---|---|
| Enviar texto | POST /api/v1/messages/send/text | sessionId, to, text |
| Enviar imagem | POST /api/v1/messages/send/image | sessionId, to, image (caption opcional) |
| Consultar sessão | GET /api/v1/sessions/{sessionId} | sessionId no caminho |
| Resposta de erro | qualquer rota | success, error, statusCode |
Java puro com java.net.http.HttpClient
Em um serviço sem Spring, um batch ou uma aplicação desktop, o cliente nativo do JDK resolve. Crie uma única instância de HttpClient e reaproveite; ela mantém o pool de conexões. Repare nos dois timeouts: o do builder limita o tempo para abrir a conexão, e o do request limita a espera pela resposta.
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URI;
import java.net.http.*;
import java.time.Duration;
import java.util.Map;
public final class DApiClient {
private static final String BASE = "https://api.d-api.cloud";
private final HttpClient http = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(3))
.build();
private final ObjectMapper json = new ObjectMapper();
private final String apiKey;
public DApiClient(String apiKey) { this.apiKey = apiKey; }
public void sendText(String sessionId, String to, String text) throws Exception {
String body = json.writeValueAsString(Map.of("sessionId", sessionId, "to", to, "text", text));
HttpRequest req = HttpRequest.newBuilder(URI.create(BASE + "/api/v1/messages/send/text"))
.timeout(Duration.ofSeconds(10))
.header("Authorization", apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> res;
try {
res = http.send(req, HttpResponse.BodyHandlers.ofString());
} catch (HttpTimeoutException e) {
throw new DApiException(408, "sem resposta dentro do timeout");
}
if (res.statusCode() >= 400) {
ApiError err = json.readValue(res.body(), ApiError.class);
throw new DApiException(res.statusCode(), err.error());
}
}
public record ApiError(boolean success, String error, int statusCode) {}
public static final class DApiException extends RuntimeException {
public final int status;
public DApiException(int status, String message) { super(message); this.status = status; }
}
}O Jackson aparece só para serializar o JSON; se o projeto já usa Gson, a troca é direta. O HttpTimeoutException é lançado quando o tempo do request esgota, e convém tratá-lo à parte: nesse caso a mensagem pode ter sido entregue mesmo sem a resposta chegar.
Spring Boot com RestClient
No Spring Boot 3.2 ou superior, o RestClient é o jeito idiomático de chamar APIs de forma síncrona. Declare um bean configurado com URL base, header e uma request factory com timeouts, e injete onde precisar.
@Configuration
class DApiConfig {
@Bean
RestClient dapiRestClient(@Value("${dapi.api-key}") String apiKey) {
var factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(Duration.ofSeconds(3));
factory.setReadTimeout(Duration.ofSeconds(10));
return RestClient.builder()
.baseUrl("https://api.d-api.cloud")
.defaultHeader("Authorization", apiKey)
.requestFactory(factory)
.build();
}
}
@Service
class WhatsAppService {
private final RestClient dapi;
WhatsAppService(RestClient dapiRestClient) { this.dapi = dapiRestClient; }
record SendImage(String sessionId, String to, String image, String caption) {}
void enviarComprovante(String sessionId, String to, String urlImagem) {
dapi.post()
.uri("/api/v1/messages/send/image")
.contentType(MediaType.APPLICATION_JSON)
.body(new SendImage(sessionId, to, urlImagem, "Seu comprovante"))
.retrieve()
.onStatus(HttpStatusCode::isError, (req, res) -> {
String corpo = new String(res.getBody().readAllBytes());
throw new DApiException(res.getStatusCode().value(), corpo);
})
.toBodilessEntity();
}
}O onStatus transforma qualquer 4xx ou 5xx em uma exceção sua, com o corpo da API, que traz o motivo no campo error. Uma falha de rede ou tempo esgotado chega como ResourceAccessException. Separar as duas ajuda a decidir o que merece nova tentativa.
Receber o webhook com @PostMapping
Todo evento chega com o mesmo envelope, então um record com JsonNode no campo data dá conta de todos os tipos. O controller valida o token do caminho, entrega o evento a um serviço assíncrono e responde. Como o webhook de WhatsApp não é assinado, esse token secreto é a principal barreira contra chamadas falsas.
record WebhookEvent(String event, String sessionId, String timestamp, String traceId, JsonNode data) {}
@RestController
class WhatsAppWebhookController {
private final EventoProcessor processor;
private final String token;
WhatsAppWebhookController(EventoProcessor processor, @Value("${dapi.webhook-token}") String token) {
this.processor = processor;
this.token = token;
}
@PostMapping("/webhooks/whatsapp/{token}")
ResponseEntity<Void> receber(@PathVariable("token") String recebido, @RequestBody WebhookEvent evento) {
if (!MessageDigest.isEqual(token.getBytes(), recebido.getBytes())) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
if ("messages.received".equals(evento.event()) && !evento.data().path("fromMe").asBoolean()) {
processor.processar(evento); // método @Async, roda em outro executor
}
return ResponseEntity.ok().build();
}
}Lembre de habilitar @EnableAsync e de definir um executor com fila limitada. Em aplicações com mais de uma instância, ou quando o processamento chama outros sistemas lentos, troque o @Async por uma fila persistente. A lista de eventos e o que vem em cada um estão em webhook de WhatsApp.
Monitorar a conexão pelo próprio webhook
Em sistemas corporativos, o incidente mais comum não é uma mensagem com erro, e sim um número que parou de funcionar sem ninguém perceber. O evento connection.status resolve isso: ele chega no mesmo endpoint, com data.status valendo connected, disconnected ou logged_out. No processor, trate esse evento antes das mensagens:
- disconnected: registre e acompanhe. Quedas curtas acontecem, e a própria D-API tenta restabelecer a conexão.
- logged_out: o aparelho saiu da sessão e alguém precisa ler o QR Code de novo. Esse é o caso para abrir alerta no seu canal de plantão ou avisar o cliente dono do número.
- connected: feche o alerta e libere os envios que ficaram retidos, se a sua aplicação os segurou.
Transformar esses eventos em métrica, com um contador por status no Micrometer, deixa o painel do Grafana mostrando quantas conexões estão saudáveis a qualquer momento, sem polling na API.
Oficial ou não oficial, o mesmo código
Muitos times Java trabalham em empresas que avaliam a API oficial da Meta por exigência de compliance. Na D-API, o tipo é escolhido na criação da sessão (unofficial ou cloud_api), e o sessionId resultante funciona nas mesmas rotas de envio mostradas acima. A diferença está nas regras: para iniciar conversa na oficial é preciso template aprovado pela Meta. A comparação completa está em API oficial vs não oficial, e o produto em API oficial de WhatsApp. Para quem integra WhatsApp em sistemas de gestão, o que costuma ser o caso em Java, vale ler também sobre API de WhatsApp para ERP.
Perguntas frequentes
Tem SDK Java da D-API no Maven Central?
Não. A API é REST e qualquer cliente HTTP do ecossistema Java atende: o HttpClient nativo, RestClient, WebClient, OkHttp ou Feign. Os exemplos desta página usam só o que vem no JDK e no Spring Boot.
Funciona em Java 8?
O java.net.http.HttpClient só existe a partir do Java 11. Em Java 8 a integração continua possível com HttpURLConnection, Apache HttpClient ou OkHttp, com o mesmo endpoint, header e corpo JSON.
RestClient ou WebClient no Spring?
Se a aplicação é Spring MVC tradicional, RestClient, disponível a partir do Spring Framework 6.1, é mais simples e síncrono. WebClient faz sentido quando a aplicação inteira já é reativa com WebFlux.
O código muda se eu usar a API oficial da Meta?
O envio de texto e mídia continua na mesma rota, com o sessionId da conexão oficial. O que muda é iniciar conversa: na API oficial fora da janela de atendimento é preciso usar template aprovado, pela rota de template.
Como processar o webhook sem prender a thread do Tomcat?
Responda 200 logo depois de validar e mande o evento para um executor, um método anotado com Async ou uma fila. No Java 21, um executor de virtual threads é uma opção leve para esse trabalho.
Continue lendo
Teste a API de WhatsApp da D-API
Trial de 3 dias com acesso completo. Sem cartão, sem fidelidade.