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.

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.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 no WhatsApp depende das configurações de privacidade do destinatário — se as confirmações de leitura estiverem desativadas, o status read nunca chegará. Trate delivered como um estado terminal de sucesso.

Rastreamento de status de entrega

Quando você envia uma mensagem via /api/v1/messages/send, recebe um message_id (UUID). Todos os webhooks de status subsequentes referenciam esse mesmo UUID.

  • 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.

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 usam o mesmo bloco normalizado data.media. O remetente continua sendo data.from (IGSID para Instagram, PSID para Facebook), e data.type segue o tipo de anexo da Meta, como "image" ou "audio". Use data.type como o tipo primário da mensagem e como o media_type de saída ao ecoar ou encaminhar mídia.

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 60 minutos após o webhook — 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)

Com uma licença Starter, mensagens de mídia são entregues com type: "unsupported" e um campo upgrade_required indicando qual licença é necessária:

{
  "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"
  }
}

O campo upgrade_required informa qual plano de licença é necessário. Faça upgrade pela página Billing no portal para receber o conteúdo completo de mídia.

Observação: Mensagens com tipos realmente não suportados (ex.: location, contacts, reaction) ainda chegam como type: "unsupported" sem upgrade_required, independentemente do seu plano de licença.

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. Mensagens recebidas da Meta são sempre salvas do nosso lado, mesmo que o repasse falhe.

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
  • Facebookfirst_name, last_name, profile_pic

WhatsApp não é suportado (o nome já está no webhook). Os resultados são cacheados por 5 minutos — 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.

Documentação da API Fiwano