---
source_url: https://www.pubnub.com/docs/chat/swift-chat-sdk/build/features/messages/read-receipts
title: Read receipts
updated_at: 2026-06-16T12:48:54.323Z
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Read receipts

Read receipts show if channel members have viewed a message.

:::note Required setup
Read Receipts requires [Unread Message Count](https://www.pubnub.com/docs/chat/swift-chat-sdk/build/features/messages/unread). First, [set the last read timetoken](https://www.pubnub.com/docs/chat/swift-chat-sdk/build/features/messages/unread#mark-messages-as-read-one-channel) for each user on a channel.
:::

## Stream read receipts

`onReadReceiptReceived()` provides read status for messages on a channel via a closure. The method listens for updates and passes [ReadReceipt](#readreceipt-type) events to the callback. You can also use `channel.stream.readReceipts()` for an `AsyncStream`-based approach.

:::warning Not available for public chats
Read receipts are disabled in [public chats](https://www.pubnub.com/docs/chat/swift-chat-sdk/build/features/channels/create#create-public-channel) by default. You can control this behavior per channel type with the [emitReadReceiptEvents](https://www.pubnub.com/docs/chat/swift-chat-sdk/build/configuration#chatconfiguration) configuration parameter.
:::

:::note Deprecation
`streamReadReceipts()` is deprecated. Use `onReadReceiptReceived()` (closure-based) or `channel.stream.readReceipts()` (AsyncStream-based) instead.
:::

### ReadReceipt type

Read receipts now use a typed `ReadReceipt` struct instead of a raw dictionary:

```swift
public struct ReadReceipt {
    public let userId: String
    public let lastReadTimetoken: Timetoken
}
```

### Method signature

```swift
channel.onReadReceiptReceived(
    callback: @escaping (ReadReceipt) -> Void
) -> AutoCloseable
```

#### Input

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| callback | (ReadReceipt) | Yes |  | Closure called with a `ReadReceipt` whenever someone marks a message as read. |

#### Output

| Parameter | Description |
| --- | --- |
| `AutoCloseable` | An object you must retain. When released or closed, the listener stops. |

### Sample code

:::tip Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
:::

Receive updates for read receipts on the `support` channel.

###### Closure

```swift
// Assumes a "ChannelImpl" reference named "channel"

// Important: Keep a strong reference to the returned "AutoCloseable" object as long as you want
// to receive updates. If the "AutoCloseable" is deallocated, the stream will be cancelled,
// and no further items will be produced. You can also stop receiving updates manually
// by calling the "close()" method on the "AutoCloseable" object.
autoCloseable = channel.streamReadReceipts { readReceipts in
  // readReceipts is a [Timetoken: [String]] dictionary
  for (timetoken, userIds) in readReceipts {
    print("Timetoken \(timetoken) was last read by users: \(userIds)")
  }
}
```

###### AsyncStream

```swift
// Assumes a "ChatImpl" reference named "chat"
Task {
  if let channel = try await chat.getChannel(channelId: "support") {
    for await readReceipts in channel.streamReadReceipts() {
      // readReceipts is a [Timetoken: [String]] dictionary
      // mapping each last-read timetoken to the list of user IDs who read up to that point
      for (timetoken, userIds) in readReceipts {
        debugPrint("Timetoken \(timetoken) was last read by users: \(userIds)")
      }
    }
  } else {
    debugPrint("Channel not found")
  }
}
```

## Fetch read receipts

`fetchReadReceipts()` retrieves historical read receipts for a channel with pagination support.

### Method signature

```swift
channel.fetchReadReceipts(
    limit: Int? = nil,
    page: PubNubHashedPage? = nil,
    filter: String? = nil,
    sort: [PubNub.MembershipSortField] = []
) async throws -> (receipts: [ReadReceipt], page: PubNubHashedPage?)
```

#### Input

| Parameter | Description |
| --- | --- |
| `limit`Type: `Int`Default: `nil` | Number of read receipts to return per call. |
| `page`Type: `PubNubHashedPage`Default: `nil` | Pagination object for fetching additional pages. |
| `filter`Type: `String`Default: `nil` | Expression to filter results. The [filter language](https://www.pubnub.com/docs/general/metadata/filtering) is defined here. |
| `sort`Type: `[PubNub.MembershipSortField]`Default: `[]` | Sort order for results. |

#### Output

| Parameter | Description |
| --- | --- |
| `(receipts: [ReadReceipt], page: PubNubHashedPage?)` | A tuple containing an array of `ReadReceipt` objects and pagination information. |

### Sample code

:::tip Sample code
The code samples in Swift Chat SDK focus on asynchronous code execution.
You can also write synchronous code as the parameters are shared between the async and sync methods but we don't provide usage examples of such.
:::

Fetch all read receipts for the `support` channel.

```swift
// Assumes a "ChatImpl" reference named "chat"
Task {
  if let channel = try await chat.getChannel(channelId: "support") {
    let result = try await channel.fetchReadReceipts()
    let receipts = result.receipts
    // Use for subsequent call: channel.fetchReadReceipts(page: nextPage)
    let nextPage = result.page

    receipts.forEach { receipt in
      print("User \(receipt.userId) has read up to timetoken: \(receipt.lastReadTimetoken)")
    }
  }
}
```