Get in Touch

Have a question about the platform, need help with your integration, or want to discuss partnership opportunities and enterprise pricing? Drop us an email — we’ll do our best to get back to you within 3–4 hours.

contact@fiwano.com
Menu da documentação

Trabalhando com um agente de IA? Baixe a documentação completa como arquivo Markdown para usar como contexto.

Baixar .md completo

Recebendo Mensagens

Quando um usuário envia mensagem ao seu canal conectado, o Fiwano entrega a mensagem — e depois seus status de entrega — à webhook_url do seu canal como uma requisição POST. Esta página cobre a verificação de webhooks, os formatos de payload por canal, o download de mídia recebida e a consulta ao perfil de um remetente.

Defina a webhook_url e escolha quais webhook_events receber ao conectar um canal (veja Canais); por padrão nenhum evento está habilitado. Se o canal tiver um webhook_secret, cada entrega é assinada para você verificar que veio do Fiwano — fortemente recomendado. Enquanto você não definir um segredo, as entregas são enviadas sem assinatura.

Setup e payload de webhook do WhatsApp

Para WhatsApp, habilite message.received no canal e aponte webhook_url para seu endpoint HTTPS público. A Fiwano recebe o webhook original da Meta vindo da WhatsApp Cloud API, resolve o canal conectado, normaliza o payload, assina a entrega se você configurou um webhook_secret e envia para você.

A diferença útil em relação a integrar direto na Meta é que o envelope do webhook tem o mesmo formato nos três canais:

  • Remetentes do WhatsApp chegam como números de telefone em data.from.
  • Remetentes do Instagram chegam como valores IGSID em data.from.
  • Remetentes do Facebook Messenger chegam como valores PSID em data.from.

Os campos de nível superior (event, channel_id, channel_type, timestamp, data) permanecem estáveis, então um único receiver consegue lidar com webhooks do WhatsApp, webhooks do Instagram e webhooks do Messenger sem três parsers separados da Meta.

Verificando assinaturas

Quando o canal tem um webhook_secret, toda requisição de webhook inclui um header X-Webhook-Signature:

X-Webhook-Signature: sha256=<hmac_hex>

Para verificar: compute o HMAC-SHA256 do corpo bruto (raw) da requisição usando seu webhook_secret como chave, depois compare o digest em hex. (Se nenhum segredo estiver configurado, esse header não é enviado — defina um para habilitar a verificação.)

import hmac, hashlib

def verify_signature(body: bytes, secret: str, header: str) -> bool:
    expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
    received = header.replace("sha256=", "")
    return hmac.compare_digest(expected, received)

Tipos de evento

Cada tipo de canal suporta um conjunto específico de eventos de webhook. Somente os eventos que você habilitar explicitamente via webhook_events são entregues. Por padrão, nenhum evento está habilitado — você precisa configurá-los após conectar um canal.

Evento Descrição Canais
message.received Mensagem recebida de um usuário Todos
message.echo Cópia de uma mensagem que sua empresa enviou fora da Fiwano (app WhatsApp Business, caixa de entrada do Instagram, Caixa de Entrada da Página do Facebook, Meta Business Suite, outra integração) Todos
message.sent Sua mensagem foi aceita pela Meta WhatsApp
message.delivered Mensagem entregue no dispositivo do destinatário WhatsApp, Instagram, Facebook
message.read Mensagem lida pelo destinatário * WhatsApp, Instagram, Facebook
message.failed A entrega da mensagem falhou WhatsApp

* message.read depende das configurações de privacidade do destinatário no WhatsApp e no Instagram — se ele tiver desativado as confirmações de leitura, o status read nunca chegará. Trate delivered como um estado terminal de sucesso.

Novos tipos de evento podem ser adicionados com o tempo; eles nunca são ativados em um canal existente até você incluí-los em webhook_events. Ignore campos de payload que você não conhece — veja Compatibilidade.

Rastreamento de status de entrega

Quando você envia texto, mídia ou um modelo do WhatsApp por qualquer endpoint de envio, recebe um message_id (UUID). Todos os webhooks de status subsequentes referenciam esse mesmo UUID; o ID da Meta permanece interno ao Fiwano.

  • message_id está sempre presente em todos os eventos de status — é um UUID gerado pelo Fiwano, não um ID interno da Meta.
  • Progressão de status: sent → delivered → read. Cada status implica todos os anteriores.
  • data.recipient é o identificador do usuário: número de telefone (WhatsApp), IGSID (Instagram) ou PSID (Facebook).
  • Todos os canais usam exatamente o mesmo formato de webhook.
  • Cascata de leitura: quando um usuário lê uma conversa, o Fiwano envia um webhook message.read separado para cada mensagem não lida — não apenas a mais recente. As confirmações de leitura do Facebook e do Instagram são por conversa (a Meta informa "lido até este momento", e o Instagram nomeia apenas a última mensagem), então o Fiwano as resolve contra todas as mensagens que você enviou àquele usuário; o WhatsApp reporta cada mensagem individualmente.
  • Os status podem chegar fora de ordem — no Instagram um read pode chegar antes do delivered da mesma mensagem. Trate o status mais alto já visto como o atual e faça upsert por message_id.

Formato do payload

Todos os payloads compartilham a mesma estrutura de nível superior:

{
  "event": "message.received",
  "channel_id": "a1b2c3d4e5f67890",
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": { ... }
}

message.received (WhatsApp)

{
  "event": "message.received",
  "channel_id": "a1b2c3d4e5f67890",
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "wamid.xxx",
    "from": "1234567890",
    "from_name": "John Doe",
    "type": "text",
    "text": "Hello!"
  }
}

data.from — número de telefone do remetente sem +. Use diretamente como recipient ao responder.

message.received (Instagram)

{
  "event": "message.received",
  "channel_id": "b2c3d4e5f6789012",
  "channel_type": "instagram",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "mid.xxx",
    "from": "6543217890123456",
    "from_name": null,
    "type": "text",
    "text": "Hi there!"
  }
}

data.from — IGSID. Use como recipient ao responder. from_name é sempre null (a Meta não inclui o nome do remetente nos webhooks do IG).

message.received (Facebook Messenger)

{
  "event": "message.received",
  "channel_id": "c3f8a1b2e4d56789",
  "channel_type": "facebook",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "mid.xxx",
    "from": "7890123456789012",
    "from_name": null,
    "type": "text",
    "text": "Hello from Messenger!"
  }
}

data.from — PSID. Use como recipient ao responder. from_name é sempre null (a Meta não inclui o nome do remetente nos webhooks do FB).

message.received — mídia (Pro)

Com uma licença Pro, mensagens de mídia incluem o conteúdo do arquivo. O arquivo de mídia é baixado da Meta e armazenado temporariamente. Use o download_url para buscar o arquivo antes que ele expire.

Exemplo de imagem no WhatsApp:

{
  "event": "message.received",
  "channel_id": "a1b2c3d4e5f67890",
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "wamid.xxx",
    "from": "1234567890",
    "from_name": "John Doe",
    "type": "image",
    "caption": "Check this photo",
    "media": {
      "media_id": "m1b2c3d4e5f67890",
      "mime_type": "image/jpeg",
      "file_size": 245760,
      "filename": null,
      "sha256": "abc123...",
      "duration_ms": null,
      "download_url": "https://fiwano.com/api/v1/media/m1b2c3d4e5f67890",
      "expires_at": "2025-01-15T11:30:00Z"
    }
  }
}

Exemplo de mensagem de voz no WhatsApp:

{
  "event": "message.received",
  "channel_id": "a1b2c3d4e5f67890",
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "wamid.xxx",
    "from": "1234567890",
    "from_name": "John Doe",
    "type": "audio",
    "media": {
      "media_id": "m2b3c4d5e6f78901",
      "voice": true,
      "mime_type": "audio/ogg; codecs=opus",
      "file_size": 12345,
      "filename": null,
      "sha256": "def456...",
      "duration_ms": 5200,
      "download_url": "https://fiwano.com/api/v1/media/m2b3c4d5e6f78901",
      "expires_at": "2025-01-15T11:30:00Z"
    }
  }
}

Instagram e Facebook Messenger entregam o mesmo bloco data.media; apenas data.from muda (IGSID ou PSID em vez de um número de telefone).

data.type é o tipo da mensagem em todos os canais e vem sempre de um conjunto fixo: text, image, audio, video, document, sticker (somente WhatsApp) ou unsupported. Faça o roteamento por ele. Os quatro tipos de mídia são exatamente os valores aceitos como media_type de saída, então um evento de mídia recebido pode ser encaminhado sem tabela de conversão — exceto sticker, que existe apenas na entrada e precisa ser recodificado para sair como image.

Instagram e Facebook Messenger também usam anexos para coisas que não são arquivo, como um post compartilhado ou um pin de localização. Esses chegam como type: "unsupported", sem bloco data.media — veja tipo unsupported abaixo.

Quando a Meta inclui texto junto com mídia, o Fiwano expõe esse texto como data.caption no evento de mídia. Mensagens somente de texto continuam usando data.text. Essa regra é igual para WhatsApp, Instagram e Facebook Messenger.

Vários anexos: cada anexo é entregue em seu próprio webhook message.received e em seu próprio POST HTTP; o Fiwano nunca envia um array de eventos de webhook. Todos os arquivos da mensagem de origem são preparados antes da entrega do primeiro evento, e depois os eventos são enviados na ordem dos anexos da Meta. O primeiro evento mantém o ID da mensagem da Meta e recebe a legenda, quando houver. Os eventos seguintes usam IDs determinísticos com .2, .3 e assim por diante, sem repetir a legenda:

mid.xxx       imagem + legenda
mid.xxx.2     imagem
mid.xxx.3     vídeo

Trate o message_id recebido como uma chave opaca de idempotência; não interprete o sufixo nem envie esse ID para a Meta. As tentativas de entrega continuam independentes por evento, então uma falha no endpoint do cliente ainda pode fazer uma parte posterior chegar antes da nova tentativa de uma parte anterior. Uma falha no download de mídia não elimina os demais anexos: seu evento contém media.download_url: null e media.error.

O Fiwano preserva o formato de arquivo original da Meta e não faz transcodificação de mídia. media.mime_type descreve os bytes do arquivo baixado, não a semântica da mensagem. Por exemplo, clipes em estilo de voz do Facebook Messenger costumam baixar como OGG/Opus (audio/ogg), enquanto mensagens de áudio do Instagram podem baixar como MP4 somente-áudio servido com video/mp4. Em ambos os casos o tipo da mensagem ainda é data.type: "audio".

Apenas para WhatsApp recebido, a Meta fornece um indicador confiável de mensagem de voz. O Fiwano o expõe como media.voice: true quando presente. Instagram e Facebook Messenger não expõem um indicador de voz confiável equivalente pelo payload do webhook, então media.voice é omitido para esses canais.

O download_url é autenticado; busque-o com sua X-API-Key. Não o passe diretamente como media_url de saída, porque a Meta não enviará o header da sua API key — re-hospede os bytes atrás de uma URL HTTPS pública ou assinada primeiro.

Campos do payload de mídia:

Campo Tipo Descrição
media_id string ID do arquivo de mídia — use em GET /api/v1/media/{media_id} para baixar
voice bool Presente apenas para mensagens de voz do WhatsApp (true). Omitido para IG/FB porque a Meta não fornece um indicador de voz confiável ali.
mime_type string Tipo MIME (ex.: image/jpeg, audio/ogg; codecs=opus)
file_size int Tamanho do arquivo em bytes
filename string|null Nome de arquivo original (apenas documentos)
sha256 string|null Hash SHA-256 da Meta (apenas WhatsApp)
duration_ms int|null Duração em milissegundos (apenas áudio/vídeo)
download_url string|null URL de download autenticada. null se o download da Meta falhou.
error string Presente apenas quando o download falhou — descreve o erro
expires_at string Timestamp ISO 8601 — o arquivo é excluído após esse horário

Observação: Trate mensagens de voz como mensagens de áudio. data.type: "audio" é o valor estável entre canais para roteamento e encaminhamento. media.voice é uma dica opcional, exclusiva do WhatsApp, para UI/UX.

Baixando mídia recebida

Busque o arquivo em data.media.download_url (que é GET /api/v1/media/{media_id}) com sua X-API-Key:

curl https://fiwano.com/api/v1/media/m1b2c3d4e5f67890 \
  -H "X-API-Key: YOUR_API_KEY" \
  --output photo.jpg

A resposta são os bytes brutos do arquivo com o Content-Type original (e um nome de arquivo em Content-Disposition quando conhecido). Os arquivos expiram cerca de 60 minutos após o Fiwano buscá-los da Meta; media.expires_at é a referência definitiva. Baixe prontamente e re-hospede o que precisar manter; após a expiração a URL retorna 410 Gone. Os tamanhos estão em Capacidades; os códigos de status na Referência da API.

message.received — tipo não suportado (todos os canais)

Uma mensagem chega como type: "unsupported" quando o Fiwano não pode entregar o conteúdo como arquivo. unsupported_type diz o que era, e não há bloco data.media. Existem dois motivos, e upgrade_required os distingue.

Mídia em uma licença Starter. O arquivo existe, mas seu plano não o inclui. unsupported_type é o tipo de mídia que o Pro teria entregue, e upgrade_required indica o plano que o libera:

{
  "event": "message.received",
  "channel_id": "a1b2c3d4e5f67890",
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:00Z",
  "data": {
    "message_id": "wamid.xxx",
    "from": "1234567890",
    "from_name": "John Doe",
    "type": "unsupported",
    "unsupported_type": "image",
    "upgrade_required": "pro"
  }
}

Faça upgrade pela página Billing no portal para receber o conteúdo completo de mídia.

Conteúdo que não é um arquivo. Nenhum plano entrega esses casos, então upgrade_required está ausente. unsupported_type carrega o nome que a própria Meta usa para o conteúdo:

Canal Valores de unsupported_type
WhatsApp location, contacts e outros tipos de mensagem que não são mídia
Instagram, Facebook Messenger share e ig_reel (post ou reel compartilhado), story_mention, location, fallback (link compartilhado), template, unsupported

Qualquer tipo não listado aqui chega da mesma forma, então um unsupported_type desconhecido continua sendo apenas conteúdo não suportado. Reações a mensagens são ignoradas e não são entregues como eventos de webhook.

message.echo — mensagens enviadas fora da Fiwano

Quando alguém do seu lado responde a um cliente sem passar pela Fiwano, a Meta ecoa essa mensagem de volta — e a Fiwano pode entregar uma cópia para você, para que seu sistema veja a conversa inteira, não apenas a metade dele. Origens por canal:

Canal De onde a mensagem foi enviada
WhatsApp App WhatsApp Business ou dispositivo vinculado, em um número Coexistence
Instagram Caixa de entrada do app Instagram, Meta Business Suite ou outra integração
Facebook Messenger Caixa de Entrada da Página, Meta Business Suite ou outra integração

Habilite por canal adicionando message.echo a webhook_events (desativado por padrão, disponível em todos os planos). Mensagens enviadas pela Fiwano nunca chegam como eco — você já as tem.

{
  "event": "message.echo",
  "channel_id": "b2c3d4e5f6789012",
  "channel_type": "instagram",
  "timestamp": "2026-09-01T10:30:00Z",
  "data": {
    "message_id": "550e8400-e29b-41d4-a716-446655440000",
    "recipient": "6543217890123456",
    "status": "sent",
    "type": "text",
    "text": "Resposta do operador"
  }
}
  • message_id — um UUID da Fiwano, exatamente como o que você recebe ao enviar pela API. Ele é estável: se a Meta reenviar o mesmo eco, você recebe o mesmo UUID — deduplique por ele.
  • recipient — o usuário que recebeu a mensagem, no mesmo formato aceito pelos endpoints de envio (telefone no WhatsApp, IGSID no Instagram, PSID no Facebook). Você pode responder diretamente para recipient.
  • status: "sent" — o estado inicial do ciclo de vida. Um eco confirma que a mensagem existe na conversa, não que chegou ao dispositivo do destinatário. Não é emitido message.sent separado para ecos.
  • Quem exatamente enviou a mensagem (operador, dispositivo ou app) não é exposto — a Meta não fornece uma identidade confiável para isso.

Rastreamento de status para ecos. Por padrão um eco é uma cópia única: sem delivered/read posteriores. Defina o campo booleano echo_statuses do canal como true (via PATCH /api/v1/channels/{id} ou no Portal) e as mensagens ecoadas ganham o mesmo ciclo de status das mensagens enviadas pela Fiwano: os webhooks message.delivered / message.read / message.failed seguintes referenciam o mesmo message_id do eco e são filtrados pelo seu webhook_events exatamente como os status comuns.

Os status delivered e read de ecos do WhatsApp são entregues da mesma forma que para mensagens enviadas pela Fiwano. A Meta não garante formalmente a entrega de status para mensagens enviadas do app WhatsApp Business, então trate um status ausente como normal, não como erro.

O Instagram não tem recibo de entrega; o próprio eco equivale ao delivered sintético que a Fiwano emite para os seus envios no Instagram, então nenhum message.delivered separado segue um eco do Instagram. Uma confirmação de leitura do Instagram cobre a conversa inteira: segue um message.read para cada mensagem ecoada que o usuário ainda não tinha lido, da mesma forma que para as mensagens enviadas pela Fiwano.

Status e ecos são entregues de forma independente e at-least-once: um status pode ocasionalmente chegar antes do eco ao qual pertence. Correlacione por message_id e faça upsert em vez de depender da ordem de chegada.

Mídia em ecos não é entregue. Uma mensagem de mídia ecoada mantém o data.type real (image, audio, video, document, sticker) e a legenda quando presente, mas o arquivo em si é pulado — data.media chega sem download:

{
  "data": {
    "message_id": "550e8400-e29b-41d4-a716-446655440000",
    "recipient": "6543217890123456",
    "status": "sent",
    "type": "image",
    "caption": "Foto da fatura",
    "media": {"media_id": null, "download_url": null, "unavailable": "echo_media_not_supported", "kind": "image"}
  }
}

A regra que você já aplica à mídia recebida — verifique media.download_url antes de baixar — cobre este caso sem código extra e mantém seu handler compatível caso a mídia de ecos fique disponível no futuro. Mensagens do Instagram/Messenger com múltiplos anexos são divididas em eventos message.echo separados por anexo (cada um com seu próprio message_id), e anexos que não são arquivos chegam como type: "unsupported" com unsupported_type — igual ao message.received.

Não são entregues como ecos: reações, edições e exclusões de mensagens (unsend). São alterações de uma mensagem existente, não mensagens novas, e são puladas silenciosamente. No WhatsApp, ecos existem apenas para números Coexistence — um canal conectado somente pela Cloud API não tem origem de mensagens externas, então message.echo nunca dispara nele.

Atenção: nunca espelhe um eco de volta na mesma conversa automaticamente. Sua resposta não geraria eco (envios da Fiwano são filtrados), mas um bot do outro lado — ou uma segunda integração que também espelha ecos — pode criar um loop. Sempre deduplique por message_id antes de agir sobre um eco.

message.delivered / message.read (todos os canais)

{
  "event": "message.read",
  "channel_id": "b2c3d4e5f6789012",
  "channel_type": "instagram",
  "timestamp": "2025-01-15T10:30:10Z",
  "data": {
    "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "status": "read",
    "recipient": "6543217890123456"
  }
}

Mesmo formato para todos os canais e todos os status (sent, delivered, read). message_id é o UUID da resposta de envio.

message.failed (WhatsApp)

{
  "event": "message.failed",
  "channel_id": "a1b2c3d4e5f67890",
  "channel_type": "whatsapp",
  "timestamp": "2025-01-15T10:30:05Z",
  "data": {
    "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "recipient": "1234567890",
    "status": "failed",
    "error": "Message undeliverable",
    "errors": [{"code": 131047, "title": "Message undeliverable"}]
  }
}

Política de novas tentativas

Se sua webhook URL retornar um status não-2xx ou estiver inacessível, o sistema tenta novamente automaticamente:

  • 7 tentativas com backoff exponencial: 30s, 1m, 2m, 2m, 2m, 2m, 2m (~12 minutos no total)
  • Prazo rígido de 20 minutos — após o qual a entrega é marcada como permanentemente falha
  • E-mail de aviso enviado após a 3ª tentativa falha (com as novas tentativas ainda em andamento)
  • Alerta por e-mail enviado quando todas as tentativas se esgotam (falha permanente)
  • Os payloads são criptografados em repouso durante as novas tentativas e apagados após a entrega ou expiração

Importante: Seu endpoint deve responder com HTTP 2xx em até 5 segundos. Respostas não-2xx ou timeouts disparam a fila de novas tentativas. A Fiwano não retém para repasse os payloads de webhook entregues com sucesso; após uma falha na entrega inicial, o payload criptografado é armazenado temporariamente para novas tentativas automáticas.

Dica: Habilite apenas os eventos de webhook que você de fato trata. Eventos não tratados que recebem respostas não-2xx vão encher sua fila de novas tentativas desnecessariamente.

Perfil do remetente

O WhatsApp inclui o nome do remetente em cada webhook (data.from_name) — nenhuma chamada extra necessária. Instagram e Facebook não (data.from_name é sempre null); para obter um nome ou avatar, chame o endpoint de perfil:

GET /api/v1/channels/{channel_id}/profile/{user_id}

Passe o valor de data.from (IGSID para Instagram, PSID para Facebook) como user_id. Ele retorna:

  • Instagramusername, name, profile_pic, follower_count, is_verified_user
  • Facebook — nome de exibição em first_name; last_name e profile_pic quando disponíveis na Meta

WhatsApp não é suportado (o nome já está no webhook). Resultados bem-sucedidos são cacheados por 5 minutos; resultados indisponíveis são cacheados brevemente para que uma conversa recém-indexada possa ser consultada novamente em pouco tempo. A flag cached da resposta indica se foi um acerto de cache. Request/response completos e códigos de status estão na Referência da API.

Dica: chame isto uma vez quando você vir um novo data.from pela primeira vez, depois cacheie o resultado do seu lado — não há necessidade de chamar a cada mensagem.

Perguntas frequentes

Como recebo mensagens do WhatsApp com um webhook?

Defina uma webhook_url no canal de WhatsApp conectado e habilite message.received em webhook_events. A Fiwano então envia cada mensagem recebida do WhatsApp para seu endpoint como um POST assinado com payload JSON normalizado.

Como é o payload de um webhook do WhatsApp?

O nível superior sempre inclui event, channel_id, channel_type, timestamp e data. Para mensagens de texto do WhatsApp, data inclui message_id, from, from_name, type e text. Mensagens de mídia incluem uma download_url autenticada e temporária no Pro.

O mesmo formato de webhook funciona para Instagram e Messenger?

Sim. A Fiwano normaliza WhatsApp, Instagram DM e Facebook Messenger no mesmo envelope de evento. O identificador do remetente muda por canal — número de telefone, IGSID ou PSID — mas o formato do webhook permanece consistente.

Como verifico a assinatura de um webhook da Fiwano?

Defina um webhook_secret no canal, compute HMAC-SHA256 sobre o corpo bruto da requisição com esse segredo e compare com o header X-Webhook-Signature.