Skip to main content
Consent management allows users to control which conversations and contacts they interact with. This guide covers setting consent preferences, filtering conversations, and handling blocked users. LibXMTP tracks consent at three levels:
use xmtp_db::consent_record::ConsentState;

pub enum ConsentState {
    Unknown,   // No explicit preference set
    Allowed,   // User has allowed this entity
    Denied,    // User has blocked/denied this entity
}
Consent can be tracked for different entity types:
use xmtp_db::consent_record::ConsentType;

pub enum ConsentType {
    ConversationId,  // Specific group/conversation
    InboxId,         // Entire inbox (all their conversations)
    Address,         // Wallet address
}

For Conversations

Set consent for a specific group:
use xmtp_db::consent_record::{ConsentState, ConsentType, StoredConsentRecord};

// Allow a conversation
let consent_record = StoredConsentRecord::new(
    hex::encode(&group.group_id),
    ConsentType::ConversationId,
    ConsentState::Allowed,
);

client.set_consent_states(&[consent_record]).await?;
Or use the group helper:
// Update consent directly on the group
group.update_consent_state(ConsentState::Allowed)?;

// Check current state
let state = group.consent_state()?;
assert_eq!(state, ConsentState::Allowed);

For Inbox IDs

Block all conversations from a specific inbox:
let consent_record = StoredConsentRecord::new(
    target_inbox_id.to_string(),
    ConsentType::InboxId,
    ConsentState::Denied,
);

client.set_consent_states(&[consent_record]).await?;

For Addresses

Set consent by wallet address:
let consent_record = StoredConsentRecord::new(
    "0x1234...".to_string(),
    ConsentType::Address,
    ConsentState::Denied,
);

client.set_consent_states(&[consent_record]).await?;

Batch Updates

Update multiple consent preferences at once:
let records = vec![
    StoredConsentRecord::new(
        inbox_id_1.to_string(),
        ConsentType::InboxId,
        ConsentState::Allowed,
    ),
    StoredConsentRecord::new(
        inbox_id_2.to_string(),
        ConsentType::InboxId,
        ConsentState::Denied,
    ),
    StoredConsentRecord::new(
        hex::encode(&group_id),
        ConsentType::ConversationId,
        ConsentState::Allowed,
    ),
];

client.set_consent_states(&records).await?;
Check consent for a specific entity:
use xmtp_db::consent_record::ConsentType;

// Check group consent
let state = client.get_consent_state(
    ConsentType::ConversationId,
    hex::encode(&group_id),
).await?;

match state {
    ConsentState::Allowed => println!("Conversation allowed"),
    ConsentState::Denied => println!("Conversation blocked"),
    ConsentState::Unknown => println!("No preference set"),
}

// Check inbox consent
let state = client.get_consent_state(
    ConsentType::InboxId,
    inbox_id.to_string(),
).await?;
Filter conversations by consent state:
use xmtp_db::group::{GroupQueryArgs, GroupMembershipState};

// Get only allowed conversations
let args = GroupQueryArgs::default()
    .consent_states(Some(vec![ConsentState::Allowed]));

let allowed_groups = client.find_groups(args)?;

// Get blocked conversations
let denied_args = GroupQueryArgs::default()
    .consent_states(Some(vec![ConsentState::Denied]));

let blocked_groups = client.find_groups(denied_args)?;

Group Creation

When you create a group, consent defaults to Allowed:
let group = client.create_group(None, None)?;

// Consent is automatically set to Allowed
assert_eq!(group.consent_state()?, ConsentState::Allowed);

Receiving Invites

When invited to a group, consent defaults to Unknown:
// User receives a welcome message
client.sync_welcomes().await?;

let groups = client.find_groups(GroupQueryArgs::default())?;
for group in groups {
    let state = group.consent_state()?;
    if state == ConsentState::Unknown {
        println!("New invitation - no consent set yet");
    }
}

Filtering Streams

Consent preferences automatically filter message streams:

Stream Allowed Messages Only

use xmtp_db::consent_record::ConsentState;

// Only receive messages from allowed conversations
let stream = client.stream_all_messages(
    None,  // conversation_type
    Some(vec![ConsentState::Allowed]),  // consent filter
).await?;

while let Some(message) = stream.next().await {
    // Only messages from allowed conversations
    process_message(message);
}

Stream All Conversations

Include both allowed and unknown (new invites):
let stream = client.stream_all_messages(
    None,
    Some(vec![ConsentState::Allowed, ConsentState::Unknown]),
).await?;
Sync only groups matching consent preferences:
// Sync only allowed and unknown groups
let summary = client.sync_all_welcomes_and_groups(
    Some(vec![ConsentState::Allowed, ConsentState::Unknown])
).await?;

println!("Synced {} groups", summary.num_groups_synced);

Device Sync

Consent preferences sync across installations:
// On device 1: Set consent
group.update_consent_state(ConsentState::Denied)?;

// On device 2: Consent is automatically synced
client.sync_welcomes().await?;
let synced_group = client.group(&group.group_id)?;
assert_eq!(synced_group.consent_state()?, ConsentState::Denied);
Consent preferences are synced between your installations via the device sync mechanism. Changes on one device will propagate to other devices.

TypeScript (Node.js) Examples

import { ConsentState, ConsentEntityType } from '@xmtp/node-bindings'

// Allow a conversation
await client.setConsentStates([{
  entityType: ConsentEntityType.GroupId,
  entity: conversation.id(),
  state: ConsentState.Allowed
}])

// Block an inbox
await client.setConsentStates([{
  entityType: ConsentEntityType.InboxId,
  entity: targetInboxId,
  state: ConsentState.Denied
}])

Handling Unwanted Conversations

1
Detect Unwanted Groups
2
Identify groups with unknown consent:
3
let groups = client.find_groups(
    GroupQueryArgs::default()
        .consent_states(Some(vec![ConsentState::Unknown]))
)?;

for group in groups {
    let metadata = group.metadata()?;
    println!("New group: {:?}", metadata.group_name);
    println!("Added by: {}", group.added_by_inbox_id()?);
    
    // Let user decide
    // group.update_consent_state(ConsentState::Allowed)?;
    // or
    // group.update_consent_state(ConsentState::Denied)?;
}
4
Block and Leave
5
Block a conversation and optionally leave:
6
// Block the conversation
group.update_consent_state(ConsentState::Denied)?;

// Optionally leave the group
group.leave().await?;
7
Unblock
8
Change consent back to allowed:
9
// Unblock
group.update_consent_state(ConsentState::Allowed)?;

// Group messages will now be received

Best Practices

2
When creating groups for others, consent is already set to allowed:
3
let group = client.create_group(None, None)?;
assert_eq!(group.consent_state()?, ConsentState::Allowed);
// No need to set consent explicitly
5
When receiving invites, prompt users:
6
let new_groups = client.sync_welcomes().await?;

for group in new_groups {
    if group.consent_state()? == ConsentState::Unknown {
        let metadata = group.metadata()?;
        
        // Show UI to user
        // "Accept invitation to: {:?}?", metadata.group_name
        
        // Based on user choice:
        // group.update_consent_state(ConsentState::Allowed)?;
        // or
        // group.update_consent_state(ConsentState::Denied)?;
    }
}
8
Always filter message streams to respect user preferences:
9
// Good: Only show allowed messages
let stream = client.stream_all_messages(
    None,
    Some(vec![ConsentState::Allowed]),
).await?;

// Also good: Include unknown for new invites
let stream = client.stream_all_messages(
    None,
    Some(vec![ConsentState::Allowed, ConsentState::Unknown]),
).await?;

// Avoid: Showing denied conversations
// let stream = client.stream_all_messages(None, None).await?;
11
Ensure consent syncs across devices:
12
// Device sync is enabled by default
let client = Client::builder(identity_strategy)
    .device_sync_worker_mode(DeviceSyncMode::Enabled)
    // ... other config
    .build().await?;

// Consent changes will sync automatically
DM consent works the same as groups:
// Create DM (consent defaults to Allowed)
let dm = client.find_or_create_dm(target_inbox_id, None).await?;
assert_eq!(dm.consent_state()?, ConsentState::Allowed);

// Block DM partner
dm.update_consent_state(ConsentState::Denied)?;

// Or block their entire inbox
let consent_record = StoredConsentRecord::new(
    target_inbox_id.to_string(),
    ConsentType::InboxId,
    ConsentState::Denied,
);
client.set_consent_states(&[consent_record]).await?;

Error Handling

use xmtp_db::StorageError;

match group.update_consent_state(ConsentState::Denied) {
    Ok(_) => println!("Consent updated"),
    Err(e) => {
        eprintln!("Failed to update consent: {}", e);
        // Handle error - maybe retry or notify user
    }
}

Next Steps

Database Encryption

Secure your local data with encryption

Managing Groups

Learn more about group management

Build docs developers (and LLMs) love