Skip to content

Latest commit

 

History

History
214 lines (160 loc) · 5.19 KB

File metadata and controls

214 lines (160 loc) · 5.19 KB

NIP-100 Title: Read Receipts for Encrypted Direct Messages Author: npub1lgp6j9rrumk347vyx8rq9ycvqg4vwasugtqx4wup2tgqemhxj27sxxw02m Status: Draft Category: Communication Created: 2025-12-02

Summary

This NIP defines an optional mechanism for acknowledging that a NIP-44 encrypted direct message has been viewed by its intended recipient. It introduces a new event kind (1499) used to publish a read receipt that references the original encrypted message.

Motivation

Encrypted direct messages (NIP-44) provide confidentiality but no delivery semantics. Many chat-style applications expect read acknowledgments, and several Nostr clients have experimented with ad-hoc implementations.

NIP-100 provides a minimal, interoperable solution that:

  • does not modify NIP-44
  • does not require any relay changes
  • is fully optional for clients
  • is intentionally small and simple

Clients that do not implement this NIP continue to operate normally.

Event Kind

Read receipt events use:

kind: 1499

A kind 1499 event asserts that the author claims to have viewed the referenced encrypted direct message.

Event Format

Required Tags

Tag Description
["e", <event-id>] Event ID of the encrypted DM being acknowledged
["p", <sender-pubkey>] Pubkey of the sender of the DM
["k", "14"] Indicates the referenced event is of kind 14 (NIP-44 encrypted DM)

Content

The content field MUST be an empty string:

""

Example

{
  "kind": 1499,
  "created_at": 1735689000,
  "pubkey": "npub1reader...",
  "tags": [
    ["e", "7e8bf74059..."],
    ["p", "npub1xyz..."],
    ["k", "14"]
  ],
  "content": ""
}

Client Behavior

Publishing Receipts

A client MAY publish a read receipt when:

  • the user has explicitly enabled read receipts, and
  • the user has viewed the decrypted content of the referenced message.

A client MUST NOT publish receipts without user intent.

Handling Incoming Receipts

A client MAY display a read receipt if:

  1. the referenced DM exists locally,
  2. the DM is decryptable by the user, and
  3. the receipt is authored by the intended recipient.

Clients SHOULD ignore receipts referencing unknown or undecryptable messages, or those with malformed tags.

Relay Behavior

Relays:

  • MUST accept and distribute kind 1499 events without special logic
  • MUST NOT treat read receipts differently from other events

No relay changes are required.

Security and Privacy Considerations

Publishing a read receipt reveals that a user viewed a specific encrypted message at a specific time. Clients MUST provide users with the option to disable read receipts.

A receipt authenticates only the author’s pubkey. It does not prove that the message was displayed. Read receipts should therefore be treated as advisory signals.

NIP-44 encryption continues to protect message content.

Backwards Compatibility

  • Clients that do not support this NIP will ignore kind 1499 events.
  • The NIP does not modify NIP-44 or encrypted DM behavior.
  • No relay changes are necessary.

Reference Implementation Notes

A basic client implementation requires:

  1. Detecting when decrypted DM content becomes visible.
  2. Publishing a kind 1499 event referencing the DM event ID.
  3. Optionally marking messages as “read” when corresponding receipts are observed.

Python Example

import time

def make_read_receipt(dm_event, my_pubkey):
    return {
        "kind": 1499,
        "created_at": int(time.time()),
        "pubkey": my_pubkey,
        "tags": [
            ["e", dm_event["id"]],
            ["p", dm_event["pubkey"]],
            ["k", "14"]
        ],
        "content": ""
    }

TypeScript Example

export function makeReadReceipt(dmEvent, myPubkey) {
  return {
    kind: 1499,
    created_at: Math.floor(Date.now() / 1000),
    pubkey: myPubkey,
    tags: [
      ["e", dmEvent.id],
      ["p", dmEvent.pubkey],
      ["k", "14"]
    ],
    content: ""
  };
}

Go Example

func MakeReadReceipt(dmEvent Event, myPubkey string) Event {
    return Event{
        Kind:      1499,
        CreatedAt: time.Now().Unix(),
        PubKey:    myPubkey,
        Tags: Tags{
            {"e", dmEvent.ID},
            {"p", dmEvent.PubKey},
            {"k", "14"},
        },
        Content: "",
    }
}

Rust Example

pub fn make_read_receipt(dm_event: &Value, my_pubkey: &str) -> Value {
    json!({
        "kind": 1499,
        "created_at": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(),
        "pubkey": my_pubkey,
        "tags": [
            ["e", dm_event["id"]],
            ["p", dm_event["pubkey"]],
            ["k", "14"]
        ],
        "content": ""
    })
}

Kotlin Example

fun makeReadReceipt(dmEvent: NostrEvent, myPubkey: String): NostrEvent {
    return NostrEvent(
        kind = 1499,
        created_at = System.currentTimeMillis() / 1000,
        pubkey = myPubkey,
        tags = listOf(
            listOf("e", dmEvent.id),
            listOf("p", dmEvent.pubkey),
            listOf("k", "14")
        ),
        content = ""
    )
}

Copyright

Public domain. Breath and use at will. Like air.