NIP-100 Title: Read Receipts for Encrypted Direct Messages Author: npub1lgp6j9rrumk347vyx8rq9ycvqg4vwasugtqx4wup2tgqemhxj27sxxw02m Status: Draft Category: Communication Created: 2025-12-02
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.
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.
Read receipt events use:
kind: 1499
A kind 1499 event asserts that the author claims to have viewed the referenced encrypted direct message.
| 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) |
The content field MUST be an empty string:
""
{
"kind": 1499,
"created_at": 1735689000,
"pubkey": "npub1reader...",
"tags": [
["e", "7e8bf74059..."],
["p", "npub1xyz..."],
["k", "14"]
],
"content": ""
}
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.
A client MAY display a read receipt if:
- the referenced DM exists locally,
- the DM is decryptable by the user, and
- the receipt is authored by the intended recipient.
Clients SHOULD ignore receipts referencing unknown or undecryptable messages, or those with malformed tags.
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.
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.
- 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.
A basic client implementation requires:
- Detecting when decrypted DM content becomes visible.
- Publishing a kind 1499 event referencing the DM event ID.
- Optionally marking messages as “read” when corresponding receipts are observed.
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": ""
}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: ""
};
}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: "",
}
}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": ""
})
}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 = ""
)
}Public domain. Breath and use at will. Like air.