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 implementDownloadable:
Streaming Download
For large files, use streaming to avoid memory overhead: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: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 thejpeg_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:Downloading link thumbnails
TheLinkThumbnail 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:
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
Using upload response in messages
Thewhatsapp_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.
Building the message by hand
Building the message by hand
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 usingAlbumMessage 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:- The client sends a
?resume=1probe to the CDN for each host - If the server responds with complete, the existing URL is returned immediately — no upload occurs
- If the server responds with a byte offset, only the remaining bytes are uploaded using the
file_offsetquery parameter - 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:- Generate random 32-byte
media_key - Derive encryption keys using HKDF-SHA256:
- IV: 16 bytes
- Cipher key: 32 bytes
- MAC key: 32 bytes
- Encrypt with AES-256-CBC
- Append HMAC-SHA256 MAC (10 bytes)
- 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:
Streaming encryption from a reader
For syncRead sources, encrypt_media_streaming wraps MediaEncryptor and reads in 8KB chunks:
Download Decryption
Decryption is automatic when downloading:- Fetch encrypted data from WhatsApp CDN
- Verify MAC (last 10 bytes)
- Decrypt with AES-256-CBC
- Verify plaintext SHA256
- Return decrypted data
Error Handling
Download Errors
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 todownload_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, andMediaDownloader 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 ahost_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 validauth_ttl— how long the auth token specifically is valid (may be shorter thanttl)
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 standaloneMediaDownloader has no session to refresh. Upload credential refresh remains separate.
Best Practices
Next Steps
- Sending Messages - Send media with captions and quotes
- Receiving Messages - Handle incoming media
- Custom Backends - Implement custom HTTP clients for media operations