# What Happens When Media Messages Expire in Baileys and How Re‑upload Is Handled

> Learn how Baileys manages expired media messages, handling re-uploads for incoming and outgoing media according to WhatsApp Web's 24-hour rules.

- Repository: [WhiskeySockets/Baileys](https://github.com/WhiskeySockets/Baileys)
- Tags: deep-dive
- Published: 2026-08-01

---

**Baileys handles expired media messages through placeholder-resend requests for incoming media and automatic re‑upload with fresh credentials for outgoing media, all governed by WhatsApp Web's 24‑hour lifetime rules.**

The Baileys library implements the same media lifecycle semantics as the official WhatsApp Web client. When a media URL expires or becomes unreachable, the library executes different recovery strategies depending on whether the message is incoming or outgoing. This article examines the expiration checks, placeholder-resend mechanism, and re‑upload retry logic as implemented in the WhiskeySockets/Baileys codebase.

## Incoming Media Expiration: Placeholder Resend

When Baileys receives a media message with a stale or missing download URL, it initiates a **placeholder-resend request** to recover the content from the connected phone.

### When URLs Expire During Reception

Baileys detects expired media in [`src/Socket/messages-recv.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Socket/messages-recv.ts) (lines 1650‑1689). The logic triggers when `msg.message.imageMessage?.url` (or equivalent for video, audio, document, sticker) is falsy:

```typescript
// src/Socket/messages-recv.ts – placeholder resend logic
if (msg.message && !msg.message?.imageMessage?.url) {
  const cleanKey: proto.IMessageKey = {
    remoteJid: msg.key.remoteJid,
    fromMe: msg.key.fromMe,
    id: msg.key.id,
    participant: msg.key.participant,
  };

  const msgData: Partial<WAMessage> = {
    key: msg.key,
    messageTimestamp: msg.messageTimestamp,
    pushName: msg.pushName,
    participant: msg.participant,
    verifiedBizName: msg.verifiedBizName,
  };

  requestPlaceholderResend(cleanKey, msgData)
    .then(requestId => {
      if (requestId && requestId !== 'RESOLVED') {
        ev.emit('messages.update', [{
          key: msg.key,
          update: { messageStubParameters: [NO_MESSAGE_FOUND_ERROR_TEXT, requestId] },
        }]);
      }
    })
    .catch(err => logger.warn({ err, msgId: msg.key.id }, 'failed to request placeholder resend'));
}

```

The `requestPlaceholderResend` function sends a **PDO (placeholder data object)** to the device. The phone responds with a fresh media node containing updated `url`, `mediaKey`, and `directPath` values.

### Status Broadcasts: Hard 24‑Hour Expiry

Status (story) broadcasts follow stricter rules. Baileys computes message age using `unixTimestampSeconds() - messageTimestamp` and compares against `STATUS_EXPIRY_SECONDS` (defined in [`src/Defaults/index.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Defaults/index.ts) as 86,400 seconds / 24 hours). Messages exceeding this threshold are acknowledged and discarded without retry:

```typescript
// src/Socket/messages-recv.ts – lines 1697‑1707
if (isStatusBroadcast && messageAge > STATUS_EXPIRY_SECONDS) {
  // Skip expired status messages entirely
  await sendMessageAck(msg);
  return;
}

```

## Outgoing Media: Re‑upload on Failure

When sending media, Baileys generates temporary upload credentials that may expire before transmission completes. The library implements **automatic re‑upload with fresh key derivation**.

### Upload Flow and Retry Mechanism

The `sendMediaMessage` pattern in [`src/Socket/messages-send.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Socket/messages-send.ts) coordinates with utilities in [`src/Utils/messages-media.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/messages-media.ts):

```typescript
// Simplified re‑upload pattern for outgoing media
async function sendMediaMessage(
  media: WAMediaUpload,
  type: MediaType,
  logger?: ILogger,
) {
  // 1️⃣  Extract raw file data and compute SHA-256 hash
  const { filePath, fileSha256, fileLength } = await getRawMediaUploadData(
    media, 
    type, 
    logger
  );

  // 2️⃣  Upload to WhatsApp media server via HTTP
  const uploadResp = await uploadWithNodeHttp({
    filePath,
    fileSha256,
    fileLength,
    mediaType: type,
    logger,
  });

  // 3️⃣  Construct message with fresh credentials
  const mediaMessage = {
    url: uploadResp.url,
    mediaKey: uploadResp.mediaKey,
    directPath: uploadResp.directPath,
    // …additional fields
  };

  // 4️⃣  Retry on server-side rejection
  try {
    await sock.sendMessage(targetJid, { [type + 'Message']: mediaMessage });
  } catch (e) {
    logger?.warn({ e }, 'Media upload failed, retrying...');
    return sendMediaMessage(media, type, logger); // Fresh upload attempt
  }
}

```

### Key Functions in the Re‑upload Pipeline

| Function | Location | Purpose |
|----------|----------|---------|
| `getRawMediaUploadData` | [`src/Utils/messages-media.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/messages-media.ts) | Prepares file stream, computes `fileSha256`, returns temporary path |
| `getMediaKeys` | [`src/Utils/messages-media.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/messages-media.ts) | Derives HKDF-based encryption keys from `mediaKey` |
| `uploadWithNodeHttp` | [`src/Utils/messages-media.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/messages-media.ts) | Executes HTTPS POST to WhatsApp's media server |
| `uploadWithFetch` | [`src/Utils/messages-media.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Utils/messages-media.ts) | Alternative fetch-based uploader |

If the server rejects an upload (HTTP 404, auth failure, or expired URL), the catch block re-invokes the entire pipeline—generating new keys, new hashes, and a new upload URL.

## Media Key Handling and Decryption Failures

Media keys are time-sensitive credentials tied to specific upload sessions. Baileys derives decryption keys using HKDF in `getMediaKeys`:

```typescript
// src/Utils/messages-media.ts – key derivation
function getMediaKeys(mediaKey: Buffer, mediaType: MediaType) {
  const expanded = hkdf(mediaKey, 112, { info: `WhatsApp Media Keys ${mediaType}` });
  return {
    iv: expanded.slice(0, 16),
    cipherKey: expanded.slice(16, 48),
    macKey: expanded.slice(48, 80),
  };
}

```

When decryption fails due to missing or malformed keys, Baileys falls back to the **placeholder-resend flow** described earlier, treating the message as expired and requesting fresh credentials from the phone.

## Logging and Observability

All expiration and retry events flow through the structured logger:

- **Debug**: Successful placeholder request initiation
- **Warn**: Failed placeholder resend, upload retry attempts
- **Error**: Unexpected decryption failures or unrecoverable errors

The `messages.update` event emitted during placeholder resend allows applications to track pending media recovery via the `requestId` in `messageStubParameters`.

## Summary

- **Incoming expired media**: Baileys sends a placeholder-resend request to the phone via `requestPlaceholderResend` in [`src/Socket/messages-recv.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Socket/messages-recv.ts), caching metadata and awaiting fresh credentials.

- **Status broadcasts older than 24 hours**: Automatically skipped using `STATUS_EXPIRY_SECONDS` check; no recovery attempted.

- **Outgoing failed uploads**: The entire upload pipeline restarts with fresh key derivation in `getMediaKeys` and new HTTP upload via `uploadWithNodeHttp`.

- **Decryption failures**: Trigger the same placeholder-resend mechanism as expired URLs.

## Frequently Asked Questions

### How long does WhatsApp allow media messages to remain accessible?

WhatsApp enforces a **24‑hour lifetime** for status broadcast media, defined as `STATUS_EXPIRY_SECONDS` in [`src/Defaults/index.ts`](https://github.com/WhiskeySockets/Baileys/blob/main/src/Defaults/index.ts). Standard chat media follows similar server-side expiration rules, though Baileys relies on the phone to provide fresh URLs via placeholder resend rather than enforcing a hard client-side cutoff.

### Can I disable automatic re‑upload for outgoing media?

No. The retry logic is embedded in the send path and triggers on any upload failure. To implement custom behavior, wrap `sendMessage` calls and intercept specific error codes before the built-in retry executes.

### What causes a placeholder-resend request to fail?

Failure occurs when the phone is offline, the original message was deleted on the device, or the message exceeds WhatsApp's server-side retention window. The promise rejects and Baileys logs a warning with the message ID; no further automatic recovery is attempted.

### Does Baileys cache media keys between sessions?

No. Media keys are derived per-message using HKDF and are not persisted. Each placeholder-resend or re‑upload generates fresh keys, ensuring cryptographic isolation between upload attempts.