Skip to main content

Overview

This guide covers uploading and downloading media (images, videos, audio, documents, stickers) with automatic encryption and decryption.

Downloading Media

From message objects

Download media directly from message types that implement Downloadable:
See Download API reference for full details.

Streaming Download

For large files, use streaming to avoid memory overhead:
See Download API reference for streaming details.
When the HTTP client supports streaming (like the default UreqHttpClient), downloads use ~40KB of memory regardless of file size (8KB buffer + decryption state). If the HTTP client doesn’t support streaming, download_to_writer automatically falls back to a buffered download — the file is fetched into memory first, then decrypted and written. This fallback ensures the method works with any HttpClient implementation.

From raw parameters

Download media when you have the raw encryption parameters:
See Download API reference for raw parameter downloads.

Streaming from raw parameters

See Download API reference for streaming from raw parameters.

Thumbnails

Thumbnails are not generated by the library — you provide them as raw JPEG bytes when constructing media messages. WhatsApp clients use these thumbnails as low-resolution previews while the full media is downloading.

Setting thumbnails on media messages

Set the jpeg_thumbnail field on image, video, or document messages:

Thumbnail fields by message type

Inline thumbnails (jpeg_thumbnail / png_thumbnail) are embedded directly in the encrypted message payload. Server-hosted thumbnail fields (thumbnail_direct_path, etc.) reference a separately uploaded thumbnail that clients can download independently. For most use cases, inline thumbnails are sufficient.

Reusing thumbnails from incoming messages

When re-uploading media (for example, after processing or transcoding), you can clone the thumbnail from the original message:
If you don’t need to modify the media, use CDN reuse instead — it forwards the entire message (including thumbnails) without downloading or re-uploading anything.
The LinkThumbnail variant in MediaType supports downloading thumbnails for URL previews in ExtendedTextMessage:

Forwarding media via CDN reuse

When you receive a media message (image, video, audio, document, sticker), you can “forward” it by reusing the original CDN fields directly — no download or re-upload needed. This is instant regardless of file size because no media bytes are transferred. The technique works because every media message contains all the CDN metadata (url, direct_path, media_key, file_sha256, etc.) needed for recipients to fetch the blob. You simply clone those fields into a new wa::Message and call send_message:
The same pattern works for any media type:
Only use download + upload when you need the plaintext bytes (processing, transcoding, re-encoding). To forward existing media unchanged, reuse the original message’s CDN fields directly.

When CDN reuse won’t work

CDN URLs expire after some time. If the original media URL has expired, recipients won’t be able to download it. In that case, use media reupload to request the server to provide a fresh download path, or fall back to downloading and re-uploading the media.

Uploading Media

Basic Upload

See Upload API reference for full details.

Using upload response in messages

The whatsapp_rust::media module turns an UploadResponse into a ready-to-send wa::Message, so you don’t hand-assemble the CDN/crypto fields (url, direct_path, media_key, the two SHA-256 hashes, file_length, media_key_timestamp, streaming_sidecar):
media::video_message, media::document_message, and media::audio_message work the same way with their own options structs (and carry the streaming sidecar for video/audio). See media message builders.
You can still assemble the proto yourself when you need a field the options structs don’t expose (e.g. explicit width/height):

Sending media as an album

You can group multiple images and/or videos into an album. See Sending Messages - Album messages for a full walkthrough of the parent/child album pattern using AlbumMessage and wrap_as_album_child.

Resumable uploads

For files 5 MiB or larger, the client automatically checks whether a previous upload of the same file exists on the CDN before uploading the full payload. This avoids re-uploading data that the server already has. The resume check flow works as follows:
  1. The client sends a ?resume=1 probe to the CDN for each host
  2. If the server responds with complete, the existing URL is returned immediately — no upload occurs
  3. If the server responds with a byte offset, only the remaining bytes are uploaded using the file_offset query parameter
  4. If the server responds with not found (or the check fails), a full upload proceeds
Resumable upload detection is fully transparent. You call client.upload() the same way regardless of file size. The resume check is non-fatal — if it fails for any reason, the client falls back to a full upload.

Media Types

Available media types

Media type properties

ProductCatalogImage is the only unencrypted media type. It uploads to /product/image instead of the /mms/ prefix used by other types. This matches WhatsApp Web’s behavior where CreateMediaKeys.js skips encryption for product catalog images.

Encryption Details

Upload Encryption

Media is automatically encrypted before upload:
  1. Generate random 32-byte media_key
  2. Derive encryption keys using HKDF-SHA256:
    • IV: 16 bytes
    • Cipher key: 32 bytes
    • MAC key: 32 bytes
  3. Encrypt with AES-256-CBC
  4. Append HMAC-SHA256 MAC (10 bytes)
  5. Compute SHA256 hashes of plaintext and ciphertext

Chunk-based encryption with MediaEncryptor

MediaEncryptor provides a chunk-based API for encrypting media incrementally. Unlike encrypt_media_streaming, it doesn’t require a sync Read source — you can feed plaintext in arbitrarily sized chunks, making it suitable for async streams and network sources:
You can also write directly to a file or writer instead of buffering in memory:
See Upload API reference for full details.

Streaming encryption from a reader

For sync Read sources, encrypt_media_streaming wraps MediaEncryptor and reads in 8KB chunks:
See Upload API reference for full details.

Download Decryption

Decryption is automatic when downloading:
  1. Fetch encrypted data from WhatsApp CDN
  2. Verify MAC (last 10 bytes)
  3. Decrypt with AES-256-CBC
  4. Verify plaintext SHA256
  5. Return decrypted data
Never skip SHA256 verification! The library automatically verifies both encrypted and decrypted hashes to ensure data integrity.

Error Handling

Download Errors

Upload Errors

Advanced Usage

Custom media processing

Streaming with Progress

Automatic retry and failover

Both download and upload methods automatically retry when WhatsApp’s CDN returns certain HTTP errors. The client handles three categories of errors: Auth errors (401/403): The client invalidates the cached MediaConn, fetches fresh credentials from WhatsApp’s servers, and retries the operation once with new auth tokens. Media not found (404/410): When a download URL has expired or the media has been moved, the CDN returns 404 or 410. The client treats this the same as an auth error — it invalidates the cached media connection, re-derives download URLs with fresh credentials and hosts, and retries once. This matches WhatsApp Web’s MediaNotFoundError handling. Other errors (e.g., 500): The client tries the next available CDN host without refreshing credentials. Media connections include multiple hosts sorted by priority (primary hosts first, then fallback hosts), and the client iterates through them sequentially.
This retry behavior is fully transparent — no changes are needed in your code. Both download and upload handle auth refresh, URL re-derivation, and host failover automatically, with a maximum of one credential refresh per operation.
For streaming downloads (download_to_writer), the writer is automatically seeked back to position 0 before each retry so partial writes don’t corrupt the output.

Additional retry logic

For transient errors beyond auth failures, you can implement your own retry wrapper:

Media connection

Automatic management

The client automatically manages media connection tokens, including refreshing them when they expire or when the CDN rejects them with 401/403/404/410:

Host types and failover order

Each media connection includes multiple CDN hosts with a host_type field indicating whether the host is "primary" or "fallback". The client sorts hosts with primary hosts first, so uploads and downloads always try the fastest hosts before falling back to alternatives. Each host may also include a fallback_hostname for additional resilience.

Token expiration

Media connections have two TTL values:
  • ttl — how long the route/host information is valid
  • auth_ttl — how long the auth token specifically is valid (may be shorter than ttl)
The client uses the minimum of these two values to determine when the cached connection has expired. When expired, the next media operation automatically fetches fresh credentials.

Internal refresh

Media connection refresh and invalidation are managed internally by the client. When the CDN rejects a request (401/403/404/410), the client automatically invalidates the cached connection and fetches fresh credentials before retrying. You don’t need to manage this manually.

Best Practices

Next Steps