Referência de payloads¶
Convenções¶
Exemplos abaixo usam nomes sintéticos, example.invalid e placeholders. São contratos para leitura, não ordens de envio real. Campos como <TELEFONE_FICTICIO> e <CPF_FICTICIO> precisam ser substituídos por fixtures locais apropriadas para passar validadores; placeholders deliberadamente não são documentos/telefones válidos.
| Fronteira | Fonte principal |
|---|---|
| WhatsApp inbound | src/core/integrations/whatsapp/adapter.py |
| WhatsApp outbound | src/core/integrations/whatsapp/cloud.py |
| Saída IA | src/core/application/parser.py, action_mapping.py |
| Sessão/mensagem/demanda | src/core/domain/ e src/core/integrations/dynamodb/serialization.py |
| CRM | src/core/integrations/hubspot/payloads.py |
| eGestor | src/core/integrations/egestor/payloads.py |
| Handoff | src/core/integrations/handoff/interface.py, email.py, whatsapp.py |
| Fila / sync | src/webhook_handler/app.py, src/core/integrations/dynamodb/crm_sync.py |
Inbound Meta e normalização¶
Envelope relevante para POST /webhook:
{
"object":"whatsapp_business_account",
"entry":[{
"id":"<WABA_ID>",
"changes":[{
"field":"messages",
"value":{
"messaging_product":"whatsapp",
"metadata":{"phone_number_id":"<PHONE_NUMBER_ID>"},
"messages":[{
"from":"<TELEFONE_SOMENTE_DIGITOS>",
"id":"wamid.EXEMPLO_001",
"timestamp":"1789387200",
"type":"text",
"text":{"body":"Quero informações sobre uma análise de amostra sintética."}
}]
}
}]
}]
}
extract_inbound_messages() percorre todas as entradas e mudanças, não apenas a primeira mensagem. _normalize_sender() retira não dígitos e prefixa +; sem dígitos lança ValueError. O ID original é preservado. Timestamp ausente/inválido usa UTC atual. Mídia produz caption quando existe ou [tipo]; tipo desconhecido é tratado como document. Nenhum binário é baixado por esse adapter.
O contrato interno produzido por WhatsAppCloudInboundMessage.to_internal_payload():
{
"phone_number":"<TELEFONE_FICTICIO_NORMALIZADO>",
"content":"Texto fictício",
"message_type":"text",
"instance":"gogenetic",
"provider_message_id":"wamid.EXEMPLO_001",
"received_at":"2026-09-14T12:00:00+00:00",
"phone_number_id":"<PHONE_NUMBER_ID>"
}
O adapter Cloud atual constrói a instância default gogenetic; não existe nesse adapter um mapeamento automático de phone_number_id para goyou. O payload interno/emulador pode carregar instance explicitamente. provider_message_id ausente na rota interna ganha UUID no webhook. Callbacks contendo só status não produzem entradas de cliente.
Outbound WhatsApp¶
WhatsAppClient.send_message(to=..., text=...) monta POST /{version}/{phone_number_id}/messages, com Authorization: Bearer <TOKEN> e JSON:
{
"messaging_product":"whatsapp",
"recipient_type":"individual",
"to":"<TELEFONE_SOMENTE_DIGITOS>",
"type":"text",
"text":{"preview_url":false,"body":"Mensagem fictícia de atendimento."}
}
O adapter extrai messages[0].id da resposta quando presente e retorna StubResponse. Falhas HTTP ou de transporte seguem a política em WhatsApp. O nome mock_id é compartilhado entre stub e clientes reais e pode conter ID real do provider.
Saída de agente e resultado LLM¶
{"s":1,"qualification_data":{"tipo_amostra":"amostra sintética"}}Quantas amostras você pretende analisar?
ParsedLLMResponse guarda o inteiro s, o objeto completo em json_part e o texto em message. O formato não é JSON puro: não aplique json.loads() à string inteira. Veja parser e reparo.
LLMCallResult tem raw_response: str, model: str, tokens_input: int, tokens_output: int, latency_ms: int. OpenAIClient transforma messages em input de responses.create, opcionalmente inclui reasoning.effort, lê output_text e usage. Não usa response_format/JSON Schema no request atual; o contrato é instruído e validado pela aplicação.
Session: campos persistidos¶
Session é dataclass mutável com identidade protegida. O serializer grava strings de enum e timestamps ISO; campos novos precisam manter compatibilidade com registros antigos. A chave DynamoDB/GSI é adicionada pelo repository, fora dos campos abaixo.
| Campos | Tipo / default | Significado |
|---|---|---|
session_id, phone_number |
string, obrigatórios | Identidade imutável |
whatsapp_instance |
gogenetic/goyou, obrigatório |
Marca/instância imutável |
created_at |
datetime UTC, obrigatório | Criação imutável |
state |
SessionState, NEW |
Estado de domínio |
current_agent |
AgentName ou null | Agente atribuído; não implica que está executando |
segment, intent |
Enums ou null | Classificação |
service_identified |
string ou null | Nome canônico |
qualification_data, cadastral_data |
objetos, {} |
Campos por merge |
collected_packages |
lista de strings, [] |
IDs já injetados |
context_enrichment_count |
inteiro, 0 | Contador resetado por inbound do bot |
updated_at, closed_at |
datetime; segundo opcional | Última mudança e encerramento |
handoff_reason |
HandoffReason ou null | Motivo do encaminhamento |
assigned_to |
telefone ou null | Operador que assumiu |
handoff_renotified_at |
datetime ou null | Última renotificação |
handoff_renotification_count |
inteiro, 0 | Quantidade de renotificações |
handoff_customer_renotified_at |
datetime ou null | Aviso correspondente ao cliente; serializer omite chave quando null |
egestor_synced, hubspot_synced |
booleanos, false | Flags históricas; CRM lido é enriquecido com projeção |
hubspot_contact_id, hubspot_deal_id |
string ou null | Identificadores externos |
crm_terminal_stage |
string ou null | Stage lógico terminal opcional |
egestor_payload, hubspot_payload |
objetos ou null | Payloads preparados/guardados |
operational_summary |
objeto, string legada ou null | Resumo humano estruturado |
previous_session_id |
string ou null, imutável | Vínculo entre sessões |
recognized_name |
string ou null | Nome reconhecido de sessão anterior |
pending_subject_change_text |
string ou null | Solicitação mantida enquanto pede confirmação |
retry_count |
inteiro, 0 | Falhas de validação cadastral |
Exemplo abreviado de snapshot de domínio:
{
"session_id":"sessao-exemplo",
"phone_number":"<TELEFONE_FICTICIO>",
"whatsapp_instance":"gogenetic",
"state":"DATA_COLLECT",
"current_agent":"coleta",
"segment":"PESQUISA",
"intent":"servico",
"service_identified":"Metagenoma_Shotgun",
"qualification_data":{"tipo_amostra":"amostra sintética"},
"cadastral_data":{"nome":"Pessoa Exemplo","email":"teste@example.invalid"},
"collected_packages":["SAMPLE_INSTRUCTIONS"],
"context_enrichment_count":1,
"created_at":"2026-09-14T12:00:00+00:00",
"updated_at":"2026-09-14T12:01:00+00:00",
"previous_session_id":null
}
Message Log e demanda¶
Message exige message_id, session_id, phone_number, direction, message_type, content, timestamp, state_before, state_after. Direções: INBOUND/OUTBOUND/SYSTEM. Tipos: text/image/audio/video/document/sticker/system.
Campos opcionais: debounced_with, llm_raw_response, llm_parsed_json, clean_message_sent, agent, operator_phone, provider_message_id, context_packages_active, llm_model, tokens_input, tokens_output, latency_ms, error. error="superseded" torna um outbound inadequado para replay/histórico IA. Campos de log de conteúdo são dados sensíveis de operação; a sanitização do logger não remove automaticamente conteúdo persistido no Message Log.
OutOfPortfolioDemand contém demand_id, session_id, phone_number, push_name, service_requested, estimated_segment, whatsapp_instance, original_message, created_at. from_triagem() cria UUID e horário. O serviço solicitado é texto livre e não precisa constar no catálogo oferecido.
CRM: contato e negócio¶
build_contact_payload(cadastral_data, segment, phone_number=None) gera:
{
"properties":{
"firstname":"Pessoa",
"lastname":"Exemplo",
"email":"teste@example.invalid",
"phone":"<TELEFONE_FICTICIO>",
"address":"Rua Exemplo, número fictício",
"city":"Cidade Exemplo",
"state":"PR",
"zip":"<CEP_FICTICIO>",
"segmento":"PESQUISA",
"origem_lead":"Agente Digital WhatsApp",
"cpf_cnpj":"<CPF_FICTICIO>"
}
}
Sem nome, firstname="Lead WhatsApp"; sem telefone cadastral, usa fallback do argumento. Propriedades podem ser null no builder; o cliente HTTP prepara a forma final conforme operação. A origem legada hubspot é compatibilidade de contrato; o host configurado pode ser o CRM GoGenetic.
build_deal_payload() produz propriedades e associação opcional:
{
"properties":{
"dealname":"Metagenoma_Shotgun — Pessoa Exemplo",
"pipeline":null,
"dealstage":null,
"hubspot_owner_id":null,
"segmento":"PESQUISA",
"origem_lead":"Agente Digital WhatsApp",
"servico_solicitado":"Metagenoma_Shotgun",
"descricao_solicitacao":"Solicitação fictícia de análise.",
"historico_conversa":"Resumo fictício para o operador.",
"qualification_data_json":"tipo_amostra=amostra sintética",
"telefone":"<TELEFONE_FICTICIO>",
"email":"teste@example.invalid",
"cpf_cnpj":"<CPF_FICTICIO>"
},
"associations":[{
"to":{"id":"contato-exemplo"},
"types":[{"associationCategory":"HUBSPOT_DEFINED","associationTypeId":3}]
}]
}
qualification_data_json é nome histórico: seu conteúdo é string chave=valor; ..., não JSON serializado. pipeline/owner são preenchidos pela configuração do cliente; stage depende da operação/projeção. Sem contact_mock_id, associations começa vazia. O worker é o escritor dos deals; consulte CRM para request HTTP final, lookup e idempotência.
eGestor: cliente¶
build_customer_payload(cadastral_data=...) gera o corpo atual de /contatos:
{
"nome":"Pessoa Exemplo",
"tipo":["cliente"],
"cpfcnpj":"<DOCUMENTO_FICTICIO_SOMENTE_DIGITOS>",
"emails":["teste@example.invalid"],
"fones":["<TELEFONE_FICTICIO>"]
}
cpfcnpj aceita origem cpfcnpj, cpfCnpj ou cpf_cnpj, nessa precedência, e remove não dígitos. Se nenhum dígito existir, a propriedade é omitida. nome é acessado como obrigatório; ausência pode gerar KeyError. emails/fones só são incluídos quando há valor. Endereço/cidade/UF/CEP coletados não são enviados por este builder atual. Não confunda o corpo implementado com uma especificação futura de API própria compatível.
Handoff e resumo operacional¶
HandoffNotifier.notify(session) recebe a própria Session, não um payload HTTP genérico. O notifier SES deriva Source/Destination/Message; o notifier WhatsApp monta texto com contexto e encaminha aos operadores disponíveis. operational_summary tem contrato próprio:
{
"motivo_encerramento":"pedido_cliente",
"servico_identificado":null,
"segmento":"PESQUISA",
"pontos_chave":["Cliente fictício solicitou contato humano"],
"dados_coletados":{},
"proxima_acao":"Ler o histórico e continuar o atendimento",
"resumo_texto":"A pessoa pediu atendimento humano para esclarecer a solicitação."
}
summary_text() aceita esse objeto, uma string legada ou null. O gerador faz normalização e fallback; as sete chaves estão presentes no objeto normalizado. Não usa o contrato de action codes.
StubResponse é comum aos adapters:
{"success":false,"mock_id":null,"detail":"falha_ficticia","error_kind":"transient"}
error_kind aceita transient, permanent, uncertain ou null. HandoffNotifyResult acrescenta notified, operator_phone, queue_position. Um retorno success=true, notified=false, queue_position=1 significa que o encaminhamento ficou na fila, não que o operador recebeu mensagem.
Receipts, SQS e projeção CRM¶
InboundTurn contém telefone, ID, conteúdo, recebido e instância. A mensagem SQS é apenas:
{"phone_number":"<TELEFONE_FICTICIO>","message_id":"wamid.EXEMPLO_001"}
Receipt DynamoDB adiciona ttl, agent_routed_at, dispatched_at, sync_claimed_at e completed_at conforme progresso. O marcador latest lista pending_message_ids. A Lambda retorna batchItemFailures com IDs SQS, não IDs WhatsApp.
Projeção CRM_SYNC#{session_id}/META guarda desired (stage, terminal, contact, deal), revision SHA-256, confirmed_revision, etag, contact_id, deal_id, creation_started, status, attempts, next_attempt_at, pending_since, timestamps e campos de lease/erro quando aplicáveis. Estados da projeção: PENDING/SYNCED/BLOCKED/UNCERTAIN. Esses estados não pertencem a SessionState.
O job src/crm_sync/app.py é agendado e ignora conteúdo de negócio do evento para buscar pendências na tabela. Retorna {"status":"disabled"} quando desabilitado ou {"status":"ok","metrics":{...}} com Pending, Blocked, Uncertain, OldestPendingSeconds. A intenção é durável no DynamoDB; não há uma segunda fila SQS de CRM no caminho atual.
Testes e evolução dos contratos¶
Fontes de verificação: tests/unit/integrations/test_whatsapp_adapter.py, test_whatsapp_cloud_client.py, test_dynamodb_repos.py, test_egestor_http_client.py, test_hubspot_http_client.py, tests/unit/application/test_parser.py, test_operational_summary.py, test_crm_sync.py e tests/unit/lambdas/test_crm_sync.py.
Quando mudar um payload, identifique quem constrói, quem serializa e quem lê registros anteriores. Adicione teste de round-trip para persistência, request capturado para HTTP e replay para saída de agente. Não substitua propriedades legadas por nomes novos sem atualizar todos os consumidores. Veja erros e arquitetura.