Consent records what a user wants to hear. A conversation or inbox ID has one of three states. An entity with no record is unknown. The UI locations below are suggested app behavior.
State
Meaning
UI location
unknown
Nobody has decided
Requests
allowed
The user consented
Main inbox
denied
The user refused
Hidden
Consent records are stored locally. When device sync is enabled, device sync copies decisions to the inbox’s other installations. A later consent time wins; at equal times, denied wins over allowed, then unknown.
Usually unknown; an existing DM can inherit its prior state
The user creates a conversation
allowed
The user sends a message
allowed
The app sets consent after accepting or subscribing
allowed
The app sets consent after blocking or unsubscribing
denied
The user is re-added to a group they left
unknown, unless the group names the own inbox as creator
The user’s creation of the conversation with the contact is considered consent. On the current client, a joined group that names the own inbox as creator is also set to allowed. This check uses the creator field; it does not prove which inbox added the user.
The user’s response to the conversation is considered consent.
Sending a message sets the conversation to allowed from any prior state, including denied. Prevent sending in a denied conversation unless the user explicitly reverses the block.
group.state().common.consentState with suspend or try await
Read DM
(await dm.state()).consentState
dm.state().consentState with suspend or try await
Read an entity
client.preferences.consentState(entity)
client.preferences().consentState(entity)
Set conversation
conversation.updateConsentState(state)
conversation.updateConsentState(state)
Set many
client.preferences.setConsentStates(records)
client.preferences().setConsentStates(records)
Stream changes
client.events(filter)
client.events(filter)
For JavaScript, a consent entity is { kind: "conversation", conversationId } or
{ kind: "inbox", inboxId }. Kotlin uses ConsentEntity.Conversation(id) or
ConsentEntity.Inbox(id). Swift uses .conversation(conversationId: id) or
.inbox(inboxId: id). A ConsentRecord holds that entity and its state. Swift
uses argument labels, such as consentState(entity: entity) and
setConsentStates(records: records).
Inbox consent does not automatically set conversation consent. Set the
conversation state when the user accepts or blocks a conversation. Consent does
not prevent sending. Pass consent filters when listing or reading messages.
The stream reports a ConsentChanged payload with entityKind, entity, and state on Kotlin and Swift. JavaScript wraps this payload in event.consent_changed; its fields are entity_kind, entity, and state.
Batch state changes when possible. A batch commits its final state for each
entity in one transaction. Device sync publishes preference changes separately.
A stream of the client events that filter selects.
events({
kinds: EventKind[]
kinds: ["consent.changed"],
references_own_messages?: boolean |undefined
references_own_messages: false,
});
const
constreceive:Promise<void>
receive= (async () => {
forawait (const
constevent:ClientEvent
eventof
conststream:EventStream
stream) {
if (
constevent:ClientEvent
event.
kind: "conversation.joined"|"conversation.removed"|"conversation.membership_changed"|"conversation.metadata_changed"|"conversation.paused"|"message.received"|"message.status_changed"|"message.deleted"|"message.expired"|"consent.changed"|"hmac_keys.updated"|"identity.registered"|"identity.own_installation_added"|"identity.own_installation_revoked"|"client.rejected_by_server"|"client.lockout_changed"|"conversation.fork_detected"|"notifications.failed"|"archive.restored"|"connection.state_changed"|...8 more ...|"lagged"
A stream of the client events that filter selects.
events({
kinds: EventKind[]
kinds: ["consent.changed"],
references_own_messages?: boolean |undefined
references_own_messages: false,
});
const
constreceive:Promise<void>
receive= (async () => {
forawait (const
constevent:ClientEvent
eventof
conststream:EventStream
stream) {
if (
constevent:ClientEvent
event.
kind: "conversation.joined"|"conversation.removed"|"conversation.membership_changed"|"conversation.metadata_changed"|"conversation.paused"|"message.received"|"message.status_changed"|"message.deleted"|"message.expired"|"consent.changed"|"hmac_keys.updated"|"identity.registered"|"identity.own_installation_added"|"identity.own_installation_revoked"|"client.rejected_by_server"|"client.lockout_changed"|"conversation.fork_detected"|"notifications.failed"|"archive.restored"|"connection.state_changed"|...8 more ...|"lagged"
kind==="consent.changed")
handleConsent: (consent:ConsentChange) =>void
handleConsent(
constevent: {
readonlykind:"consent.changed";
readonlyconsent_changed:ConsentChanged;
}
event);
}
})();
client.events(EventFilter(
kinds =listOf(EventKind.CONSENT_CHANGED),
groupIds =null,
contentTypes =null,
referencesOwnMessages =false,
)).collect { event ->
if (event is ClientEvent.ConsentChanged) handleConsent(event.consentChanged)
Groups and DMs expose addedByInboxId and creatorInboxId as properties on Browser and Node. Kotlin and Swift expose addedByInboxId() and creatorInboxId() methods. Both values can be absent, for example after restoration. Do not treat a creator field alone as proof that the creator added the user.
Use client events to react to local consent changes. Event listeners do not apply consent filters.