Skip to content

S3 enabled: messageContextInfo guard aborts the whole upsert pipeline - no MESSAGES_UPSERT webhook for media (v2.3.7) #2768

Open

Description

@cnemer21

Environment

  • Evolution API v2.3.7 (Docker evoapicloud/evolution-api:latest)
  • S3 storage enabled (Cloudflare R2, S3-compatible endpoint, S3_SKIP_POLICY=true)
  • Channel: WhatsApp Baileys, multiple instances on the same server
  • DB: PostgreSQL, Redis cache (CACHE_REDIS_SAVE_INSTANCES=false)

Summary

Since enabling S3_ENABLED=true, media messages that reach an instance either (a) wrapped as a messageContextInfo-only payload, or (b) whose getBase64FromMediaMessage fails, no longer emit messages.upsert webhooks at all. The message row is still saved to the database, but the webhook is never fired. With S3 disabled, the same flows emit webhooks normally — the affected code path only runs when S3 is enabled.

Two user-visible scenarios (both reproduced):

  1. Document sent via API (POST /message/sendMedia): the receiving instance receives the message as a messageContextInfo-only wrapper → guard returns → no S3 upload and no webhook. The receiver's integration never sees the message.
  2. Media sent from the linked phone: the sending instance's own fromMe echo → getBase64FromMediaMessage fails → no upload and no webhook. The sender's integration never registers the outgoing media (only messages.update receipts arrive).

Evidence (v2.3.7 logs, timestamps local)

Scenario 1 — document sent via API from instance A to instance B (B's webhook received nothing):

[instanceB] VERBOSE [ChannelStartupService] Message contains only messageContextInfo, skipping media processing
[instanceB] VERBOSE [ChannelStartupService] No valid media to upload (messageContextInfo only), skipping MinIO
  • No S3 object created under instance B's prefix.
  • No p.sendData-Webhook block with event: 'messages.upsert' in the logs for B — only messages.update receipts from A.

Scenario 2 — photo sent from A's linked phone (A's webhook never got the fromMe upsert):

[instanceA] VERBOSE [ChannelStartupService] No valid media to upload (messageContextInfo only), skipping MinIO
  • The receiving instance processed the same message fine (S3 object uploaded + webhook delivered).
  • Instance A emitted only messages.update (SERVER_ACK / DELIVERY_ACK) — no messages.upsert.

Relevant code (v2.3.7, whatsapp.baileys.service.js)

n.message = n.message[d].message;
if ("messageContextInfo" in n.message && Object.keys(n.message).length === 1)
  return this.logger.verbose("Message contains only messageContextInfo, skipping media processing"), null;
...
const D = await this.getBase64FromMediaMessage({ message: b }, true);
if (!D) {
  this.logger.verbose("No valid media to upload (messageContextInfo only), skipping MinIO");
  return; // ← aborts the rest of the message processing, including webhook emission
}

The guard introduced by the 2.3.7 fix "MinIO Upload: Handle messageContextInfo in media upload to prevent MinIO errors" appears to early-return out of the whole upsert flow instead of skipping only the media upload. Because this code path only executes when S3 is enabled, enabling S3 silently drops messages.upsert webhooks for these messages.

Expected behavior

Skip (or defer) the S3 upload but still emit messages.upsert — e.g. without mediaUrl, or with the original message payload — so integrations keep receiving every message. Ideally, retry the media upload when the real payload arrives after decryption.

Steps to reproduce

  1. Two instances (different WhatsApp numbers), webhooks enabled for both, S3 enabled.
  2. POST /message/sendMedia (document via URL) from instance A to B's number → B's webhook receives nothing for the document (it arrives on the phone and is saved to B's DB).
  3. Send a photo from A's linked phone to B → A's webhook receives no fromMe upsert for the photo; B receives it normally.
  4. Disable S3 and repeat → webhooks flow again.

Workaround

None besides disabling S3 (restores the webhook flow) or synthesizing outgoing messages on the integration side from the API send response plus messages.update receipts.


Happy to provide more log excerpts or a live reproduction if needed. Thanks for the great project!

Activity

  1. cnemer21 commented on Oct 26, 2026

    @cnemer21
    Author

    Update from the reporter — we confirmed the root cause in production and worked around it with a local patch. Sharing the findings + fix in case it helps land an official fix.

    Confirmation of root cause

    The fatal early-return exists in two code paths (both inside dist's bundled Baileys service):

    1. messages.upsert handler (the main one): DB save happens first (message.create), then the S3 block runs, then the webhook is emitted (sendDataWebhook("messages.upsert", ...)). When getBase64FromMediaMessage returns falsy (wrapper-only message or download failure), the return skips the webhook entirely. Message is in the DB but integrations never see it.
    2. Send flow (sendMessageWithTyping, emits send.message): same pattern, same return.

    Additional detail that matters for anyone patching: in the published image, the executing code is the single bundle dist/main.js (entry: node dist/main). The files under dist/api/** are not loaded at runtime — patching those chunks has no effect.

    Suggested fix (what we applied, minified equivalent shown)

    Make a failed media extraction skip only the upload, never the rest of the flow:

    // BEFORE (both occurrences)
    if (!media) { this.logger.verbose("No valid media to upload (messageContextInfo only), skipping MinIO"); return; }
    // ...upload using media...
    
    // AFTER
    if (!media) { this.logger.verbose("No valid media to upload (messageContextInfo only), skipping MinIO"); }
    else { /* ...upload using media... */ }

    Resulting behavior (validated in production, 2 instances, multiple media types):

    Scenario Before After
    Media sent from linked phone → sender instance fromMe echo no messages.upsert upsert emitted (no mediaUrl)
    Document/image sent via API → receiving instance no messages.upsert upsert emitted with full documentMessage/imageMessage payload (no mediaUrl when extraction fails)
    Normal incoming media upsert + mediaUrl (upload works) unchanged

    No duplicate or empty-payload webhooks were observed: the guard only triggers for messages that already passed hasValidMediaContent, so the emitted payload always contains the real message content; wrapper-only protocol events keep their previous behavior (they never reach this path).

    Deployment note (local workaround until official fix)

    We ship the patched main.js via a read-only bind mount in docker-compose, with the base image pinned by digest, plus the original file kept alongside for rollback. Happy to test an official fix when available — happy to provide more evidence if useful.

  2. abitech-dev commented on Oct 26, 2026

    @abitech-dev

    Good

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions