https://api.polymorfa.com/messaging/{session} and
require the scope shown in the generated reference.
Most session paths accept an team credential. The presence and
observation-policy paths also accept a project credential. A credential kind
that is not accepted for a path returns 403 before the request reaches the
session, and holding the right scope does not change that. Client tokens are
authorized by their recorded actions rather than by scopes. The per-operation
credential kinds and client actions for presence are listed in
SDK availability.
Presence observation
Presence and typing observation default tooff. Set the maximum policy for a
project with PUT /messaging/projects/{projectId}/observation-policy:
labelMode accepts one further mode, project, which adds a durable
project-scoped projection. See Label observation.
Read the project ceiling with GET /messaging/projects/{projectId}/observation-policy.
A session can inherit that ceiling or select an equally restrictive mode with
GET|PUT /messaging/{session}/observation-policy. A session override accepts
inherit, off, events, or cache, and project for labelMode;
selecting a mode above the project ceiling returns 400.
Policy updates require presence:observe. Changing labelMode additionally
requires labels:manage; an older client can omit labelMode without changing
the existing label policy. Presence subscriptions require presence:observe.
Reading the policy or cached state requires presence:read, while changing the
linked account’s own presence requires presence:write.
Subscribe to one user for 120 seconds with
POST /messaging/{session}/presence/{conversation}/subscribe. Then read the latest
allowed observation with GET /messaging/{session}/presence/{conversation}. A GET never
subscribes and never performs a live WhatsApp lookup. It returns status as
unknown, fresh, or stale; unknownReason is disabled, not_observed,
or suspended. Cached records include observedAt, may include lastSeen, and
become stale after 180 seconds.
GET /messaging/{session}/presence reports the session’s last requested and last
successfully sent self-presence values. Its authoritative field is always
false; the linked-device protocol does not provide a synchronous server read
of the current session presence.
Observation is bounded per session to 256 cached identities and 64 active targets.
Within ten minutes, a session can request at most 128 unique targets, 240 total
subscriptions, and 12 renewals for one target. Exceeding a limit returns
429, clears cached presence records and active subscriptions, and suspends
presence observation for 15 minutes. Subscription attempts during that window
also return 429. Typing observation is unaffected: its policy, its webhooks,
and any cached chat state continue under typingMode.
Messaging remains connected. Presence observations are ephemeral and are not
written to PostgreSQL.
Label observation
labelMode controls whether Polymorfa retains label definitions and chat-label
associations. It accepts off, events, cache, or project. The default is
off: label observation is opt-in, like presence and typing observation. A
session override also accepts inherit and cannot exceed the project setting.
Changing
project to a lower-retention mode removes the durable projection,
including projections for disconnected sessions. Removal runs in the
background. Re-enabling project while that removal is still in progress
returns 409; retry once it completes. If 409 persists, contact support.
Read definitions with GET /messaging/{session}/labels and one chat’s assignments
with GET /messaging/{session}/labels/chats/{conversation}. These existing routes return
the label array by default. Add ?includeObservation=true to receive policy,
status, and labels. status is disabled, unknown, partial, or fresh.
An unknown response includes unknownReason as observation_disabled,
not_retained, not_observed, or expired. Cache results include observedAt
and expiresAt; cache observations expire after 15 minutes. Durable results
include observedAt without a cache expiry.
A full linked-device sync replaces the durable projection as one generation.
Incremental events received before that snapshot return partial, so clients
can distinguish remembered data from a complete snapshot.
PUT /messaging/{session}/labels/chats/{conversation} has replacement semantics and accepts
an empty labels array to remove all assignments. Replacement requires an
authoritative cached or durable view of the chat. If that view is unavailable,
the endpoint returns 409 without sending a partial update. Label updates that
omit name or color have the same requirement so omitted fields are never
overwritten with empty values.
Quick-reply observation
quickReplyMode controls observation of WhatsApp Business App quick replies.
It accepts off, events, or cache and defaults to events. A session
override also accepts inherit and cannot exceed the project setting.
Read remembered entries with
GET /messaging/{session}/business/quick-replies. The response includes policy,
status, and quickReplies. status is disabled, unknown, partial, or
fresh. An unknown response includes unknownReason as
observation_disabled, not_retained, or not_observed.
A full linked-device sync replaces the in-memory projection atomically.
Incremental changes received before that snapshot return partial. The cache
retains at most 256 entries within a 256 KiB payload budget per session and is
cleared when observation is disabled. A GET never performs a live WhatsApp
lookup, and quick-reply content is not written to PostgreSQL. During a full
sync, the incoming 256 KiB payload budget is staged separately until it
replaces the previous snapshot or the sync fails.
History sync processing
History sync defaults to metadata-only processing. Configure a linked-device session withPUT /messaging/sessions/{session}:
mode accepts metadata_only or deliver. metadata_only commits a durable,
project-scoped record containing the chunk identity, sync type, order, progress,
file length, item counts, outcome, and timestamps. It does not marshal the
history protobuf, publish message bodies, write per-message history secrets,
or create temporary files. deliver publishes the compressed protobuf through
the durable history.sync webhook path before acknowledging the chunk.
The linked device sends hist_sync only after the selected acceptance point.
Duplicate notifications reuse the stored outcome and event identity, including a
chunk the device re-uploads under a new identifier after a failure: it resolves
to the original record and is not delivered twice. A chunk whose delivery
attempt fails keeps its metadata row unacknowledged so it is retried. A chunk whose metadata write itself fails leaves no row at all, so it
is neither acknowledged nor retried — the linked device resends it on the next
sync. Metadata rows contain no protobuf, message body, contact body, media key,
or download path. Acknowledged metadata is retained for 30 days. Unacknowledged
or failed metadata is retained for 90 days so delivery failures remain diagnosable
without allowing the table to grow without bound.
deliver refuses a chunk larger than the event transport carries, records the
outcome as payload_too_large, and does not acknowledge it. The chunk’s metadata
row is still written, so the sync type, item counts, and size stay visible in
metadata_only terms. Keep requestFull disabled if you need every chunk of a
large history delivered as a webhook payload.
requestFull defaults to false. When enabled, it applies on the next pairing
registration and requests at most 30 days, 64 MB per full sync, and 128 MB of
declared storage quota. A change made while the session is waiting for a QR scan
still reaches that pairing. Updating the processing mode applies to a connected
runner immediately; changing requestFull does not initiate a live lookup or
download.
Deleting a session deletes its history-sync metadata with it, before the
retention windows above elapse.
Privacy and disappearing messages
Read all privacy categories withGET /privacy. Update one category with
PUT /privacy/{setting}:
SetPrivacySettingRequest schema in the generated reference. Invalid
setting/value combinations return 400 without changing the account.
Set the default timer for new conversations with
PUT /privacy/disappearing/default. Set a timer for one direct or group chat
with PUT /chats/{conversation}/disappearing:
durationSeconds accepts 0, 86400, 604800, or 7776000, representing
off, 24 hours, 7 days, or 90 days. A direct-chat timer update uses the same
encrypted message transport as other outgoing direct-chat messages.
Contact metadata and blocklist
The contact endpoints expose cached contact names and live account metadata:
Live metadata comes from the linked account. Empty profile fields remain empty
strings or arrays; they do not imply that the contact is a business account.
Contact and participant objects use the stable
id as their primary key;
phone numbers and usernames are optional aliases. See
User identities.
Catalog, product, collection, and order reads are documented under
Catalogs and commerce.
Group settings
Use these operations after creating or joining a group:member-add accepts admin_add or all_member_add.
Channels
Create a channel withPOST /channels, and delete a channel you own with
DELETE /channels/{id}. Deletion is permanent. The delete request returns
success only after WhatsApp confirms that the channel state is DELETED:
Send an empty
reaction string to remove your existing reaction. The live
updates response contains durationSeconds; subscribe again after that period
if you still need live channel updates.
Reject an incoming call
Use thecallId and caller ID from the incoming-call webhook:
Unsupported operations
An operation that cannot be performed by the linked-device protocol returns501. Treat 501 as a capability boundary rather than retrying it. The
generated native endpoint reference lists the exact request and response
schemas for every operation.