download
Download and decrypt media from a message.Any message type that implements the
Downloadable trait. Includes:ImageMessageVideoMessageAudioMessageDocumentMessageStickerMessageExternalBlobReference(app state)HistorySyncNotification
Decrypted media bytes. For encrypted media (E2EE), automatically decrypts using AES-256-CBC and verifies HMAC-SHA256. For plaintext media (newsletters/channels), validates SHA-256 hash.
Example: download image
Example: download with error handling
Automatic retry and URL re-derivation
All download methods handle three categories of CDN errors automatically:- Auth errors (401/403): The client invalidates the cached media connection, fetches fresh credentials, and retries the download once.
- Media not found (404/410): When a media URL has expired or the file has been relocated, the CDN returns 404 or 410. The client treats this the same as an auth error — it invalidates the cached connection, re-derives download URLs with fresh credentials and hosts, and retries once. This matches WhatsApp Web’s
MediaNotFoundErrorhandling. - Other errors (e.g., 500): The client tries the next available CDN host without refreshing credentials. Hosts are tried in priority order (primary first, then fallback).
download_to_writer
Download media to a writer using streaming when available, with automatic buffered fallback. This is the method to use for downloading straight to a file — pass aFile or BufWriter<File>.
When the HTTP client supports streaming (supports_streaming() returns true), the entire HTTP download, decryption, and file write happen in a single blocking thread with ~40KB memory usage regardless of file size. When streaming is not available, the method automatically falls back to a buffered download — fetching the full response into memory, then decrypting and writing to the writer. This ensures download_to_writer works with any HttpClient implementation.
Message containing downloadable media
Writer for streaming output. Must be Send + ‘static for use in blocking task.
Returns the writer after successful download, seeked back to position 0.
Example: streaming download
When using an HTTP client that supports streaming (like the default
UreqHttpClient), memory usage is constant ~40KB (8KB read buffer + decryption state). HTTP clients that don’t support streaming fall back to buffered downloads, which load the full file into memory before writing.download_from_params
Download and decrypt media from raw CDN parameters without the original message. The parameters are bundled into aDownloadParams struct.
The CDN/crypto fields needed to fetch and decrypt the media. Build one with
DownloadParams::encrypted.Decrypted media bytes
Example: download from stored metadata
download_from_params_to_writer
Streaming variant ofdownload_from_params that writes to a writer.
The CDN/crypto fields needed to fetch and decrypt the media. See
DownloadParams.Writer for streaming output
Returns the writer after successful download
DownloadParams
ADownloadable built from raw CDN fields, for re-downloading media without the original message.
| Field | Type | Description |
|---|---|---|
direct_path | String | WhatsApp CDN path (e.g. /v/t62.7118-24/12345_67890) |
media_key | Option<Vec<u8>> | 32-byte media key. None for plaintext (newsletter/channel) media |
file_sha256 | Vec<u8> | SHA-256 of the decrypted file |
file_enc_sha256 | Option<Vec<u8>> | SHA-256 of the encrypted file (encrypted media only) |
file_length | u64 | Original file size in bytes |
media_type | MediaType | Image, Video, Audio, Document, Sticker, … |
DownloadParams::encrypted
Convenience constructor for encrypted (E2EE) media — fillsmedia_key and file_enc_sha256 as Some(...).
DownloadParams implements Downloadable, so it works with download, download_to_writer, download_from_params, and download_from_params_to_writer.
fetch_sticker_pack
Fetch first-party sticker pack metadata (and the per-sticker download handles) from the WhatsApp CDN.The first-party sticker pack ID (typically extracted from a received
sticker_pack_message).BCP-47 locale tag for localized name / publisher strings. Pass
"en" to match whatsmeow’s default.Pack metadata plus a
Vec<StickerPackItem> of individual stickers. Each StickerPackItem implements Downloadable, so you can pass it straight to client.download(...).https://static.whatsapp.net/sticker?lottie=1&cat=sticker_pack_data&id={pack_id}&lg={locale}, parses the JSON envelope, and constructs the StickerPack. The endpoint is unauthenticated — the call works whether or not you are paired.
StickerPack
StickerPackItem
The CDN response is a JSON envelope, not a protobuf message —
StickerPack / StickerPackItem live in wacore::sticker_pack and are independent of waproto::whatsapp::StickerPackMessage (which represents the inline pack-bubble in a chat). The struct mirrors whatsmeow’s FirstPartyStickerPack.Downloadable Trait
TheDownloadable trait provides a generic interface for downloading media from any message type.
WhatsApp CDN path for the media file
32-byte encryption key. Present for E2EE media,
None for plaintext (newsletter/channel) media.SHA-256 hash of the encrypted file. Used for encrypted media validation.
SHA-256 hash of the decrypted file. Used for plaintext media validation.
Original file size in bytes
Media type for HKDF key derivation (
Image, Video, Audio, Document, etc.)Static CDN URL for direct download. Present on newsletter/channel media, bypasses host construction.
Returns
true if media is encrypted (has media_key), false for plaintext mediaBuilt-in Implementations
TheDownloadable trait is automatically implemented for:
wa::message::ImageMessagewa::message::VideoMessagewa::message::AudioMessagewa::message::DocumentMessagewa::message::StickerMessagewa::ExternalBlobReference(app state)wa::message::HistorySyncNotification
MediaType
Media type enum for encryption/decryption.Image/Sticker→"WhatsApp Image Keys"Video→"WhatsApp Video Keys"Audio→"WhatsApp Audio Keys"Document→"WhatsApp Document Keys"History→"WhatsApp History Keys"AppState→"WhatsApp App State Keys"StickerPack→"WhatsApp Sticker Pack Keys"StickerPackThumbnail→"WhatsApp Sticker Pack Thumbnail Keys"LinkThumbnail→"WhatsApp Link Thumbnail Keys"
MediaType methods
| Method | Return type | Description |
|---|---|---|
app_info() | &'static str | HKDF info string for key derivation |
mms_type() | &'static str | Media type string for MMS path construction |
upload_path() | &'static str | URL path prefix for upload/download |
is_encrypted() | bool | Whether this media type uses E2E encryption |
Upload paths
| Media type | Upload path |
|---|---|
Image / Sticker | /mms/image |
Video | /mms/video |
Audio | /mms/audio |
Document | /mms/document |
History | /mms/md-msg-hist |
AppState | /mms/md-app-state |
StickerPack | /mms/sticker-pack |
StickerPackThumbnail | /mms/thumbnail-sticker-pack |
LinkThumbnail | /mms/thumbnail-link |
ProductCatalogImage | /product/image |
ProductCatalogImage is unencrypted — is_encrypted() returns false. This matches WhatsApp Web’s behavior where CreateMediaKeys.js skips encryption for product catalog images. Its upload path is /product/image (not under the /mms/ prefix like other media types).Media Decryption
WhatsApp uses different handling for encrypted (E2EE) and plaintext media:Encrypted Media (E2EE)
- Download encrypted bytes from CDN
- Verify HMAC-SHA256 (last 10 bytes)
- Decrypt using AES-256-CBC with keys derived from
media_keyvia HKDF - Return decrypted plaintext
media_key is expanded using HKDF-SHA256 to derive:
- 16-byte IV
- 32-byte cipher key
- 32-byte MAC key
Plaintext media (newsletter/channel)
- Download plaintext bytes from CDN (often via
static_url) - Verify SHA-256 hash matches
file_sha256 - Return plaintext (no decryption needed)
Newsletter and channel media is not encrypted. The library automatically detects this when
media_key is absent and switches to plaintext validation.Example: detect media type
DownloadUtils
Low-level static methods for media decryption and validation. These are re-exported fromwacore::download and useful when you need fine-grained control over the download pipeline.
Key methods
| Method | Description |
|---|---|
verify_and_decrypt(encrypted_payload, media_key, media_type) | Verifies HMAC and decrypts AES-256-CBC in one call |
decrypt_stream(reader, media_key, media_type) | Streaming decryption from a reader (convenience wrapper around decrypt_stream_to_writer) |
decrypt_stream_to_writer(reader, media_key, media_type, writer) | Streaming decryption directly into a writer with constant memory usage |
validate_plaintext_sha256(data, expected_sha256) | Validates SHA-256 hash of plaintext media (in-memory) |
copy_and_validate_plaintext_to_writer(reader, expected_sha256, writer) | Streams plaintext media to a writer while validating SHA-256 hash |
prepare_download_requests(downloadable, media_conn) | Builds CDN request URLs with host failover |
get_media_keys(media_key, app_info) | Derives IV, cipher key, and MAC key via HKDF |