Skip to content

fix: adiciona timeout no login para evitar spinner infinito - #65

Open
marketingtopconta wants to merge 5 commits into
thaleslaray:mainfrom
marketingtopconta:claude/platform-access-analysis-3o1xh2
Open

fix: adiciona timeout no login para evitar spinner infinito#65
marketingtopconta wants to merge 5 commits into
thaleslaray:mainfrom
marketingtopconta:claude/platform-access-analysis-3o1xh2

Conversation

@marketingtopconta

@marketingtopconta marketingtopconta commented Jul 17, 2026

Copy link
Copy Markdown

Quando a requisição de login trava (bloqueio de rede/proxy/firewall antes de chegar ao servidor), o fetch nunca resolvia e o botão ficava girando para sempre sem feedback. Agora aborta após 15s e mostra uma mensagem explicando que não é senha incorreta.

Summary by CodeRabbit

  • Bug Fixes
    • Login now times out after 15 seconds and shows clearer messaging when the request times out or appears blocked.
    • Improved inbox webhook handling to extract media text more reliably and ignore events for other phone numbers.
  • New Features
    • Inbox now renders WhatsApp-style media (images/videos/documents with captions) and supports inline audio playback.
    • When media can’t be retrieved, the chat shows a media-unavailable warning instead of missing content.

Quando a requisição de login trava (bloqueio de rede/proxy/firewall
antes de chegar ao servidor), o fetch nunca resolvia e o botão ficava
girando para sempre sem feedback. Agora aborta após 15s e mostra uma
mensagem explicando que não é senha incorreta.
@vercel

vercel Bot commented Jul 17, 2026

Copy link
Copy Markdown

@claude is attempting to deploy a commit to the Thales Laray Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The login form now cancels /api/auth/login requests after 15 seconds and shows a dedicated timeout message. WhatsApp media is filtered by configured phone number, downloaded to durable storage, associated with Inbox messages, and rendered by media type.

Changes

Login timeout handling

Layer / File(s) Summary
Add request timeout and error classification
app/(auth)/login/page.tsx
handleSubmit uses an AbortController with a 15-second timer, clears the timer after completion, and distinguishes timeout aborts from other login errors.

Inbound WhatsApp media

Layer / File(s) Summary
Add inbound media storage
lib/whatsapp/inbound-media.ts
Resolves Meta media identifiers, downloads binary content, stores it in Supabase Storage, and returns a durable public URL and MIME type.
Persist media messages and filter webhook events
app/api/webhook/route.ts
Loads WhatsApp credentials, ignores events for other phone numbers, extracts media captions and filenames, downloads inbound media, and passes stored URLs to Inbox persistence.
Render media in the Inbox
components/features/inbox/MessageBubble.tsx
Detects image, video, audio, and document messages and renders their media, captions, or unavailable-state messaging.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MetaWebhook
  participant MetaGraphAPI
  participant SupabaseStorage
  participant Inbox
  MetaWebhook->>MetaGraphAPI: validate phone_number_id and resolve media_id
  MetaGraphAPI-->>MetaWebhook: return media binary and MIME type
  MetaWebhook->>SupabaseStorage: upload inbound media
  SupabaseStorage-->>MetaWebhook: return durable media URL
  MetaWebhook->>Inbox: persist message with media URL and caption
  Inbox-->>MetaWebhook: render media message in MessageBubble
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed O título descreve corretamente a principal mudança visível, o timeout no login para evitar spinner infinito.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

app/api/webhook/route.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

lib/whatsapp/inbound-media.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/(auth)/login/page.tsx (1)

13-13: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract logic to keep the page component thin.

LoginForm contains a significant amount of state and data-fetching logic. Consider extracting this logic into a custom hook (e.g., useLogin) or moving the LoginForm component to a separate file in the components/ directory to improve separation of concerns.

As per path instructions, app/**/{page,layout}.tsx files should be thin components that only connect hooks to views.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`(auth)/login/page.tsx at line 13, Extract the state and data-fetching
logic from LoginForm into a dedicated useLogin hook or a separate component
under components/, leaving the app page component responsible only for
connecting the hook or component to the rendered view. Preserve the existing
login behavior and UI while keeping page.tsx thin.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/`(auth)/login/page.tsx:
- Line 13: Extract the state and data-fetching logic from LoginForm into a
dedicated useLogin hook or a separate component under components/, leaving the
app page component responsible only for connecting the hook or component to the
rendered view. Preserve the existing login behavior and UI while keeping
page.tsx thin.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 51898a5c-30d9-47d0-9b1d-91de35a35f75

📥 Commits

Reviewing files that changed from the base of the PR and between 17c69e2 and 4405714.

📒 Files selected for processing (1)
  • app/(auth)/login/page.tsx

…Inbox

A Meta não envia URL de mídia no payload do webhook, só um media_id — o
código anterior tentava ler message.image?.url (sempre undefined) e o
MessageBubble não tinha renderização nenhuma para tipos de mídia.

- lib/whatsapp/inbound-media.ts: resolve o media_id via Graph API, baixa
  o binário com o access token e sobe pro bucket whatsapp-inbound-media
  (criado automaticamente, mesmo padrão do wa-template-media).
- app/api/webhook/route.ts: chama o download antes de persistir a
  mensagem no Inbox; extractInboundText agora também captura caption e
  filename de mídia.
- MessageBubble.tsx: renderiza imagem (thumbnail clicável), vídeo e
  áudio (player nativo) e documento (card de download), com fallback
  visual quando o download da mídia falha.

Só afeta mensagens recebidas a partir de agora — mídia de mensagens
antigas não pode ser recuperada (URL temporária da Meta já expirou).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/features/inbox/MessageBubble.tsx`:
- Around line 317-384: Update MediaContent so database fallback values such as
“[image]”, “[video]”, “[audio]”, and “[document]” are treated as an empty
caption before rendering. Preserve genuine captions and existing media behavior,
including the document label fallback.

In `@lib/whatsapp/inbound-media.ts`:
- Line 10: Replace the custom supabase import with getSupabaseAdmin and update
ensureBucket, the guard clause, and the upload/public-URL logic to obtain and
use the admin client’s storage API. Ensure all server-side bucket checks,
uploads, and URL generation go through the client returned by getSupabaseAdmin,
avoiding direct access to the wrapper’s nonexistent storage property.
- Around line 72-85: Add AbortSignal.timeout() to both fetch calls in the
inbound media flow: the metadata request and the binary download using
metaBody.url. Set a timeout safely below Meta’s 15-second webhook deadline, and
preserve the existing graceful null-return behavior when either fetch fails or
is aborted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 220c4d62-874c-4a02-baf0-bbe1ceaa90e6

📥 Commits

Reviewing files that changed from the base of the PR and between 4405714 and dc3be61.

📒 Files selected for processing (3)
  • app/api/webhook/route.ts
  • components/features/inbox/MessageBubble.tsx
  • lib/whatsapp/inbound-media.ts

Comment on lines +317 to +384
function MediaContent({
type,
mediaUrl,
caption,
}: {
type: 'image' | 'audio' | 'video' | 'document'
mediaUrl: string | null
caption: string
}) {
if (!mediaUrl) {
return (
<div className="flex items-center gap-2 text-sm text-[var(--ds-text-muted)] bg-black/10 rounded-lg px-3 py-2">
<ImageOff className="h-4 w-4 flex-shrink-0" />
<span>Mídia indisponível</span>
</div>
)
}

if (type === 'image') {
return (
<div className="flex flex-col gap-1.5">
<a href={mediaUrl} target="_blank" rel="noopener noreferrer">
<img
src={mediaUrl}
alt={caption || 'Imagem'}
loading="lazy"
className="max-w-[280px] max-h-[320px] w-auto rounded-lg object-cover cursor-zoom-in"
/>
</a>
{caption && (
<p className="text-base leading-relaxed whitespace-pre-wrap break-words">
<WhatsAppFormattedText text={caption} />
</p>
)}
</div>
)
}

if (type === 'video') {
return (
<div className="flex flex-col gap-1.5">
<video controls src={mediaUrl} className="max-w-[280px] max-h-[320px] rounded-lg" />
{caption && (
<p className="text-base leading-relaxed whitespace-pre-wrap break-words">
<WhatsAppFormattedText text={caption} />
</p>
)}
</div>
)
}

if (type === 'audio') {
return <audio controls src={mediaUrl} className="h-10 max-w-[260px]" />
}

// document
return (
<a
href={mediaUrl}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2.5 bg-black/10 hover:bg-black/20 transition-colors rounded-lg px-3 py-2.5 max-w-[260px]"
>
<FileText className="h-5 w-5 flex-shrink-0" />
<span className="text-sm truncate flex-1">{caption || 'Documento'}</span>
<Download className="h-3.5 w-3.5 flex-shrink-0 opacity-70" />
</a>
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Prevent the database fallback text from rendering as a visual caption.

If an inbound media message has no caption, the webhook persistence logic writes a fallback string like [image], [video], [audio], or [document] to the database content field. Currently, this raw string is passed as the caption prop and visually rendered to the user below the media.

Strip this fallback string so that it isn't rendered as an ugly caption.

✨ Proposed fix
 function MediaContent({
   type,
   mediaUrl,
   caption,
 }: {
   type: 'image' | 'audio' | 'video' | 'document'
   mediaUrl: string | null
   caption: string
 }) {
+  // Avoid rendering the database fallback text as an actual caption
+  const displayCaption = caption === `[${type}]` ? '' : caption || ''
+
   if (!mediaUrl) {
     return (
       <div className="flex items-center gap-2 text-sm text-[var(--ds-text-muted)] bg-black/10 rounded-lg px-3 py-2">
         <ImageOff className="h-4 w-4 flex-shrink-0" />
         <span>Mídia indisponível</span>
       </div>
     )
   }

   if (type === 'image') {
     return (
       <div className="flex flex-col gap-1.5">
         <a href={mediaUrl} target="_blank" rel="noopener noreferrer">
           <img
             src={mediaUrl}
-            alt={caption || 'Imagem'}
+            alt={displayCaption || 'Imagem'}
             loading="lazy"
             className="max-w-[280px] max-h-[320px] w-auto rounded-lg object-cover cursor-zoom-in"
           />
         </a>
-        {caption && (
+        {displayCaption && (
           <p className="text-base leading-relaxed whitespace-pre-wrap break-words">
-            <WhatsAppFormattedText text={caption} />
+            <WhatsAppFormattedText text={displayCaption} />
           </p>
         )}
       </div>
     )
   }

   if (type === 'video') {
     return (
       <div className="flex flex-col gap-1.5">
         <video controls src={mediaUrl} className="max-w-[280px] max-h-[320px] rounded-lg" />
-        {caption && (
+        {displayCaption && (
           <p className="text-base leading-relaxed whitespace-pre-wrap break-words">
-            <WhatsAppFormattedText text={caption} />
+            <WhatsAppFormattedText text={displayCaption} />
           </p>
         )}
       </div>
     )
   }

   if (type === 'audio') {
     return <audio controls src={mediaUrl} className="h-10 max-w-[260px]" />
   }

   // document
   return (
     <a
       href={mediaUrl}
       target="_blank"
       rel="noopener noreferrer"
       className="flex items-center gap-2.5 bg-black/10 hover:bg-black/20 transition-colors rounded-lg px-3 py-2.5 max-w-[260px]"
     >
       <FileText className="h-5 w-5 flex-shrink-0" />
-      <span className="text-sm truncate flex-1">{caption || 'Documento'}</span>
+      <span className="text-sm truncate flex-1">{displayCaption || 'Documento'}</span>
       <Download className="h-3.5 w-3.5 flex-shrink-0 opacity-70" />
     </a>
   )
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function MediaContent({
type,
mediaUrl,
caption,
}: {
type: 'image' | 'audio' | 'video' | 'document'
mediaUrl: string | null
caption: string
}) {
if (!mediaUrl) {
return (
<div className="flex items-center gap-2 text-sm text-[var(--ds-text-muted)] bg-black/10 rounded-lg px-3 py-2">
<ImageOff className="h-4 w-4 flex-shrink-0" />
<span>Mídia indisponível</span>
</div>
)
}
if (type === 'image') {
return (
<div className="flex flex-col gap-1.5">
<a href={mediaUrl} target="_blank" rel="noopener noreferrer">
<img
src={mediaUrl}
alt={caption || 'Imagem'}
loading="lazy"
className="max-w-[280px] max-h-[320px] w-auto rounded-lg object-cover cursor-zoom-in"
/>
</a>
{caption && (
<p className="text-base leading-relaxed whitespace-pre-wrap break-words">
<WhatsAppFormattedText text={caption} />
</p>
)}
</div>
)
}
if (type === 'video') {
return (
<div className="flex flex-col gap-1.5">
<video controls src={mediaUrl} className="max-w-[280px] max-h-[320px] rounded-lg" />
{caption && (
<p className="text-base leading-relaxed whitespace-pre-wrap break-words">
<WhatsAppFormattedText text={caption} />
</p>
)}
</div>
)
}
if (type === 'audio') {
return <audio controls src={mediaUrl} className="h-10 max-w-[260px]" />
}
// document
return (
<a
href={mediaUrl}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2.5 bg-black/10 hover:bg-black/20 transition-colors rounded-lg px-3 py-2.5 max-w-[260px]"
>
<FileText className="h-5 w-5 flex-shrink-0" />
<span className="text-sm truncate flex-1">{caption || 'Documento'}</span>
<Download className="h-3.5 w-3.5 flex-shrink-0 opacity-70" />
</a>
)
function MediaContent({
type,
mediaUrl,
caption,
}: {
type: 'image' | 'audio' | 'video' | 'document'
mediaUrl: string | null
caption: string
}) {
// Avoid rendering the database fallback text as an actual caption
const displayCaption = caption === `[${type}]` ? '' : caption || ''
if (!mediaUrl) {
return (
<div className="flex items-center gap-2 text-sm text-[var(--ds-text-muted)] bg-black/10 rounded-lg px-3 py-2">
<ImageOff className="h-4 w-4 flex-shrink-0" />
<span>Mídia indisponível</span>
</div>
)
}
if (type === 'image') {
return (
<div className="flex flex-col gap-1.5">
<a href={mediaUrl} target="_blank" rel="noopener noreferrer">
<img
src={mediaUrl}
alt={displayCaption || 'Imagem'}
loading="lazy"
className="max-w-[280px] max-h-[320px] w-auto rounded-lg object-cover cursor-zoom-in"
/>
</a>
{displayCaption && (
<p className="text-base leading-relaxed whitespace-pre-wrap break-words">
<WhatsAppFormattedText text={displayCaption} />
</p>
)}
</div>
)
}
if (type === 'video') {
return (
<div className="flex flex-col gap-1.5">
<video controls src={mediaUrl} className="max-w-[280px] max-h-[320px] rounded-lg" />
{displayCaption && (
<p className="text-base leading-relaxed whitespace-pre-wrap break-words">
<WhatsAppFormattedText text={displayCaption} />
</p>
)}
</div>
)
}
if (type === 'audio') {
return <audio controls src={mediaUrl} className="h-10 max-w-[260px]" />
}
// document
return (
<a
href={mediaUrl}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2.5 bg-black/10 hover:bg-black/20 transition-colors rounded-lg px-3 py-2.5 max-w-[260px]"
>
<FileText className="h-5 w-5 flex-shrink-0" />
<span className="text-sm truncate flex-1">{displayCaption || 'Documento'}</span>
<Download className="h-3.5 w-3.5 flex-shrink-0 opacity-70" />
</a>
)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/features/inbox/MessageBubble.tsx` around lines 317 - 384, Update
MediaContent so database fallback values such as “[image]”, “[video]”,
“[audio]”, and “[document]” are treated as an empty caption before rendering.
Preserve genuine captions and existing media behavior, including the document
label fallback.

Source: Linked repositories

Comment thread lib/whatsapp/inbound-media.ts Outdated
Comment on lines +72 to +85
const metaRes = await fetch(`https://graph.facebook.com/v24.0/${mediaId}`, {
headers: { Authorization: `Bearer ${credentials.accessToken}` },
})
if (!metaRes.ok) {
console.warn(`[InboundMedia] Falha ao resolver media_id ${mediaId}: HTTP ${metaRes.status}`)
return null
}
const metaBody = (await metaRes.json()) as { url?: string; mime_type?: string }
if (!metaBody.url) return null

// 2. Baixa o binário usando o mesmo access token
const fileRes = await fetch(metaBody.url, {
headers: { Authorization: `Bearer ${credentials.accessToken}` },
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Add a timeout to Meta API fetches to prevent webhook hangs.

The Meta Webhook requires a 200 OK response within 15 seconds. A slow network response or large media download from the Graph API could block the request thread, causing the webhook to timeout and Meta to blindly retry the event.

Add an AbortSignal.timeout() to guarantee the fetches fail fast enough for the webhook to gracefully degrade (by saving the message without media) rather than failing the entire webhook execution.

🛡️ Proposed fix to enforce timeouts
     // 1. Resolve o media_id em uma URL temporária + mime_type
     const metaRes = await fetch(`https://graph.facebook.com/v24.0/${mediaId}`, {
       headers: { Authorization: `Bearer ${credentials.accessToken}` },
+      signal: AbortSignal.timeout(10000)
     })
     if (!metaRes.ok) {
       console.warn(`[InboundMedia] Falha ao resolver media_id ${mediaId}: HTTP ${metaRes.status}`)
       return null
     }
     const metaBody = (await metaRes.json()) as { url?: string; mime_type?: string }
     if (!metaBody.url) return null

     // 2. Baixa o binário usando o mesmo access token
     const fileRes = await fetch(metaBody.url, {
       headers: { Authorization: `Bearer ${credentials.accessToken}` },
+      signal: AbortSignal.timeout(10000)
     })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const metaRes = await fetch(`https://graph.facebook.com/v24.0/${mediaId}`, {
headers: { Authorization: `Bearer ${credentials.accessToken}` },
})
if (!metaRes.ok) {
console.warn(`[InboundMedia] Falha ao resolver media_id ${mediaId}: HTTP ${metaRes.status}`)
return null
}
const metaBody = (await metaRes.json()) as { url?: string; mime_type?: string }
if (!metaBody.url) return null
// 2. Baixa o binário usando o mesmo access token
const fileRes = await fetch(metaBody.url, {
headers: { Authorization: `Bearer ${credentials.accessToken}` },
})
const metaRes = await fetch(`https://graph.facebook.com/v24.0/${mediaId}`, {
headers: { Authorization: `Bearer ${credentials.accessToken}` },
signal: AbortSignal.timeout(10000),
})
if (!metaRes.ok) {
console.warn(`[InboundMedia] Falha ao resolver media_id ${mediaId}: HTTP ${metaRes.status}`)
return null
}
const metaBody = (await metaRes.json()) as { url?: string; mime_type?: string }
if (!metaBody.url) return null
// 2. Baixa o binário usando o mesmo access token
const fileRes = await fetch(metaBody.url, {
headers: { Authorization: `Bearer ${credentials.accessToken}` },
signal: AbortSignal.timeout(10000),
})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/whatsapp/inbound-media.ts` around lines 72 - 85, Add
AbortSignal.timeout() to both fetch calls in the inbound media flow: the
metadata request and the binary download using metaBody.url. Set a timeout
safely below Meta’s 15-second webhook deadline, and preserve the existing
graceful null-return behavior when either fetch fails or is aborted.

claude added 3 commits July 17, 2026 22:50
O objeto supabase importado de lib/supabase é um facade (.from/.rpc)
sem propriedade .storage — só o SupabaseClient real retornado por
getSupabaseAdmin() tem acesso ao Storage API. Corrige o erro de build
"Property 'storage' does not exist" do deploy anterior.
O SDK do Supabase Storage retorna { error } em vez de lançar exceção —
o try/catch anterior não capturava esse erro, então createBucket falhava
silenciosamente e o bucket nunca era criado (confirmado: 0 buckets no
projeto). Causava "Bucket not found" no upload da mídia recebida.

Bucket whatsapp-inbound-media já foi criado manualmente para destravar
produção; este commit só corrige a visibilidade do erro pra próxima vez.
Uma WABA pode ter múltiplos números de telefone, cada um usado por uma
plataforma diferente (ex: SmartZap e outro sistema conectados à mesma
conta comercial da Meta). Sem filtrar por phone_number_id, este app
processava mensagens/status que eram na verdade de outro número —
causando mensagens do SmartZap aparecerem "vazadas" em outra plataforma
conectada à mesma WABA, e vice-versa.

Agora comparamos change.value.metadata.phone_number_id contra o
phoneNumberId configurado (getWhatsAppCredentials) e ignoramos (continue)
eventos de mensagens/status que não são deste número. Template status
updates continuam passando — são a nível de WABA, sem phone_number_id.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/api/webhook/route.ts (1)

984-985: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Include voice and sticker media types.

WhatsApp heavily uses voice notes (voice) and stickers (sticker), which have their own properties in the Meta webhook payload. If these are omitted, they will not be passed to the download handler, causing them to appear without their associated media in the Inbox.

🐛 Proposed fix
-            const mediaId =
-              message.image?.id || message.video?.id || message.audio?.id || message.document?.id || null
+            const mediaId =
+              message.image?.id || message.video?.id || message.audio?.id || message.voice?.id || message.document?.id || message.sticker?.id || null
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/webhook/route.ts` around lines 984 - 985, Update the mediaId
extraction in the webhook message handling flow to also check message.voice?.id
and message.sticker?.id, preserving the existing image, video, audio, document,
and null fallback behavior so these media types reach the download handler.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@app/api/webhook/route.ts`:
- Around line 984-985: Update the mediaId extraction in the webhook message
handling flow to also check message.voice?.id and message.sticker?.id,
preserving the existing image, video, audio, document, and null fallback
behavior so these media types reach the download handler.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e8ebb5bc-33e1-4677-bd48-8c1efb70a387

📥 Commits

Reviewing files that changed from the base of the PR and between dc3be61 and 65723c0.

📒 Files selected for processing (2)
  • app/api/webhook/route.ts
  • lib/whatsapp/inbound-media.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/whatsapp/inbound-media.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants