Nostr Events Explained: Complete Technical Guide
Deep dive into Nostr events - structure, kinds, signatures, tags, and how events flow through the decentralized network.
Introduction
Everything you do on Nostr—posting, liking, messaging, following—is an event. Understanding events is understanding Nostr at the protocol level.
This guide explains event structure, types, how they’re created and verified, and why this simple design makes Nostr powerful.
What is a Nostr Event?
The Core Concept
An event is a signed piece of data representing an action or content on Nostr.
Examples of Events:
- A post you write (kind:1)
- A reaction/like (kind:7)
- Your profile information (kind:0)
- Your follow list (kind:3)
- An encrypted message (kind:4)
- A deletion request (kind:5)
Key Characteristics:
- Events are immutable (once signed, content can’t change)
- Events are verifiable (signature proves authorship)
- Events are portable (any relay can store them)
- Events are self-contained (include all necessary metadata)
The Event Structure
Every Nostr event follows this JSON structure:
{
"id": "4b697394206de8e4c68b3d695d7b9e6d5d1e9e3b4c5f9d7e8a6b3c4d5e6f7a8b",
"pubkey": "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
"created_at": 1674055201,
"kind": 1,
"tags": [
["e", "referenced_event_id", "wss://relay.example.com"],
["p", "mentioned_pubkey"]
],
"content": "Hello Nostr! This is my first post.",
"sig": "signature_hex_here"
}
Let’s break down each field.
Event Fields Explained
1. ID (Event Identifier)
"id": "4b697394206de8e4c68b3d695d7b9e6d5d1e9e3b4c5f9d7e8a6b3c4d5e6f7a8b"
What it is:
- SHA-256 hash of serialized event data
- 64 hexadecimal characters
- Unique identifier for this event
How it’s calculated:
- Serialize event data (pubkey, created_at, kind, tags, content)
- SHA-256 hash the serialized JSON
- Result is the event ID
Purpose:
- Unique reference to this event
- Used for replies, quotes, reactions
- Prevents duplicates
- Verifies event integrity
Format (NIP-19):
- Raw:
4b697394206de8e4c68b3d695d7b9e6d5d1e9e3b4c5f9d7e8a6b3c4d5e6f7a8b - Bech32:
note1fd5h8dpqm6xwf35t84v44au7d4w3n03mfh0e6l52dvly6hnk0zdqz4dgt9
2. Pubkey (Author’s Public Key)
"pubkey": "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"
What it is:
- The author’s public key (64 hex characters)
- Identifies who created this event
- Used to verify the signature
Purpose:
- Proves authorship (via signature verification)
- Links event to an identity
- Allows filtering by author
Format:
- Raw hex:
3bf0c63... - Bech32 (npub):
npub180cvv83m27afv2pzhd97pl3aw5lu729x0cmnj7zv0edxcnjle67q23pczl
3. Created_at (Timestamp)
"created_at": 1674055201
What it is:
- Unix timestamp (seconds since Jan 1, 1970 UTC)
- When the event was created
- Set by the author’s client
Purpose:
- Chronological ordering
- Time-based filtering
- Determining recency
Important Notes:
- Client-side timestamp (not verified by relays)
- Can be set to any value (future or past)
- Relays may reject events with unreasonable timestamps
- Used for sorting feeds
Example: 1674055201 = January 18, 2023, 17:13:21 UTC
4. Kind (Event Type)
"kind": 1
What it is:
- Integer indicating event type
- Defines how to interpret the event
- Different kinds serve different purposes
Purpose:
- Categorizes events
- Clients filter by kind
- Determines event handling
Common Kinds (we’ll cover this in detail below)
5. Tags (Metadata Array)
"tags": [
["e", "referenced_event_id", "wss://relay.example.com", "reply"],
["p", "mentioned_pubkey"],
["t", "nostr"],
["t", "bitcoin"]
]
What it is:
- Array of arrays (each tag is an array)
- Metadata about the event
- References to other events, users, topics
Purpose:
- Link events together (replies, quotes)
- Mention users
- Add hashtags
- Provide context
- Relay hints
Tag Structure:
- First element: tag type (letter)
- Remaining elements: tag values
- Variable length
Common Tags (we’ll cover in detail below)
6. Content
"content": "Hello Nostr! This is my first post."
What it is:
- The actual content/message
- Always a string
- Can be empty
Format Depends on Kind:
- kind:1 (note): Plain text, Markdown, or formatted text
- kind:0 (profile): JSON metadata (name, about, picture)
- kind:4 (DM): Encrypted string
- kind:30023 (long-form): Markdown article
- kind:7 (reaction): Emoji or ”+” for like
Encoding:
- UTF-8 string
- Can include emojis, special characters
- Clients may support Markdown, HTML (rendering varies)
7. Sig (Signature)
"sig": "3045022100d8c6e8f7a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7..."
What it is:
- Schnorr signature (secp256k1)
- Signs the event ID with the author’s private key
- 128 hexadecimal characters
Purpose:
- Proves the author created this event
- Prevents tampering (changing any field breaks signature)
- Allows verification without trusting relays
How it Works:
- Author’s client creates event
- Client calculates event ID (hash)
- Client signs ID with private key
- Signature added to event
- Recipients verify signature using author’s public key
Verification:
Valid Signature = Sign(Event ID, Private Key)
Verification = Verify(Event ID, Signature, Public Key)
If verification succeeds, the event is authentic and unmodified.
Event Kinds (Types)
Event kind determines what the event represents.
Regular Events (Replaceable)
kind:0 - Profile Metadata
Stores user profile information.
{
"kind": 0,
"content": "{\"name\":\"Alice\",\"about\":\"Nostr enthusiast\",\"picture\":\"https://example.com/pic.jpg\"}",
"tags": []
}
Characteristics:
- Replaceable: Newest overwrites older
- Only one kind:0 per pubkey matters (latest)
- JSON string in content field
Content Fields (common):
name: Display nameabout: Bio/descriptionpicture: Profile picture URLbanner: Banner image URLnip05: NIP-05 verification (email-like identifier)lud16: Lightning address (for zaps)website: Personal website
kind:3 - Contact List (Follow List)
Your list of followed public keys.
{
"kind": 3,
"content": "",
"tags": [
["p", "pubkey1", "wss://relay1.com", "Alice"],
["p", "pubkey2", "wss://relay2.com", "Bob"]
]
}
Characteristics:
- Replaceable: Newest overwrites
- Tags list followed pubkeys
- Content usually empty (or relay list)
Tag Structure:
["p", "pubkey", "relay_hint", "petname"]- petname: optional friendly name
Ephemeral Events
kind:20000-29999 - Ephemeral Events
Events not meant to be stored.
Examples:
- kind:20001 - Authentication events
- kind:22242 - Client authentication
Characteristics:
- Relays may not store them
- Meant for real-time communication
- Short-lived
Regular Events (Non-Replaceable)
kind:1 - Short Text Note
Standard social media post.
{
"kind": 1,
"content": "This is a note!",
"tags": [
["t", "nostr"]
]
}
Characteristics:
- Most common event type
- Plain text (clients may render Markdown)
- Can include mentions, hashtags, links
kind:4 - Encrypted Direct Message
Private message (NIP-04 encrypted).
{
"kind": 4,
"content": "encrypted_string_here",
"tags": [
["p", "recipient_pubkey"]
]
}
Characteristics:
- Content encrypted using recipient’s public key
- Only sender and recipient can decrypt
- Metadata (who messaged whom) is public
- See Privacy Guide for limitations
kind:5 - Event Deletion
Request to delete previous event.
{
"kind": 5,
"tags": [
["e", "event_id_to_delete"]
]
}
Characteristics:
- Requests deletion of your own events
- Relays may honor or ignore
- Not enforceable (some relays will keep event)
- Best effort deletion
kind:6 - Repost
Sharing another’s event (like retweet).
{
"kind": 6,
"content": "{original_event_json}",
"tags": [
["e", "original_event_id"],
["p", "original_author_pubkey"]
]
}
kind:7 - Reaction
Like or emoji reaction.
{
"kind": 7,
"content": "+",
"tags": [
["e", "reacted_event_id"],
["p", "reacted_event_author"]
]
}
Content:
"+"= like/upvote"-"= dislike- Emoji = custom reaction (❤️, 😂, etc.)
Long-Form Content
kind:30023 - Long-Form Article
Blog post, article, long content.
{
"kind": 30023,
"content": "# Article Title\n\nMarkdown content here...",
"tags": [
["d", "article-slug"],
["title", "Article Title"],
["published_at", "1674055201"],
["t", "nostr"],
["t", "bitcoin"]
]
}
Characteristics:
- Parameterized Replaceable (by “d” tag)
- Markdown content
- Tags include title, publish timestamp
- Used by Habla, Highlighter clients
Other Kinds
kind:40 - Channel Creation
Create public chat channel.
kind:41 - Channel Metadata
Update channel info.
kind:42 - Channel Message
Message in public channel.
kind:9735 - Zap Receipt
Proof of Lightning payment (zap).
kind:10000+ - Special Events
Various application-specific events.
See NIPs documentation for complete list.
Tags Explained
Tags provide metadata and link events together.
Common Tag Types
“e” Tag - Event Reference
References another event.
["e", "event_id", "relay_hint", "marker"]
Components:
"e"- Tag type- Event ID (hex or note1…)
- Relay URL (optional, where to find event)
- Marker (optional): “reply”, “root”, “mention”
Usage:
- Replies: Tag parent event
- Quotes: Reference quoted event
- Threads: Link to root event
Example (reply to event):
["e", "original_event_id", "wss://relay.damus.io", "reply"]
“p” Tag - Pubkey Reference
Mentions or references a user.
["p", "pubkey", "relay_hint"]
Usage:
- Mention user in note
- Tag author of replied/quoted event
- DM recipient
- Follow list entries
Example:
["p", "3bf0c63fcb...", "wss://relay.damus.io"]
“t” Tag - Hashtag/Topic
Categorizes event by topic.
["t", "nostr"]
Usage:
- Hashtags for discovery
- Topic indexing
- Search optimization
Example (multiple tags):
"tags": [
["t", "nostr"],
["t", "bitcoin"],
["t", "censorship-resistance"]
]
“d” Tag - Identifier (Parameterized Replaceable)
Unique identifier for replaceable events.
["d", "article-slug"]
Usage:
- Identify which long-form article to replace
- Allow multiple replaceable events of same kind
- Create “addressable” events
Example:
- kind:30023 with
["d", "my-first-article"] - Later update: same kind + same “d” tag → replaces first
Other Tags:
["a", "kind:pubkey:d-tag"]- Addressable event reference["r", "url"]- URL reference["g", "geohash"]- Geolocation["nonce", "value", "difficulty"]- Proof of work["subject", "text"]- Subject line (DMs)["title", "text"]- Article title
Custom Tags: Apps can define their own
Event Lifecycle
Creation
Step 1: Client Creates Event
User interacts with client (writes post, clicks like, etc.)
Step 2: Client Builds Event Object
const event = {
pubkey: user_pubkey,
created_at: Math.floor(Date.now() / 1000),
kind: 1,
tags: [],
content: "Hello Nostr!"
};
Step 3: Calculate Event ID
const event_data = JSON.stringify([
0,
event.pubkey,
event.created_at,
event.kind,
event.tags,
event.content
]);
const event_id = sha256(event_data);
event.id = event_id;
Step 4: Sign Event
event.sig = sign(event.id, user_private_key);
Step 5: Publish to Relays
Client sends signed event to configured relays.
Relay Storage
Relay Receives Event:
-
Verify Signature:
- Extract pubkey
- Verify signature matches event ID
- Reject if invalid
-
Validate Event:
- Check required fields present
- Verify timestamp reasonable
- Apply relay policies (content rules, rate limits)
-
Store Event:
- Save to database
- Index by pubkey, kind, tags, timestamp
-
Respond to Client:
["OK", "event_id", true, ""]Or:
["OK", "event_id", false, "error: reason"] -
Distribute to Subscribers:
- Send event to clients subscribed to matching filters
Distribution
Clients Subscribe:
["REQ", "subscription_id", {"kinds": [1], "authors": ["pubkey1", "pubkey2"]}]
Relay Sends Matching Events:
["EVENT", "subscription_id", {event_object}]
Client Receives:
- Verify signature
- Process event (display, store locally, etc.)
- Update UI
Verification
Anyone Can Verify:
Every client verifies every event signature:
1. Extract pubkey from event
2. Recalculate event ID from event data
3. Verify signature: verify(event.id, event.sig, event.pubkey)
4. If valid: event is authentic
5. If invalid: reject event
This Prevents:
- Relays forging events
- Modified events
- Impersonation
Trust Model: Don’t trust relays, trust cryptography.
Special Event Behaviors
Replaceable Events
Kinds: 0, 3, and 10000-19999
Behavior:
- Only the newest event of this kind (per pubkey) is valid
- Older events discarded
- Allows updating profile, follow list, etc.
Example: Two kind:0 events from same pubkey
- First:
created_at: 1674050000 - Second:
created_at: 1674060000 - Only second is valid (newer timestamp)
Parameterized Replaceable Events
Kinds: 30000-39999
Behavior:
- Replaceable based on kind + pubkey + “d” tag
- Allows multiple replaceable events of same kind
- Each “d” tag value creates separate replaceable set
Example: Long-form articles (kind:30023)
- Article 1: kind:30023, d:“first-article”
- Article 2: kind:30023, d:“second-article”
- Update Article 1: kind:30023, d:“first-article” (newer timestamp)
- Two articles coexist, each replaceable independently
Ephemeral Events
Kinds: 20000-29999
Behavior:
- Relays may not store
- Used for real-time, temporary data
- No expectation of persistence
Example: Authentication challenges
Advanced Event Features
Event Deletion (NIP-09)
Request deletion of your own events.
Deletion Event:
{
"kind": 5,
"tags": [
["e", "event_id_to_delete"],
["e", "another_event_id"]
],
"content": "Reason for deletion (optional)"
}
Relay Behavior (varies):
- May delete events
- May keep events
- No enforcement mechanism
- Best effort
Implication: Nostr deletions are requests, not guarantees.
Proof of Work (NIP-13)
Optional difficulty requirement.
Purpose:
- Anti-spam measure
- Relays can require computational work
Implementation:
"tags": [
["nonce", "1234567", "20"]
]
How It Works:
- Increase nonce until event ID has required leading zeros
- Higher difficulty = more computation
- Relays reject events not meeting difficulty
Usage: Limited currently, experimental
Delegation (NIP-26)
Allow another key to post on your behalf.
Use Case:
- Bot posting for you
- Temporary delegation
- Multi-device signing
Implementation: Creates delegation certificate signed by primary key
Status: Specified but limited adoption
Working with Events
Filtering Events
Clients filter events when subscribing:
Filter Examples:
All notes from specific authors:
{"kinds": [1], "authors": ["pubkey1", "pubkey2"]}
Events mentioning you:
{"kinds": [1, 6, 7], "#p": ["your_pubkey"]}
Events with hashtag:
{"kinds": [1], "#t": ["nostr"]}
Recent events:
{"kinds": [1], "since": 1674050000, "limit": 50}
Specific event:
{"ids": ["event_id"]}
Event Limits and Pagination
Limit Parameter:
{"kinds": [1], "limit": 20}
Returns maximum 20 events.
Pagination (via timestamps):
{"kinds": [1], "until": 1674050000, "limit": 20}
Fetch events before timestamp, useful for infinite scroll.
Conclusion
Events are the atomic unit of Nostr—signed, verifiable, portable pieces of data flowing through a decentralized network.
Key Takeaways:
- Events are self-contained: Everything needed is in the event
- Events are immutable: Signatures prevent tampering
- Events are portable: Any relay can store/distribute
- Events are verifiable: Cryptography, not trust
- Event kinds define purpose and behavior
Understanding events means understanding Nostr at the protocol level. Everything builds on this simple, powerful foundation.
Your posts are events. Your likes are events. Your identity is events.
That’s the beauty of Nostr.
Further Resources
- How Nostr Works - High-level protocol explanation
- NIP-01 - Basic protocol and event structure
- NIP-10 - Text note references (replies, threads)
- NIP-25 - Reactions
- All NIPs - Complete protocol specifications
Remember: Events are simple JSON objects, signed and verified. This simplicity enables everything Nostr does. ⚡