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.
With a streaming HTTP adapter and a file destination, download_to_writer keeps bounded crypto/read state. This excludes adapter/TLS buffers and destination storage. With default limits, non-streaming adapters can buffer the whole response before writing it. Explicit byte limits require streaming and refuse a non-streaming adapter before HTTP. See download limits.

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.

Downloading without an active session

Client::download and its variants need a connected Client for most media; they ask the server for CDN hosts (cached and refreshed automatically, not fetched on every call). The exception is static_url media (newsletter/channel content), which skips that entirely; see the Download API reference for details. If you’ve persisted a message’s CDN fields (for example as a DownloadParams) and want to fetch that media later, after the session that received it has disconnected, use MediaDownloader instead. It needs only an HttpClient and a Runtime; no Client, no pairing:
MediaDownloader uses the same streaming/buffered behavior and host failover as client downloads. It cannot refresh the host list through a session. A reference rejection returns a typed MediaDownloadError. Supply hosts with MediaRoute::new(hosts) or use the default-host convenience; download routes carry no auth token.

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 and NewsletterMusicArtwork are explicitly plaintext media types. For downloads of other types, key presence selects encryption, except MusicArtwork, which always requires it. Use DownloadParams::plaintext for stored newsletter metadata without a key. Product catalog images upload to /product/image.

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

Download errors redact opaque backend text in Display and Debug. Match typed variants instead of parsing diagnostic strings. Explicit access to the source chain can expose signed URLs; see safe diagnostics.

Upload Errors

Advanced Usage

Custom media processing

Streaming with Progress

When you pass a writer to download_to_writer, you must implement DownloadWriter, not just Write + Seek — see that reference for why. For a wrapper like this one, the impl is a one-line delegation to the inner writer, plus resetting your own tracked state to match:

Automatic retry and failover

For session-derived download routes, HTTP 401 can trigger one media-session refresh and URL re-derivation with refreshed hosts. HTTP 403/404/410 are terminal reference rejections; refreshing hosts cannot renew a signed reference. Download routes carry no auth token. Static URLs bypass session refresh, and MediaDownloader returns a reference rejection immediately because it has no session. Transport, HTTP, or integrity failures can fall over to another host. Local writer failures are terminal. Inspect the typed download errors and publish the destination only after success; cleanup after cancellation is not guaranteed. Upload retains its authentication token and credential-refresh behavior. See upload for that separate path.

Additional retry logic

For transient errors beyond reference rejections, you can implement your own retry wrapper:

Media connection

Automatic management

The client caches media connection information. Downloads use its CDN hosts; uploads also use its authentication token. Session-derived downloads can refresh hosts once after HTTP 401. HTTP 403/404/410 are terminal for downloads, while uploads refresh credentials on 401/403:

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.

Cache 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 session-derived media operation fetches fresh connection information. Download routes retain only its hosts.

Internal refresh

Media connection refresh and invalidation are managed internally by the client. Download refresh updates hosts and re-derives the URL without adding an auth token. Static download URLs bypass session refresh, and standalone MediaDownloader has no session to refresh. Upload credential refresh remains separate.

Best Practices

Next Steps