WhatsApp (Meta)
Visão Geral da Integração WhatsApp Business API
Descrição
O projeto OMN utiliza a API oficial do WhatsApp Business (Graph API v23.0) para comunicação, permitindo envio e recebimento de mensagens, gerenciamento de templates e processamento de webhooks.
1. Configuração e Inicialização
1.1 Constructor
Descrição: Inicializa a classe Whatsapp com cliente HTTP configurado para Graph API e injeta serviço S3 para gerenciamento de mídia.
Entrada:
constructor(@Inject(S3Service) private readonly s3Service: S3Service)
Implementação:
constructor(@Inject(S3Service) private readonly s3Service: S3Service) {
this.http = axios.create({
baseURL: 'https://graph.facebook.com/v23.0/',
})
}
2. Operações de Mídia
2.1. Obtenção da midia
2.1.1 getMediaLink
async getMediaLink(id: string, whatsappId: string, token: string): Promise<string> {
const url = `${this.http.defaults.baseURL}${id}?phone_number_id=${whatsappId}`
Descrição: Obtém mídia do WhatsApp através do ID, faz download e upload para S3, retornando URL acessível.
Entrada:
{
id: string, // ID da mídia no WhatsApp
whatsappId: string, // Phone Number ID
token: string // Access Token
}
Implementação:
// 1. GET /{id}?phone_number_id={whatsappId}
try{
const response = await this.http.get(url, {
headers: { Authorization: `Bearer ${token}` }
})
// 2. Download da mídia
const media = await this.http.get(metaUrl, {
headers: { Authorization: `Bearer ${token}` },
responseType: 'arraybuffer'
})
const mimetype = media.headers['content-type']
// 3. Upload para S3
return this.s3Service.upload(media.data, mimetype)
} catch (error) {
this.logger.log('Error:', error)
addTags({
'error.type': error.name,
'error.msg': error.message,
'error.stack': error.stack,
})
return url
}
Retorno:
string // URL da mídia (S3 ou original)
2.2. Upload de Mídia
2.2.1 uploadMedia
async uploadMedia(mediaUri: string, whatsappId: string, token: string): Promise<{ id: string }>
Descrição: Faz upload de arquivo para WhatsApp, com conversão automática de áudio para formato compatível.
Entrada:
{
mediaUri: string, // URI do arquivo
whatsappId: string, // Phone Number ID
token: string // Access Token
}
Implementação:
// 1. Extrai nome e extensão do arquivo da URI Obtem buffer e detecta tipo
const { fileName, extension } = Utils.getFileInfo(mediaUri)
let buffer = await Utils.getBuffer(mediaUri)
let mimeType = mime.lookup(extension)
const fileType = await fromBuffer(buffer) // detecta tipo real arquivo
// 2. Conversão de áudio se necessário
if (fileType?.mime.startsWith('audio')) {
const { buffer: whatsappAudioBuffer, mimeType: whatsappAudioMimeType } =
await AudioService.getInstance().convertAudioBufferToWhatsAppFormat(buffer)
buffer = whatsappAudioBuffer
mimeType = whatsappAudioMimeType
}
// 3. Upload via FormData
const form = new FormData()
form.append('file', blob, fileName)
form.append('messaging_product', 'whatsapp')
const response = await this.http.post(`${whatsappId}/media`, form, {
headers: { Authorization: `Bearer ${token}` }
})
Retorno:
{
id: string, // ID da mídia no WhatsApp
}
3. Envio de Mensagens
3.1. Payloads
3.1.1. toWhatsappMessagePayload
toWhatsappMessagePayload(message: SendMessagePayload): SendMessageWhatsappPayload {
Descrição: Converte payload interno para formato da API do WhatsApp, processando texto, mídia e contexto.
Entrada:
{
body?: string, // Texto da mensagem
to: string, // Destinatário
from:string, // Rementente
userName: string,
type:string,
mediaUri?: string, // URI da mídia
interactive?: object, // Elementos interativos
template?: object, // Template
quoted?:string,
quotedMessage?: { // Mensagem citada
id: string,
message?:string
}
}
Implementação:
const { body, to, mediaUri, interactive, template, quotedMessage } = message
let payload: SendMessageWhatsappPayload = {
messaging_product: 'whatsapp',
to
}
// Contexto para mensagem citada
if (quotedMessage?.id) {
payload.context = { message_id: message.quotedMessage.id }
}
// Processamento da mensagem de texto
if (body) {
payload = { ...payload, type: 'text', text: { body } }
}
// Proecssamento de Midia
if (mediaUri) {
const { type, fileName } = Utils.getFileInfo(mediaUri)
const supportedTypes = ['audio', 'document', 'image', 'sticker', 'video']
if (!supportedTypes.includes(type)) {
new BadRequestException('Media not supported')
}
payload = { ...payload, type, [type]: { link: mediaUri } }
if (type === 'document') payload.document.filename = fileName
}
// Processamento de template
if (template) {
payload = {
...payload,
type: 'template',
template: {
name: 'hello_world',
language: {
code: 'en_US',
},
},
}
}
return payload
Retorno:
{
messaging_product: 'whatsapp',
to: string,
type?: string,
text?: { body: string },
context?: { message_id: string },
audio?: { id?: string; link?: string },
document?: { id?: string; link?: string; filename?: string },
image?: { id?: string; link?: string },
sticker?: { id?: string; link?: string },
video?: { id?: string; link?: string }
}
3.2. Envio de Mensagem
3.2.1. sendMessage
async sendMessage(message: SendMessagePayload, whatsappId: string, token: string)
Descrição: Envia mensagem via WhatsApp Business API, com upload automático de mídia e fallback para link de texto.
Entrada:
{
message: SendMessagePayload, // Payload da mensagem
whatsappId: string, // Phone Number ID
token: string // Access Token
}
Implementação:
// 1. Transformar payload
const payload = this.toWhatsappMessagePayload(message)
const { type } = payload
const link = payload[type]?.link
const textTypes = ['template', 'text', 'interactive']
const isMedia = !textTypes.includes(type)
// 2. Processamento Midia chamando uploadMedia()
try{
if (isMedia && link) {
let media = await this.uploadMedia(link, whatsappId, token)
if (media) {
payload[type] = media
} else {
// Fallback para link de texto
delete payload[type]
payload.type = 'text'
payload.text = {
body: `Por favor, clique no link para realizar o download do arquivo: \n${link}`
}
}
}
// 3. Enviar mensagem
const response = await this.http.post(`${whatsappId}/messages?access_token=${token}`, payload)
// 4. Delay para mídia
if (isMedia) await Utils.delay(parseInt(process.env.MEDIA_MESSAGE_DELAY_MS || '1000', 10))
return response
} catch (error) {
addTags({ whatsappPayload: JSON.stringify(payload) })
this.logger.error('Sending whatsapp message error:', error)
throw error
}
}
Retorno:
AxiosResponse // Response da API do WhatsApp
3.3. Envio de Template
3.3.1. sendTemplateMessage
async sendTemplateMessage(payload: StartConversationPayload, whatsappId: string, token: string, ): Promise<AxiosResponse>
Descrição: Envia mensagem de template para iniciar conversação, com validação de variáveis e fallback para userName/companyName.
Entrada:
{
to: string, // Destinatário
template?: string, // Nome do template
templateVariables?: string[], // Variáveis do template
userName?: string, // Nome do usuário (fallback)
companyName?: string // Nome da empresa (fallback)
}
Implementação:
// 1. Validação de variáveis
if (!payload?.templateVariables && !(payload.userName && payload.companyName))
throw new BadRequestException("You must provide 'templateVariables' or both 'userName' and 'companyName'")
// 2. Preparar variáveis
if (!payload?.templateVariables)
payload.templateVariables = [payload.userName, payload.companyName]
// 3. Criar parâmetros
const parameters = []
payload.templateVariables.forEach((v) => parameters.push({ type: 'text', text: v }))
// 4. Montar payload do template
const templatePayload = {
messaging_product: 'whatsapp',
to: payload.to,
type: 'template',
template: {
name: payload?.template || WhatsappDefaultTemplateNames.contato_cliente,
language: { code: 'pt_BR' },
components: [{
type: 'body',
parameters,
}],
},
}
// 5. Enviar
return this.http.post(`${whatsappId}/messages`, templatePayload, {
headers: { Authorization: `Bearer ${token}` }
})
Retorno:
AxiosResponse
4. Gerenciamento de Templates
4.1. Obtenção do template
4.1.1 getTemplates
public async getTemplates(wabaId: string, token: string)
Descrição: Lista todos os templates de mensagem aprovados na conta WhatsApp Business.
Entrada:
{
wabaId: string, // WhatsApp Business Account ID
token: string // Access Token
}
Implementação:
try {
const templates = (
await this.http.get(`${wabaId}/message_templates`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
)?.data?.data
return templates || []
} catch (error) {
this.logger.error('error in getTemplate', JSON.stringify(error), error?.response?.data)
addTags({
'error.type': error?.name,
'error.msg': error?.message,
'error.stack': error?.stack,
})
throw new Error('Cannot get templates in whatsapp')
}
}
Retorno:
Array<{
name: string,
components: Array<{
type: string,
format?: string,
text?: string,
example?: object
}>,
language: string,
status: string,
category: string,
id: string
}>
4.2. Criação do template
4.2.1 createTemplate
async createTemplate(wabaId: string, token: string, template?: CreateWhatsappTempatePayload)
Descrição: Cria novo template de mensagem no WhatsApp Business, com verificação de duplicatas e criação automática do template padrão.
Entrada:
{
wabaId: string, // WABA ID
token: string, // Access Token
template?: CreateWhatsappTempatePayload // Template personalizado
}
Implementação:
// 1. Verificar template padrão se não fornecido
try {
if (!template) {
const defaultTemplate = await this.getDefaultTemplate(wabaId, token)
if (defaultTemplate.length !== 0) return
template = this.generate360DefaultTemplate()
} else {
// 2. Verificar duplicatas
const userTemplates = await this.getTemplates(wabaId, token)
if (userTemplates.find((t) => t.name === template.name))
throw new ConflictException('Template name is already in use')
}
// 3. Criar template
await this.http.post(`${wabaId}/message_templates`, template, {
headers: { Authorization: `Bearer ${token}` }
})
} catch (error) {
this.logger.error('error in createTemplate', JSON.stringify(error), error?.response?.data)
addTags({
'error.type': error?.name,
'error.msg': error?.message,
'error.stack': error?.stack,
})
if (error instanceof ConflictException) throw error
throw new CreateTemplateException('Whastapp Meta')
}
}
Retorno:
void // Sucesso ou lança exceção
{
"id": "1234567890123456",
"status": "PENDING",
"category": "UTILITY"
}
4.3. Template Padrão
4.3.1 generate360DefaultTemplate
private generate360DefaultTemplate(): CreateWhatsappTempatePayload
Descrição: Gera estrutura do template padrão "contato_cliente" com variáveis para nome do usuário e empresa.
Implementação:
return {
name: WhatsappDefaultTemplateNames.contato_cliente,
category: TemplateCategories.UTILITY,
language: 'pt_BR',
components: [{
type: 'BODY',
text: 'Olá {{1}} !\n\nFalo em nome da {{2}} e estou entrando em contato para continuarmos nossa conversa.\n\nAguardo o seu retorno.',
example: {
body_text: [['Bruno', 'Alterdata']],
},
}],
}
Retorno:
{
name: string,
category: 'UTILITY',
language: 'pt_BR',
components: Array<{
type: 'BODY',
text: string,
example: {
body_text: string[][]
}
}>
}
5. Processamento de WebHooks
5.1. Recebimento de Mensagens
5.1.1 whatsappPayloadToIncomingMessage
Descrição: Converte webhook do WhatsApp para formato interno, processando texto, mídia e mensagens citadas.
Entrada:
{
payload: WhatsappMessageDto, // Webhook do WhatsApp
session: SessionDocument, // Sessão ativa
whatsapp: Whatsapp // Instância do WhatsApp
}
Implementação:
// 1. Extrair informações da mensagem
const { message } = getMessagesInfo(payload)
// 2. Criar objeto base
const incomingMessage: IncomingMessageDto = {
uid: message.id,
sessionId: session._id,
platformConfigId: session.platformConfigId,
channelId: session.channelId,
from: {
uid: session.client.providerId,
userName: session.client.name,
},
message: {
body: message?.text?.body || '',
},
source: session.type,
type: WebhookTypesEnum.MESSAGE,
}
// 3. Processar mensagem citada
if (message?.context?.id) {
incomingMessage.message.quoted = { id: message?.context?.id }
}
// 4. Processar mídia
if (message.image || message.document || message.voice || message.audio || message.sticker || message.video) {
const type = message?.type
const { whatsappId, token } = session.channel.params as WhatsappConfigParams
incomingMessage.message.media = {
uri: await whatsapp.getMediaLink(message[type].id, whatsappId, token),
contentType: message[type].mime_type,
fileName: '',
}
}
Retorno:
{
uid: string,
sessionId: ObjectId,
platformConfigId: string,
channelId: string,
from: {
uid: string,
userName: string
},
message: {
body: string,
quoted?: { id: string },
media?: {
uri: string,
contentType: string,
fileName: string
}
},
source: MessageServiceEnum,
type: WebhookTypesEnum.MESSAGE
}
5.2. whatsappPayloadToStatus (Mapeamento de status)
Descrição: Converte webhook de status do WhatsApp para formato interno, mapeando estados de entrega.
Entrada:
{
payload: WhatsappMessageDto, // Webhook do WhatsApp
platformConfigId: string, // ID da configuração
channelId: string // ID do canal
}
Implementação:
// 1. Extrair dados do status
const statusPayload = payload?.entry?.[0]?.changes?.[0]?.value?.statuses?.[0]
const metadata = payload?.entry?.[0]?.changes?.[0]?.value?.metadata
// 2. Mapear status
let messageStatus: WebhookStatusMessagesEnum
switch (statusPayload?.status) {
case WhatsappMessageUpdateEnum.SENT:
messageStatus = WebhookStatusMessagesEnum.SENDED
break
case WhatsappMessageUpdateEnum.DELIVERED:
messageStatus = WebhookStatusMessagesEnum.DELIVERED
break
case WhatsappMessageUpdateEnum.READ:
messageStatus = WebhookStatusMessagesEnum.READED
}
// 3. Criar objeto de status
const status: WebhookStatusDto = {
platformConfigId,
channelId,
type: WebhookTypesEnum.MESSAGE_UPDATE,
payload: {
message: {
from: metadata?.display_phone_number || null,
to: statusPayload?.recipient_id || null,
messageId: statusPayload?.id || null,
status: messageStatus,
}
}
}
Retorno:
{
platformConfigId: string,
channelId: string,
type: WebhookTypesEnum.MESSAGE_UPDATE,
payload: {
message: {
from: string | null,
to: string | null,
messageId: string | null,
status: WebhookStatusMessagesEnum
}
}
}
6. Configuração e Subscrição
6.1. Subscrição de App
6.1.1. subscribeApp
async subscribeApp(channel: PlatformChannelsConfig)
Descrição: Subscreve aplicação aos webhooks da conta WhatsApp Business, verificando se já está subscrito.
Entrada:
{
channel: PlatformChannelsConfig // Configuração do canal
}
Implementação:
//Extração de parâmetros
const wabaId = (channel.params as WhatsappConfigParams)?.wabaId
const token = (channel.params as WhatsappConfigParams)?.token
// 1. Obter apps subscritos
const subscribedApps = (
await this.http.get(`${wabaId}/subscribed_apps`, {
headers: { Authorization: `Bearer ${token}` }
})
)?.data?.data
// 2. Verificar se app já está subscrito
const appId = process.env.WHATSAPP_META_API_APP_ID || '1274999130660446'
const whatsappApp = subscribedApps.find((app: any) =>
app?.whatsapp_business_api_data?.id === appId
)
// 3. Subscrever se necessário
if (!whatsappApp)
await this.http.post(`${wabaId}/subscribed_apps`, {}, {
headers: { Authorization: `Bearer ${token}` }
})
Retorno:
AxiosResponse | void // Response da subscrição ou void se já subscrito
7. DTOs e Estruturas de Dados
7.1. WhatsappMessageDto
Descrição: DTO principal para recebimento de webhooks do WhatsApp, contendo mensagens e atualizações de status.
{
object: string,
entry: Array<{
id: string,
time?: number,
changes?: Array<{
value: {
messaging_product: string,
metadata: {
display_phone_number: string,
phone_number_id: string
},
contacts?: Array<{
profile: { name: string },
wa_id: string
}>,
messages?: Array<{
from: string,
id: string,
timestamp: string,
type: string,
text?: { body: string },
image?: { mime_type: string, sha256: string, id: string },
document?: { mime_type: string, sha256: string, id: string },
voice?: { mime_type: string, sha256: string, id: string },
audio?: { mime_type: string, sha256: string, id: string },
sticker?: { mime_type: string, sha256: string, id: string },
video?: { mime_type: string, sha256: string, id: string },
context?: { from: string, id: string }
}>,
statuses?: Array<{
id: string,
status: 'sent' | 'delivered' | 'read',
timestamp: string,
recipient_id: string,
conversation: {
id: string,
expiration_timestamp?: string,
origin: { type: string }
},
pricing: {
billable: boolean,
pricing_model: string,
category: string
}
}>
},
field: string
}>
}>
}
7.2. SendMessageWhatsappPayload
Descrição: Payload para envio de mensagens via API do WhatsApp, suportando texto, mídia e templates.
{
messaging_product: 'whatsapp',
to: string,
type?: string,
template?: {
name: string,
language: { code: string }
},
text?: { body: string },
context?: { message_id: string },
audio?: { id?: string; link?: string },
document?: { id?: string; link?: string; filename?: string },
image?: { id?: string; link?: string },
sticker?: { id?: string; link?: string },
video?: { id?: string; link?: string }
}
7.3. WhatsappConfigParams
Descrição: Parâmetros de configuração do canal WhatsApp, contendo credenciais e identificadores.
{
token: string, // Access Token
whatsappId: string, // Phone Number ID
wabaId: string // WhatsApp Business Account ID
}
8. Enums e Constantes
8.1. WhatsappMessageUpdateEnum
Descrição: Estados de entrega de mensagens no WhatsApp.
enum WhatsappMessageUpdateEnum {
SENT = 'sent',
DELIVERED = 'delivered',
READ = 'read'
}
8.2. WhatsappDefaultTemplateNames
Descrição: Nomes dos templates padrão utilizados no sistema.
enum WhatsappDefaultTemplateNames {
contato_cliente = 'contato_cliente'
}
8.3. TemplateCategories
Descrição: Categorias de templates suportadas pelo WhatsApp Business.
enum TemplateCategories {
UTILITY = 'UTILITY'
}