Trabalhando com um agente de IA? Baixe a documentação completa como arquivo Markdown para usar como contexto.
Baixar .md completoQuando 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.
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:
data.from.data.from.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.
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)
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 | |
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 |
* 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.
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.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).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.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.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": { ... }
}
{
"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.
{
"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).
{
"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).
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.
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.
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 |
|---|---|
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.
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 |
|---|---|
| App WhatsApp Business ou dispositivo vinculado, em um número Coexistence | |
| 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.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_idantes de agir sobre um eco.
{
"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.
{
"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"}]
}
}
Se sua webhook URL retornar um status não-2xx ou estiver inacessível, o sistema tenta novamente automaticamente:
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.
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:
username, name, profile_pic, follower_count, is_verified_userfirst_name; last_name e profile_pic quando disponíveis na MetaWhatsApp 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.frompela primeira vez, depois cacheie o resultado do seu lado — não há necessidade de chamar a cada mensagem.
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.
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.
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.
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.