Overview
By default, every inbound history-sync notification from the phone is accepted and enters the major-sync queue for download, decompression, and dispatch asEvent::HistorySync. That matches WhatsApp Web’s behavior and is the right choice for a client that needs history.
A bot that only cares about a subset of syncs — or that wants to skip peer-data-request chunks, cap per-chunk size, or drop everything past initial pairing — can register a HistorySyncAdmission policy. The policy runs synchronously on the receive path and can reject a notification before the client starts any history-sync activity.
Leaving the policy unset keeps the default behavior at zero cost. The receive path only checks that the optional policy is None.
This is an opt-in seam. Rejecting a notification is a deliberate decision to drop a chunk the phone offered; the SDK will never do this on its own.
When to use it
Consider a policy when:- You want to admit some history-sync types but not others (for example, accept
INITIAL_BOOTSTRAPbut rejectRECENT). - You want to bound per-chunk cost with a size guard on
file_lengthorinline_payload_len. - You want to drop peer-data-request responses that your bot never issued.
- You need finer control than the all-or-nothing
.skip_history_sync()toggle already exposes.
.skip_history_sync() if you want to drop every history-sync notification unconditionally. That toggle is simpler and takes precedence over any registered admission policy — the policy is not consulted when skip_history_sync is on.
Registration for device pairing and the require_full_sync request are unchanged. The policy runs after pairing completes and only gates the delivery of individual history-sync chunks from the phone.
Scope
The policy is consulted for every inbound history-sync notification that reacheshandle_history_sync, except in two cases:
- The client is shutting down (the notification is dropped without consulting the policy or sending a receipt).
skip_history_syncis enabled (the client sends ahist_syncreceipt and never calls the policy).
RejectAndAcknowledge decision:
- Sends a
hist_syncreceipt so the phone considers the chunk delivered and stops re-uploading it. - Does not enqueue the notification onto the major-sync worker.
- Does not emit
Event::HistorySync.
RejectAndAcknowledge intentionally sends a positive receipt and retains no recovery record. Do not rely on the phone offering that chunk again. If you need transient load shedding, do it inside Accept by throttling downstream work, not by rejecting.
Types
HistorySyncMetadata and HistorySyncDecision are #[non_exhaustive] — new fields or variants may appear in future releases. Match with a wildcard arm on HistorySyncDecision and treat missing metadata fields as unknown rather than empty.
HistorySyncAdmission is object-safe and WASM-safe (MaybeSendSync is Send + Sync on native targets, unbounded on wasm32). It is re-exported from the crate root alongside HistorySyncMetadata and HistorySyncDecision.
decide runs inline on the receive path and is deliberately synchronous. A slow or awaiting policy would stall history-sync intake. Keep it to a fast local decision (a comparison, an atomic counter, a bitmask over sync_type); do no I/O or blocking inside it.
Metadata fields
Opting in
Register the policy on the builder before the client connects. BothBot::builder() and ClientBuilder expose the same method.
Arc, use with_history_sync_admission_arc to avoid re-boxing:
Example: cap chunk size
Drop any chunk whose sender-declared file size exceeds a threshold, and accept everything else.file_length is sender-declared and not validated, this is a best-effort gate. A chunk whose actual payload exceeds the declared size will still be admitted if the declared value is under the cap; use the standard memory-report counters to observe realised cost.
Precedence
Two related settings take precedence over the admission policy:- Shutdown. During shutdown the notification is dropped silently, without a receipt and without consulting the policy.
skip_history_sync. When on, the client sends ahist_syncreceipt and skips the policy. Change the toggle at runtime withClient::set_skip_history_sync.
require_full_sync and history_sync_config fields set at pairing time are independent of this hook. They shape what the phone offers; the admission policy shapes what the client accepts once the phone offers it.
See also
- Retry Admission Hook — the sibling opt-in policy for gating inbound group/status retry receipts.
- Inbound Durability Hook — the opt-in hook idiom this trait follows.
Bot.skip_history_sync— the all-or-nothing toggle that takes precedence over any admission policy.
Durable notification recovery
HistorySyncDurabilityHook is a separate opt-in from admission, live-message commits and HistorySyncCaptureHook. Register it before building with with_history_sync_durability_hook or with_history_sync_durability_hook_arc on ClientBuilder or BotBuilder. The last registration wins.
on_notification and admits work to the bounded history queue before carrier ACK. Queue saturation or capture failure retains the journal for a redelivery attempt without repeating Signal decryption. Construction probes the required backend operations even when no live-message durability hook is installed.
Commit the exact encoded notification before returning Ok. The SDK retains the original notification wire, including unknown fields and inline data. on_compressed_chunk receives decrypted, zlib-compressed bytes before parsing. With this hook, the hist_sync receipt also waits for successful parsing. If you separately register HistorySyncCaptureHook, the worker awaits it after durable chunk retention and before parsing. Its failure withholds the receipt even when the chunk is already retained. Skip or admission rejection intentionally acknowledges without either callback.
Deduplicate by every field of HistorySyncKey, chat, sender and id, within a separate account namespace. Preserve device components and reject the same key with different bytes. Metadata contains sender claims, not validated file properties. Protect notifications as account data because they contain media keys and may contain inline history.
Replay retained notifications
After carrier ACK, the host owns recovery if processing fails or the process exits. Retain notifications and retryclient.replay_history_sync_notification(&key, &bytes).await? until your chunk record is durable. Register the same idempotent hook first. Replay requires the loaded account identity and verifies the key belongs to that account. Downloaded chunks also need a connected socket; inline replay can run without one. Ok does not confirm receipt delivery or remote acknowledgement, and replay does not remove the host record.
Replay and worker processing share a two-task limit. Notification callbacks backpressure receive commits; chunk callbacks run on the worker or replay. Keep callbacks nonblocking and avoid waiting on client operations that depend on that pipeline. Events and callbacks may repeat. Backend durability and your own retry scheduler determine recovery strength.