Commit Graph

37 Commits

Author SHA1 Message Date
Rodribm10
b561aa8451 revert(hermes): remove camadas 2/3/4 + reabilita memória
A causa raiz dos bugs de "info repetida em turns anteriores" era o
default_scope ASC do Message conflitando com .order(desc) no debounce
(ver commit f1d3a124d). Como já corrigi com .reorder, as Camadas 2, 3 e
4 viraram peso morto que adicionava latência/false positive sem ganho.

Removido:
- Camada 2 (factual sem tool → retrigger force_factual_tool)
- Camada 3 (strip de linhas repetidas com pool de outgoings anteriores)
- Camada 4 (topic gating: bloqueio quando resposta tem tópico não pedido)
- Tracker de tool calls em McpController (suportava Camada 2)
- Snapshot baseline em OutgoingJob (suportava Camada 2)
- Regra "🚨 NÃO CONFIE NA SUA MEMÓRIA" das 4 SOUL.md Hermes

Mantido:
- Camada 1: handoff intencional ("Um momento — vou verificar") +
  loop detection (Jaccard >= 0.50 ou pergunta reformulada com 3+
  keywords). Genuíno pra bot externo (Claro/Vivo) e loops óbvios.
- Label-guard em OutgoingJob (não dispatch se conv tem triagem_humana).
- Auto-react ambient (feature original).
- Reorder fix no combined_incoming_content (causa raiz).

Memory + user_profile reabilitados nos 4 Hermes (config.yaml) e no
template do hermes-provision pra futuros agentes. Sem memória, cliente
precisa repetir nome/CPF/contexto a cada turn — UX horrível.
Contaminação cross-unit que justificava desligar vinha de outro bug
(X-Captain-Assistant-Id apontando pro parent), já corrigido.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 18:39:43 -03:00
Rodribm10
ebf98c90f4 feat(captain/hermes): camada 4 — topic gating contra info não pedida
Camada 3 (strip de linhas repetidas) não cobre quando LLM mistura tópico
antigo + atual numa LINHA SÓ (caso real: cliente pergunta sobre pet,
Hermes responde "A senha é Prime2025 e pode levar animais sim" —
overlap baixo, strip não dispara).

Camada 4 detecta tópicos factuais (Wi-Fi/senha/pet/estacionamento/preço/
cancelamento) presentes NA RESPOSTA mas AUSENTES da última pergunta do
cliente. Quando detectado, bloqueia entrega + dispara
[SISTEMA: force_topic_focus] no Hermes mandando responder
EXCLUSIVAMENTE sobre o tópico atual. 1 retry; persistindo, entrega.

Validado: cliente "Posso levar animais?" + resposta "Senha + pet" →
detecta [:wifi] como off-topic. Cliente "Qual senha wifi?" + resposta
"Senha é X" → vazio (passa normal).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 18:22:41 -03:00
Rodribm10
cc58805722 feat(captain/hermes): camada 3 — strip de linhas repetidas + check FAQs
LLM tende a "resumir" info de turns anteriores em toda nova resposta.
Camada 3 strip linhas onde >=70% das palavras significativas já apareceram
nas últimas 3 outgoings (filtra reactions). Saudações curtas preservadas.

Caso real Juliana 2026-05-02 (turn 3 ela ia repetir "Senha Prime2025 +
pet" mesmo cliente só dizendo "valeu"). Após strip: vira só "Imagina,
Rodrigo 😊".

Validator UI: novo check "FAQs próprias aprovadas > 0" — alerta quando
zero (faq_lookup cai no parent, risco de info desatualizada igual ao
bug do X-Captain-Assistant-Id que vazou Wi-Fi do parent hoje cedo).

Filtro SQL `content_attributes ->> 'external_source'` não casava (coluna
json, não jsonb); migrado pra filtro Ruby.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 18:16:39 -03:00
Rodribm10
ed99f67525 feat(captain/hermes): camada 2 — gating de saída factual sem tool call
Detecta alucinação de memória: se resposta do Hermes contém info
factual (preço/senha/horário/regra/política) E o LLM NÃO chamou
nenhuma tool MCP entre dispatch e callback, bloqueia entrega + dispara
system_message forçando consulta a tool. 1 retry; persistindo, escala.

Implementação:
- McpController: incrementa Rails.cache hermes_tool_calls:<conv_id>
  em cada tools/call.
- OutgoingJob: snapshot do contador como hermes_tool_calls_baseline
  ANTES de despachar pro Hermes.
- HermesCallbackController.gate_factual_no_tool!: compara baseline vs
  current; se igual + FACTUAL_PATTERNS bate, intercepta. Patterns
  cobrem R$, %, "senha", check-in/out + horário, política de
  cancelamento, "permitido", "pode levar pet/animal".

Caso real: cliente pede senha do Wi-Fi → Hermes responde de cabeça
"é passada presencialmente" sem chamar faq_lookup → callback intercepta,
não entrega pro cliente, manda [SISTEMA: force_factual_tool] pro Hermes
com instrução de chamar faq_lookup. Se faq_lookup vier vazio → frase-
âncora handoff.

Auto-react ambient: removido filtro de "?" que barrava em prod.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 17:58:17 -03:00
Rodribm10
c960dc7e1e feat(captain/hermes): handoff por loop + label-guard em outgoing
Substitui o interceptor agressivo de "Um momento — vou verificar"
(que bloqueava silenciosamente) por handoff explícito + loop detection:

- HANDOFF_PATTERNS: detecta a frase-âncora ("Um momento — vou
  verificar", "Aguarde um instante") e ENTREGA pro cliente,
  marcando conv com label triagem_humana.
- looped_response?: detecta 2 outgoing similares (Jaccard >= 0.50)
  OU pergunta reformulada sobre mesmo tópico (>= 3 keywords em comum
  + ambas inquisitivas via "?" / "me confirma" / "qual prefere"
  etc). 1ª resposta passa, 2ª escala. Cobre o caso "endereço ou
  link?" → "apenas link ou link + endereço?".
- OutgoingJob: guard que pula dispatch se conv tem label
  triagem_humana ou hermes_placeholder. Hermes não responde mais →
  não gasta token + não gera loop.

Cobre 2 casos do Rodrigo:
1. Bot da Claro insistindo em menu → 2ª resposta similar escala.
2. Hermes pedindo confirmação 2x sem entregar → escala.

Tokenize normaliza acentos (transliterate) pra stopwords baterem
"voce/você", "endereco/endereço", etc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 17:30:58 -03:00
Rodribm10
c8785b999c fix(captain/hermes): intercepta placeholder e força tool call
Quando o LLM emite " Um momento — vou verificar." (ou variantes:
"deixa eu verificar", "aguarde um instante") sem chamar nenhuma tool,
o callback agora:

1. NÃO entrega a msg pro cliente (UX terrível ver "vou verificar" e
   ficar esperando indefinidamente).
2. Dispara notify_event pro Hermes com [SISTEMA: force_tool_call]
   instruindo a retomar e chamar a tool relevante (generate_pix,
   send_suite_images, faq_lookup) com base na última msg do cliente.
3. Limita 2 retries por conversation via Rails.cache (TTL 5min). Após
   esgotar, marca labels hermes_placeholder + triagem_humana e descarta.

Caso real do Rodrigo: cliente confirmou reserva ("Para hoje 23h por 4h")
e o LLM respondeu apenas o placeholder (api_calls=1 no daemon, sem
tool). Cliente ficava esperando sem resposta. Agora Captain força o
LLM a chamar a tool, ou cai pra triagem humana após 2 tentativas.

PLACEHOLDER_PATTERNS cobre as variações observadas. SKILL.md já proibia
"Um momento", mas o LLM ignorava — defesa em camadas.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 17:01:18 -03:00
Rodribm10
ed21722dc4 fix(captain/hermes): callback aceita ?slug= além de ?inbox_id=
Hermes daemon faz POST /webhooks/captain/hermes_callback?slug=<profile>
mas controller só conhecia ?inbox_id. 404 → resposta do LLM nunca chegava
ao Captain. Cliente via só auto-react.

Fix: fetch_inbox resolve via Captain::Assistant.find_by(hermes_profile_name)
quando slug está presente. Inbox é a primeira CaptainInbox associada a
esse assistant. Suporta o pattern admin de re-apontar uma inbox de teste
(ex: Angelina) entre vários agentes Hermes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 13:53:12 -03:00
Rodribm10
5f6aed05c9 fix(captain/hermes-builder): callback resolve session via last_session
Bug: Hermes nao propaga chat_id no metadata do callback. Callback
controller nao conseguia resolver qual session armazenar a resposta.
WARN "no session_key resolvable — ignorando" descartava todas as
respostas do Construtor.

Fix: HermesBuilder::Storage.remember_last_session() grava ultima
session por account quando admin chama /start ou /create. Callback
le essa key via last_session_for(account_id). MVP-safe pra 1 admin
por conta.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 22:53:43 -03:00
Rodribm10
40fd0c8f50 feat(captain/hermes-builder): UI Vue + endpoints pra chat com Construtor
Tela "Construtor" no painel Captain (acessivel em sidebar pra admins) que
permite criar novo agente Hermes via chat guiado com agente Construtor
(profile Hermes separado).

Backend (admin scope):
- POST /api/v1/accounts/:id/captain/hermes_builder — manda mensagem do
  admin pro gateway do Construtor (Hermes na porta 8646)
- GET — retorna historico da sessao (Rails.cache, TTL 4h)
- DELETE /reset — limpa sessao
- POST /webhooks/captain/builder_callback — recebe respostas async do
  Construtor via plugin captain-http-callback do Hermes
- HermesBuilder::Storage (Rails.cache) — persiste msgs por session_key
  (account_id + user_id) com role/content/created_at
- HermesBuilder::Dispatcher — encaminha pro webhook do Construtor com
  HMAC opcional via ENV HERMES_BUILDER_WEBHOOK_SECRET

Frontend:
- Pagina Vue HermesBuilder/Index.vue — chat simples com:
  * Lista de mensagens com bubbles user/construtor
  * Indicador "digitando..." enquanto aguarda resposta
  * Input com Enter pra enviar / Shift+Enter pra nova linha
  * Polling 2s pra novas msgs
  * Botao Limpar conversa
- API client em api/captain/hermesBuilder.js
- Rota captain_hermes_builder_index (admin only)
- Item no sidebar Captain "Construtor (Hermes)"
- i18n keys CAPTAIN.HERMES_BUILDER em pt_BR + en

UX flow:
  Admin abre tela → digita "olá" → Construtor pergunta nome → admin
  responde → marca, persona, tabela (com opcao copiar de existente),
  regras, FAQs, identidade → resumo → confirmar → Construtor chama
  save_agent_spec → JSON salvo em /tmp/agent-specs/<slug>.json pra
  revisao posterior. NAO cria filesystem do profile nem registros DB
  (etapa SEPARADA, prox sessao).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 21:00:41 -03:00
Rodribm10
48fad2977b feat(captain/hermes): payload enriquecido + humanizadores + notif Pix proativa
Captain::Hermes::Client (enterprise/app/services/captain/hermes/client.rb):
- text_for_hermes: transcreve audio via Whisper antes de enviar pro Hermes
  (reusa Captain::OpenAiMessageBuilderService)
- image_urls_for_hermes: URLs publicas de imagens da message; plugin
  captain-webhook do Hermes baixa em /tmp/ e popula event.media_urls pra
  vision multimodal (gpt-4o-mini auxiliary)
- contact_history_snapshot: dados eager pro [ctx] (last_reservation_*,
  total_conversations, ultima_suite, etc) — memoria do contato direto no
  prompt sem precisar tool call
- notify_event + build_event_payload: dispara webhook sintetico pro Hermes
  pra eventos do sistema (Pix pago etc) — Valentina manda mensagem
  espontanea sem cliente perguntar

Captain::Payments::ConfirmationService:
- Hook notify_hermes_proactively! enfileira NotifyPaymentConfirmedJob
  apos confirmacao de Pix, somente se inbox estiver no fluxo Hermes
  (Captain interno continua igual sem mudanca)

Captain::Hermes::NotifyPaymentConfirmedJob (NOVO):
- Monta system_message "[SISTEMA: pagamento_confirmado]\n..." e dispara
  webhook pro Hermes Valentina
- Valentina (via SOUL.md) interpreta como evento do Captain e manda
  mensagem celebrativa pro cliente

Captain::Hermes::DelayedReplyJob (NOVO) — humanizadores:
- Liga indicador "digitando..." (composing) via wuzapi
- Aguarda delay configuravel via Captain::Assistant.config['response_delay']
  (modos: none, fixed, typing_simulation com chars_per_second + min/max)
- Posta msg outgoing
- Desliga typing
- Fallback no HermesCallbackController posta direto se class nao carregada

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:15:50 -03:00
Rodribm10
9ed3491d55 feat(captain/mcp): suite de 9 tools MCP + pricing tables Dolce Amore
Tools novas em enterprise/app/services/captain/mcp/tools/:
- generate_pix: Pix Inter via PricingTable + fallback link reserva-1001
- check_pix_payment: consulta Inter + dispara ConfirmationService
- send_suite_images: fotos da galeria (Captain::GalleryItem) via wuzapi
- reschedule_reservation: remarca reserva (regra 3h antecedência Dolce)
- update_contact: persiste nome/CPF/email/notas no Contact
- get_contact_history: markdown do histórico do cliente on-demand
- react_to_message: reage com emoji via wuzapi (is_reaction=true)

Captain::Mcp::PricingTables: tabela hardcoded Dolce Amore (8 categorias x 4
periodos + regras de pessoa extra). Backend e fonte de verdade — LLM nao
inventa preco.

add_label_tool: cria Label oficial automaticamente se nao existir, aceita
conversation_id em arguments.

mcp_controller: aceita X-Captain-Account-Id/Assistant-Id/Inbox-Id como
fallback de contexto.

tool_registry: 9 tools ativas.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:13:16 -03:00
Rodribm10
713bb16012 feat(captain): MCP controller aceita Bearer token além de HMAC
Hermes Agent (cliente MCP) usa `--auth header` que envia
`Authorization: Bearer <token>` — padrão MCP. Antes o Captain MCP server
só aceitava HMAC-SHA256 (X-Hub-Signature-256), incompatível com o que
Hermes manda nativamente.

Agora aceita qualquer um dos 2 modos:
- Bearer token (recomendado, padrão MCP) — Hermes envia automaticamente
- HMAC-SHA256 do body — pra clientes que preferem assinar payload

Ambos validados com ActiveSupport::SecurityUtils.secure_compare contra
o mesmo CAPTAIN_MCP_SECRET. Sem secret = validação desabilitada (PoC/dev).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:42:57 -03:00
Rodribm10
23911ea878 feat(captain): MCP server (HTTP) expondo tools pro Hermes Agent
Implementa servidor MCP (Model Context Protocol) HTTP no Captain pra
o Hermes Agent invocar tools do Captain via `hermes mcp add`. Substrato
pra integração de Nível 2 onde o agente consulta tools quando precisa
executar ações reais (buscar FAQ, adicionar label, futuramente Pix etc).

Arquivos:

- app/controllers/webhooks/captain/mcp_controller.rb
  Endpoint POST /webhooks/captain/mcp. Valida HMAC (CAPTAIN_MCP_SECRET),
  parseia JSON-RPC, despacha pro Server. Extrai params._captain_context
  com multi-tenant ids (conversation_id, inbox_id, account_id, etc).

- enterprise/app/services/captain/mcp/server.rb
  Subset MCP suficiente: initialize, tools/list, tools/call, ping,
  notifications/initialized. JSON-RPC síncrono (sem SSE).

- enterprise/app/services/captain/mcp/tool_registry.rb
  Lista centralizada de tools.

- enterprise/app/services/captain/mcp/tools/base_tool.rb
  Interface base pras tools (.name, .description, .input_schema, #call).

- enterprise/app/services/captain/mcp/tools/add_label_tool.rb
  Tool 1: aplica label na conversation atual.

- enterprise/app/services/captain/mcp/tools/faq_lookup_tool.rb
  Tool 2: busca semântica em FAQs (Captain::AssistantResponse via pgvector
  cosine). Reaproveita SearchReplyDocumentationService pra paridade com
  o caminho legado do Captain.

- config/routes.rb
  Rota POST /webhooks/captain/mcp.

Conexão pelo Hermes:
  hermes mcp add captain-tools --url http://CAPTAIN_HOST/webhooks/captain/mcp

Auth: HMAC X-Hub-Signature-256 quando CAPTAIN_MCP_SECRET setado.

TODO próxima sprint: generate_pix_tool, send_suite_images_tool. Handoff
fica via automation hoje (UI Chatwoot).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:32:38 -03:00
Rodribm10
cd519a73c4 fix(captain): converte markdown bold pra formato WhatsApp no callback Hermes
Hermes (e LLMs default em geral) emitem **negrito** no formato markdown
padrão. WhatsApp usa formato próprio: *negrito* (single asterisk). Sem
conversão, o cliente vê asteriscos literais no WhatsApp, parecendo bug.

Defesa em camadas:
1. SOUL.md da Valentina foi atualizado com regra explícita de formato
   WhatsApp (single asterisk pra bold, underscore pra itálico, etc).
2. Este controller faz normalização defensiva no callback recebido do
   Hermes: regex `**texto**` -> `*texto*` antes de criar a mensagem
   outgoing. Não afeta o resto do conteúdo.

normalize_for_whatsapp() é trivialmente reversível e idempotente
(executar 2x é igual a 1x).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:24:57 -03:00
Rodribm10
35de8b7fde feat(captain): cliente Captain ↔ Hermes (outgoing job + callback endpoint)
Implementa o lado Captain da integração Nível 2 (Hermes como cérebro).
Ativação por inbox via env var CAPTAIN_HERMES_INBOX_IDS — inboxes não
listadas seguem usando o orquestrador interno do Captain (Daniela_Reservas
etc) sem mudança alguma. Princípio "só adiciona, não retira".

Componentes:

- enterprise/app/services/captain/hermes.rb
  Módulo helper de config (env vars, URLs, secrets per-inbox).

- enterprise/app/services/captain/hermes/client.rb
  Service que monta payload (msg + contexto da conversa/inbox/contato) e
  faz POST autenticado via HMAC-SHA256 (X-Hub-Signature-256) no webhook
  do Hermes Agent (porta 8644). DispatchError em falha de rede/HTTP.

- enterprise/app/jobs/captain/hermes/outgoing_job.rb
  Wrapper Sidekiq do Client. Retry 3x em DispatchError.

- app/controllers/webhooks/captain/hermes_callback_controller.rb
  Recebe callback do plugin captain-http-callback do Hermes. Valida HMAC
  se CAPTAIN_HERMES_CALLBACK_SECRET setado, identifica conversation pela
  última pending da inbox (janela 5min) e cria mensagem outgoing.

- config/routes.rb
  Rota POST /webhooks/captain/hermes_callback (fora de /api/v1/accounts).

- enterprise/app/services/enterprise/message_templates/hook_execution_service.rb
  Branch novo no schedule_captain_response: se Hermes habilitado pra inbox,
  dispara HermesOutgoingJob; senão, fluxo Captain interno como antes.

Env vars (todas opcionais; sem set = Hermes desabilitado em todas inboxes):
- CAPTAIN_HERMES_INBOX_IDS (CSV de inbox.id)
- CAPTAIN_HERMES_WEBHOOK_BASE_URL (default http://172.17.0.1:8644)
- CAPTAIN_HERMES_CALLBACK_SECRET (HMAC validar callbacks de entrada)
- CAPTAIN_HERMES_SUBSCRIPTION_SECRET_INBOX_<id> (HMAC assinar saídas)

Limitação: identificação da conversation no callback usa última pending
da inbox dentro de 5min. OK pra PoC com 1 conversa de teste por vez. Em
produção, melhorar mapeando delivery_id ↔ conversation_id em Redis.

Próximo passo manual (admin VPS): criar subscription no Hermes:
  hermes webhook subscribe captain-inbox-1 \\
    --prompt 'Cliente disse: {message}. Responda como Daniela ...' \\
    --deliver http_callback \\
    --deliver-chat-id 'http://CAPTAIN_HOST/webhooks/captain/hermes_callback?inbox_id=1'

Depois set CAPTAIN_HERMES_INBOX_IDS=1 + CAPTAIN_HERMES_SUBSCRIPTION_SECRET_INBOX_1
no stack do Captain e testar pela inbox Angelina.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:22:22 -03:00
Rodrigo Borba
ccc1bdf35f Fix Wuzapi webhook handling 2026-02-26 10:49:00 -03:00
Rodrigo Borba
58f5ae6157 Fix Wuzapi webhook handling 2026-02-26 10:10:09 -03:00
Rodrigo Borba
0e7dc282c4 chore(style): fix rubocop offenses and update typing indicators 2026-02-25 15:06:58 -03:00
gabrieljablonski
9a4c5058f3 Merge branch 'main' into chore/merge-upstream-4.11.0 2026-02-17 23:05:26 -03:00
Muhsin Keloth
e75e8a77f6
feat(shopify): Add mandatory compliance webhooks with HMAC verification (#13549)
Fixes
https://linear.app/chatwoot/issue/CW-6494/add-shopify-mandatory-compliance-webhooks-for-app-store-listing

Shopify requires all public apps to handle three GDPR compliance
webhooks before they can be listed on the App Store. Their automated
review checks for these endpoints and verifies that apps validate HMAC
signatures on incoming requests. We were failing both checks.

This PR adds a single webhook endpoint at `POST /webhooks/shopify` that
receives all three compliance events. When Shopify sends a webhook, it
signs the payload with our app's client secret and includes the
signature in the `X-Shopify-Hmac-SHA256` header. Our controller reads
the raw body, computes the expected HMAC-SHA256 digest, and rejects
mismatched requests with a 401.

Shopify identifies the event type through the `X-Shopify-Topic` header.
For `customers/data_request` and `customers/redact`, we simply
acknowledge with a 200—Chatwoot doesn't persist any Shopify customer
data. All order lookups happen as live API calls at query time. For
`shop/redact`, which Shopify sends after a merchant uninstalls the app,
we delete the integration hook for that shop domain and remove the
stored access token and configuration.


### How to test via Rails console
```
secret = GlobalConfigService.load('SHOPIFY_CLIENT_SECRET', nil)
body = '{"shop_domain":"test.myshopify.com"}'
valid_hmac = Base64.strict_encode64(OpenSSL::HMAC.digest('SHA256', secret, body))
```

  #### Test 1: No HMAC → 401
```
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Topic' => 'customers/data_request' }
app.response.code  # => "401"
```
  ####  Test 2: Invalid HMAC → 401
```
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => 'invalid', 'X-Shopify-Topic' => 'customers/data_request' }
app.response.code  # => "401"
```
  ####  Test 3: Valid HMAC, customers/data_request → 200
```
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'customers/data_request' }
app.response.code  # => "200"
```

####  Test 4: Valid HMAC, customers/redact → 200
```
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'customers/redact' }
app.response.code  # => "200"
```

#### Test 5: Valid HMAC, shop/redact → 200 (deletes hook)
```  
# First check if a hook exists for this domain:
Integrations::Hook.where(app_id: 'shopify', reference_id: 'test.myshopify.com').count
app.post '/webhooks/shopify', params: body, headers: { 'Content-Type' => 'application/json', 'X-Shopify-Hmac-SHA256' => valid_hmac, 'X-Shopify-Topic' => 'shop/redact' }
app.response.code  # => "200"
```

---------

Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
2026-02-17 16:52:13 +05:30
gabrieljablonski
549214e96d Merge branch main into chore/merge-upstream 2025-12-20 12:44:31 -03:00
Mazen Khalil
ca5e112a8c
feat: TikTok channel (#12741)
fixes: #11834

This pull request introduces TikTok channel integration, enabling users
to connect and manage TikTok business accounts similarly to other
supported social channels. The changes span backend API endpoints,
authentication helpers, webhook handling, configuration, and frontend
components to support TikTok as a first-class channel.


**Key Notes**
* This integration is only compatible with TikTok Business Accounts
* Special permissions are required to access the TikTok [Business
Messaging
API](https://business-api.tiktok.com/portal/docs?id=1832183871604753).
* The Business Messaging API is region-restricted and is currently
unavailable to users in the EU.
* Only TEXT, IMAGE, and POST_SHARE messages are currently supported due
to limitations in the TikTok Business Messaging API
* A message will be successfully sent only if it contains text alone or
one image attachment. Messages with multiple attachments or those
combining text and attachments will fail and receive a descriptive error
status.
* Messages sent directly from the TikTok App will be synced into the
system
* Initiating a new conversation from the system is not permitted due to
limitations from the TikTok Business Messaging API.


**Backend: TikTok Channel Integration**

* Added `Api::V1::Accounts::Tiktok::AuthorizationsController` to handle
TikTok OAuth authorization initiation, returning the TikTok
authorization URL.
* Implemented `Tiktok::CallbacksController` to handle TikTok OAuth
callback, process authorization results, create or update channel/inbox,
and handle errors or denied scopes.
* Added `Webhooks::TiktokController` to receive and verify TikTok
webhook events, including signature verification and event dispatching.
* Created `Tiktok::IntegrationHelper` module for JWT-based token
generation and verification for secure TikTok OAuth state management.

**Configuration and Feature Flags**

* Added TikTok app credentials (`TIKTOK_APP_ID`, `TIKTOK_APP_SECRET`) to
allowed configs and app config, and registered TikTok as a feature in
the super admin features YAML.
[[1]](diffhunk://#diff-5e46e1d248631a1147521477d84a54f8ba6846ea21c61eca5f70042d960467f4R43)
[[2]](diffhunk://#diff-8bf37a019cab1dedea458c437bd93e34af1d6e22b1672b1d43ef6eaa4dcb7732R69)
[[3]](diffhunk://#diff-123164bea29f3c096b0d018702b090d5ae670760c729141bd4169a36f5f5c1caR74-R79)

**Frontend: TikTok Channel UI and Messaging Support**

* Added `TiktokChannel` API client for frontend TikTok authorization
requests.
* Updated channel icon mappings and tests to include TikTok
(`Channel::Tiktok`).
[[1]](diffhunk://#diff-b852739ed45def61218d581d0de1ba73f213f55570aa5eec52aaa08f380d0e16R16)
[[2]](diffhunk://#diff-3cd3ae32e94ef85f1f2c4435abf0775cc0614fb37ee25d97945cd51573ef199eR64-R69)
* Enabled TikTok as a supported channel in contact forms, channel
widgets, and feature toggles.
[[1]](diffhunk://#diff-ec59c85e1403aaed1a7de35971fe16b7033d5cd763be590903ebf8f1ca25a010R47)
[[2]](diffhunk://#diff-ec59c85e1403aaed1a7de35971fe16b7033d5cd763be590903ebf8f1ca25a010R69)
[[3]](diffhunk://#diff-725b90ca7e3a6837ec8291e9f57094f6a46b3ee00e598d16564f77f32cf354b0R26-R29)
[[4]](diffhunk://#diff-725b90ca7e3a6837ec8291e9f57094f6a46b3ee00e598d16564f77f32cf354b0R51-R54)
[[5]](diffhunk://#diff-725b90ca7e3a6837ec8291e9f57094f6a46b3ee00e598d16564f77f32cf354b0R68)
* Updated message meta logic to support TikTok-specific message statuses
(sent, delivered, read).
[[1]](diffhunk://#diff-e41239cf8dda36c1bd1066dbb17588ae8868e56289072c74b3a6d7ef5abdd696R23)
[[2]](diffhunk://#diff-e41239cf8dda36c1bd1066dbb17588ae8868e56289072c74b3a6d7ef5abdd696L63-R65)
[[3]](diffhunk://#diff-e41239cf8dda36c1bd1066dbb17588ae8868e56289072c74b3a6d7ef5abdd696L81-R84)
[[4]](diffhunk://#diff-e41239cf8dda36c1bd1066dbb17588ae8868e56289072c74b3a6d7ef5abdd696L103-R107)
* Added support for embedded message attachments (e.g., TikTok embeds)
with a new `EmbedBubble` component and updated message rendering logic.
[[1]](diffhunk://#diff-c3d701caf27d9c31e200c6143c11a11b9d8826f78aa2ce5aa107470e6fdb9d7fR31)
[[2]](diffhunk://#diff-047859f9368a46d6d20177df7d6d623768488ecc38a5b1e284f958fad49add68R1-R19)
[[3]](diffhunk://#diff-c3d701caf27d9c31e200c6143c11a11b9d8826f78aa2ce5aa107470e6fdb9d7fR316)
[[4]](diffhunk://#diff-cbc85e7c4c8d56f2a847d0b01cd48ef36e5f87b43023bff0520fdfc707283085R52)
* Adjusted reply policy and UI messaging for TikTok's 48-hour reply
window.
[[1]](diffhunk://#diff-0d691f6a983bd89502f91253ecf22e871314545d1e3d3b106fbfc76bf6d8e1c7R208-R210)
[[2]](diffhunk://#diff-0d691f6a983bd89502f91253ecf22e871314545d1e3d3b106fbfc76bf6d8e1c7R224-R226)

These changes collectively enable end-to-end TikTok channel support,
from configuration and OAuth flow to webhook processing and frontend
message handling.


------------

# TikTok App Setup & Configuration
1. Grant access to the Business Messaging API
([Documentation](https://business-api.tiktok.com/portal/docs?id=1832184145137922))
2. Set the app authorization redirect URL to
`https://FRONTEND_URL/tiktok/callback`
3. Update the installation config with TikTok App ID and Secret
4. Create a Business Messaging Webhook configuration and set the
callback url to `https://FRONTEND_URL/webhooks/tiktok`
([Documentation](https://business-api.tiktok.com/portal/docs?id=1832190670631937))
. You can do this by calling
`Tiktok::AuthClient.update_webhook_callback` from rails console once you
finish Tiktok channel configuration in super admin ( will be automated
in future )
5. Enable TikTok channel feature in an account

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
2025-12-17 07:54:50 -08:00
gabrieljablonski
801033bd5f Merge branch 'main' into chore/merge-upstream-4.5.0 2025-08-20 11:20:31 -03:00
Muhsin Keloth
87313ecc35
fix: Add delay to instagram/messenger echo events to prevent duplicate messages (#12032)
- Add 2-second delay to Facebook Messenger echo event processing to
prevent race condition
- Add 2-second delay to Instagram echo event processing for consistency
- Prevent duplicate messages when echo events arrive before send message
API completes processing

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2025-07-24 21:11:02 +04:00
Cayo P. R. Oliveira
76deea996d
chore: refactor incoming_message_baileys_service using composition and fix some failure points (#53)
* feat: implement connection update handling for Baileys

* feat: add message update handling for Baileys integration

* feat: implement message processing and handling for Baileys integration

* fix: clear message source ID from Redis when contact is not found

* fix: raise error when attachment file is not found during media handling

* refactor: reorganize includes in incoming_message_baileys_service

* feat: add helper methods for message handling in Baileys integration

* feat: include IncomingMessageServiceHelpers in MessagesUpdate module

* refactor: replace IncomingMessageServiceHelpers with BaileysHandlers::Helpers in connection_update, messages_update, and messages_upsert modules

* fix: mark message as unsupported when attachment file is not found

* refactor: remove unnecessary namespace for includes in IncomingMessageBaileysService

* refactor: add private visibility to methods in connection_update, helpers, messages_update, and messages_upsert modules

* refactor: preserve original message in handle_edited_content method

* fix: attachment error handling

* feat: implement conversation creation logic in set_conversation method

* refactor: remove unused error handling for attachment not found in messages update and upsert

* feat: update last seen timestamps in conversation on message status update

* feat: log warning for unsupported message update status in Baileys service

* chore: merge

---------

Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>
2025-05-29 10:56:39 -03:00
Cayo P. R. Oliveira
8fc14186e0
feat: process Baileys attachments, fix contact name creation, and handle unsupported messages (#26)
* fix: return not_found status for missing messages in WhatsApp webhook

* feat: enhance message handling to support image attachments

* chore: Handle incoming whatsapp baileys service attachments and implement specs for images and videos

* fix: add file_content_type method to incoming baileys messages

* chore: specs for stickers, audio and files

* fix: handle media attachment errors

* chore: convert file_content_type to string when attaching media

* fix: add handling for attachment not found error in incoming message service

* chore: refactor file_content_type method and simplify filename method

* chore: update media attachment tests

* feat: baileys unsupported message alert (#27)

* feat: create alert message for unsupported message types

* chore: fail message when try send not supported type in baileys

* chore: add tests for unsupported message handling in Baileys service

* chore: correct spelling

* chore: NIT in spec name

* chore: remove unnecessary message reload in unsupported message type test

* feat: baileys support to send attachments (#28)

* feat: enhance message sending logic with support for attachments and interactive messages

* fix: update message format to use messageContent for text messages

* feat: attachment message sending

* fix: use strict encoding for attachment file download

* chore: streamline message sending logic and remove unused error classes

* chore: remove type from message sending logic

* chore: update message sending specs

* chore: raise MessageNotSentError instead of updating message status to failed

* chore: change baileys contact name preferences (#30)

* refactor: improve contact name handling and extraction from JID

* test: enhance message handling to update contact names based on pushName and verifiedBizName

* chore: update contact name condition to match phone number from JID

* chore: correct method name typo

* chore: correct variable names for phone number consistency in tests

* chore: update message payload structure and improve error handling in send_message

* test: re-add testing for error handling

* feat: enhance contact name handling and add self-message detection

* fix: ensure presence check for verified business name in contact name retrieval

* test: improve specs

---------

Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>

---------

Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>
Co-authored-by: Gabriel Jablonski <gabriel@fazer.ai>

---------

Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>
Co-authored-by: Gabriel Jablonski <gabriel@fazer.ai>

* fix: try to disconnect baileys before destroy inbox (#29)

* fix: ensure proper disconnection of Baileys provider on channel destruction

* test: add callback tests for disconnecting channel provider in Whatsapp spec

* refactor: simplify condition for disconnecting Baileys provider on channel destruction

* refactor: enhance disconnect_channel_provider specs for Baileys provider

* test: verify channel destruction does not call disconnect_channel_provider

---------

Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>

---------

Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>
Co-authored-by: Gabriel Jablonski <gabriel@fazer.ai>
2025-04-25 16:39:17 -03:00
Cayo P. R. Oliveira
25670564eb
feat: add message update processing in IncomingMessageBaileysService (#20)
* feat: add message update processing in IncomingMessageBaileysService

* feat: implement message update handling in IncomingMessageBaileysService

* feat: add MessageNotFoundError for handling invalid update messages in IncomingMessageBaileysService

* chore: specs for message.update events and bug fixes

* chore: refactor message update handling in IncomingMessageBaileysService

* chore: nit remove redundant comment

* chore: enhance logging for unsupported message update statuses in IncomingMessageBaileysService and disabled metrics

* chore: message status update logic with transition checks

* chore: update status mapping for PENDING to sent in status_mapper

* chore: update status_mapper comments and fix case statement for status codes

* fix: logging for unsupported message updates in update_message_content method

* test: add specs for unsupported status transitions in messages.update event

* refactor: ensure message status is reloaded before assertion in messages.update event spec

* refactor: status variable in status_mapper method

* refactor: rename status_transition_allowed method to status_transition_allowed?

* refactor: streamline message creation in specs by using let! for setup

* feat: process webhook whatsapp await response (#21)

* feat: enhance WhatsApp webhook processing and error responses handling

* chore: correct spelling of 'WhatsApp' in webhook controller specs

* refactor: rename webhook processing method and improve error handling

* chore: update error handling in WhatsApp controller specs for specific exceptions

* refactor: remove handling for StandardError in WhatsApp controller specs

* refactor: simplify perform_whatsapp_events_job method

* chore: update response status from not_found to bad_request for invalid message

* refactor: update expectations for job processing in WhatsApp controller specs
2025-04-09 19:14:38 -03:00
Muhsin Keloth
7a24672b66
feat: Added the ability to create Instagram channel (#11182)
This PR is part of https://github.com/chatwoot/chatwoot/pull/11054 to
make the review cycle easier.
2025-04-03 13:57:14 +05:30
Sojan Jose
29158e32fe
chore: Logger for non-existent WhatsApp channels (#11064)
- Add a warning logger for cases where we are getting webhook events for
inactive numbers.
- Add config to discard events for inactive numbers so that the meta
will stop sending events

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
2025-03-12 15:50:38 -07:00
Sojan Jose
a6c609f43d
feat: Support for Whatsapp Cloud API (#4938)
Ability to configure Whatsapp Cloud API Inboxes

fixes: #4712
2022-07-06 21:45:03 +02:00
Vishnu Narayanan
8155024b6a
feat: fix logging levels (#4314)
https://ruby-doc.org/stdlib-2.7.0/libdoc/logger/rdoc/Logger.html

Fixes https://github.com/chatwoot/chatwoot/issues/4313
2022-03-28 18:14:30 +05:30
Sojan Jose
cf10f3d03b
chore: Provider APIs for SMS Channel - Bandwidth (#3889)
fixes: #3888
2022-02-03 15:22:13 -08:00
Vishnu Narayanan
3a48e08fe0
feat: migrate facebook env vars to globalConfig (#3369)
Migrate facebook env vars to globalConfig and make it editable from the super admin UI.
2021-11-25 00:55:26 +05:30
Tejaswini Chile
5749d25ff8
feat: Update instagram webhooks URL (#3197) 2021-10-12 12:46:27 +05:30
Sojan Jose
bd7aeba484
chore: Provider API prototype (#3112)
Enabling Support for Whatsapp via 360Dialog as a prototype for the provider APIs. 

Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
2021-10-05 23:35:06 +05:30
Sojan Jose
0a38632f14
feat: Line Channel (#2904)
- Ability to configure line bots as a channel in chatwoot
- Receive a message sent to the line bot in chatwoot
- Ability to reply to line users from chatwoot

fixes: #2738
2021-09-11 01:31:17 +05:30
Sojan Jose
671c5c931f
feat: Telegram Channel (#2901)
- Ability to configure telegram bots as a channel in chatwoot
- Receive a message sent to the telegram bot in chatwoot
- Ability to reply to telegram users from chatwoot
- Receive attachment messages in chatwoot

fixes: #1843
2021-09-10 00:00:52 +05:30