API de WhatsApp em Node.js: guia prático com o SDK oficial

Em Node.js, o caminho mais curto é o pacote d-api-sdk: um npm install, uma instância do client e você já cria a conexão, lê o QR Code e envia a primeira mensagem. Este guia cobre o fluxo inteiro em TypeScript, incluindo o webhook no Express e o tratamento de erro.

Por que usar o SDK em vez de montar as requisições

Toda a API de WhatsApp da D-API é REST, então nada impede que você escreva as chamadas com fetch ou axios. O ganho do SDK aparece no dia a dia: os métodos têm nome de ação (sessions.create, messages.sendText, interactive.sendList), os parâmetros são tipados e o editor avisa quando falta um campo obrigatório antes de o código ir para produção.

Node.js é a única linguagem com biblioteca oficial da D-API. Nas outras stacks a integração é feita com o cliente HTTP da própria linguagem, e o contrato é o mesmo. A página da integração Node.js SDK resume os módulos disponíveis.

Instalação e client configurado

Instale o pacote e crie um único client para a aplicação inteira. Guarde a API Key em variável de ambiente; ela vai no header Authorization sem o prefixo Bearer, e o SDK cuida disso sozinho.

npm install d-api-sdk
// src/dapi.ts
import { DApi } from 'd-api-sdk'

export const dapi = new DApi({
  apiKey: process.env.DAPI_API_KEY!,
  timeout: 10_000, // padrão é 30000 ms; em rota HTTP prefira algo menor
})

O baseUrl é opcional e aponta para https://api.d-api.cloud por padrão. O parâmetro timeout vale para todas as chamadas: quando estoura, o SDK aborta o fetch e lança um erro com status 408. Deixar esse valor explícito evita que uma requisição lenta segure o event loop de uma rota da sua aplicação por meio minuto.

Criar a conexão e exibir o QR Code

Cada número de WhatsApp é uma sessão, identificada por um sessionId que você escolhe. Na criação você já informa a URL do webhook, assim os eventos começam a chegar assim que o número conectar.

import { dapi } from './dapi'

await dapi.sessions.create({
  sessionId: 'loja-centro',
  connectionMode: 'qr',
  webhookUrl: 'https://seu-app.com/webhooks/whatsapp/' + process.env.WEBHOOK_TOKEN,
})

const { qrCodeImage } = await dapi.sessions.getQRCode('loja-centro')
// qrCodeImage é um data URI (data:image/png;base64,...)
// basta usar como src de uma <img> no painel do seu cliente

O QR expira em poucos segundos e é renovado pela API. Na tela de conexão, consulte getQRCode de novo em intervalos curtos até o número conectar, ou ouça o evento connection.status no webhook e troque a tela quando data.status vier como connected. Os detalhes de pareamento, inclusive por código em vez de QR, estão em API de WhatsApp com QR Code.

Enviar texto e lista com poucas linhas

Com o número conectado, o envio de texto pede três campos: sessão, destino no formato internacional sem o sinal de mais e o conteúdo. A lista interativa acrescenta o texto do botão e as seções com as opções.

await dapi.messages.sendText({
  sessionId: 'loja-centro',
  to: '5511999999999',
  text: 'Oi, Ana! Seu pedido 4812 foi aprovado.',
})

await dapi.interactive.sendList({
  sessionId: 'loja-centro',
  to: '5511999999999',
  description: 'Quando prefere receber?',
  buttonText: 'Ver horários',
  sections: [
    {
      title: 'Entrega',
      rows: [
        { rowId: 'manha', title: 'Manhã', description: '8h às 12h' },
        { rowId: 'tarde', title: 'Tarde', description: '13h às 18h' },
      ],
    },
  ],
})

O rowId é o que volta para o seu sistema quando a pessoa escolhe uma opção, então use valores que o código entenda sem consulta extra. Limites de seções e linhas, e o que o WhatsApp permite em botões, estão em botões e listas no WhatsApp.

Tratar erro e timeout

Quando a API responde com status de erro, ela devolve um corpo no formato { success: false, error, statusCode }. O SDK transforma isso em uma exceção do tipo DApiError, com message vindo do campo error, o status HTTP e o corpo original em response.

import type { DApiError } from 'd-api-sdk'

try {
  await dapi.messages.sendText({ sessionId: 'loja-centro', to, text })
} catch (err) {
  const e = err as DApiError
  if (e.status === 408) {
    // timeout do client: a mensagem pode ter saído ou não
    logger.warn({ to, status: e.status }, 'envio sem resposta a tempo')
  } else if (e.status >= 400 && e.status < 500) {
    // payload inválido ou sessão inexistente: tentar de novo não resolve
    logger.error({ status: e.status, error: e.message }, 'envio recusado')
  }
  throw err
}

Duas decisões valem ser tomadas cedo. Erro 4xx indica problema no pedido, como campo faltando ou sessão desconectada, e repetir a chamada só gera ruído. Já timeout e 5xx podem ser temporários. Quem manda muito volume costuma colocar o envio em uma fila (BullMQ, por exemplo), assim o request do seu produto responde rápido e o envio acontece em segundo plano.

Receber o webhook com Express

Mensagens recebidas, confirmações de leitura e mudanças de conexão chegam por POST na URL que você cadastrou. Todo evento tem o mesmo envelope: event, sessionId, data, timestamp e traceId. A rota abaixo valida o token secreto da URL, responde imediatamente e só depois trata a resposta da lista.

import express from 'express'
import { dapi } from './dapi'

const app = express()

app.post('/webhooks/whatsapp/:token', express.json({ limit: '1mb' }), (req, res) => {
  if (req.params.token !== process.env.WEBHOOK_TOKEN) {
    res.sendStatus(401)
    return
  }
  res.sendStatus(200) // responde antes de qualquer trabalho pesado

  const { event, sessionId, data } = req.body
  if (event !== 'messages.received' || data.fromMe) return

  if (data.type === 'list_response') {
    const escolha = data.data.selected_row_id // 'manha' ou 'tarde'
    const numero = data.from.jid.split('@')[0]
    dapi.messages
      .sendText({ sessionId, to: numero, text: 'Combinado, entrega agendada.' })
      .catch((err) => logger.error({ err, traceId: req.body.traceId }, 'falha na resposta'))
  }
})

app.listen(3000)

O webhook de WhatsApp não traz assinatura HMAC, por isso o token no caminho da URL faz o papel de segredo. Também dá para conferir o header User-Agent, que chega como Deliverify-Webhook/1.0, e se o sessionId pertence a um cliente seu. Os eventos disponíveis e a política de reenvio estão em webhook de WhatsApp.

Checklist antes de colocar em produção

  • API Key e token do webhook em variáveis de ambiente, nunca no repositório.
  • Timeout do client ajustado ao contexto: menor dentro de rota HTTP, maior em worker.
  • Deduplicação pelo data.id da mensagem, porque a mesma entrega pode chegar mais de uma vez se o seu servidor demorar a responder.
  • Log com o traceId do evento, que ajuda o suporte a rastrear uma entrega específica.
  • Um sessionId por número. Se o seu produto terá uma conexão por cliente, veja como isso se organiza na página de API de WhatsApp para SaaS.

Perguntas frequentes

Preciso usar o SDK ou posso chamar a API direto com fetch?

Os dois funcionam. A API é REST e aceita qualquer cliente HTTP. O d-api-sdk só poupa trabalho: já monta a URL, manda o header Authorization, aplica timeout e entrega tipos prontos para TypeScript.

O d-api-sdk funciona com JavaScript puro, sem TypeScript?

Sim. O pacote é publicado em JavaScript com as definições de tipo junto. Em um projeto sem TypeScript você importa e chama os mesmos métodos, só perde o autocomplete tipado no editor.

Qual versão do Node.js eu preciso?

O SDK usa o fetch nativo e o AbortController, então precisa de uma versão do Node.js que já traga fetch global, como a 18 ou superior. Em versões antigas o import funciona, mas as chamadas falham.

Como sei qual opção o cliente escolheu na lista?

A resposta chega no webhook como evento messages.received com type igual a list_response. Dentro de data.data vem selected_row_id, que é o rowId que você definiu ao enviar a lista.

Teste a API de WhatsApp da D-API

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