This is a Nostr 101, but more technical! Nostr operates without a central server, relying on clients (user applications) and relays (servers) to manage data. Clients are intentionally smart, whilst relays are intentionally ‘dumb’. Anyone can build a client and anyone can run a relay. Every user has a public key for identification, and all posts, known as events, are signed to ensure they are authentic.
Event Structure
At the heart of Nostr are events, which are JSON objects with specific fields:
- ID: A unique hash of the event data.
- Public Key: Identifies the user who created the event.
- Timestamp: When the event was created.
- Kind: Defines the event type, like a text note or user metadata.
- Tags: Additional context, such as references to other events or users.
- Content: The main text or data of the event.
- Signature: Verifies the event’s authenticity using cryptography.
These events are signed with the user’s private key, ensuring that only the owner could have created them, which helps maintain trust in a decentralized system.
Event Kinds
Relays act like databases, storing events and distributing them to clients based on their requests. Events are categorized by “kinds,” which determine how relays handle them. Some examples:
- Kind 0 is for user metadata, like profile information.
- Kind 1 is for short text notes, similar to social media posts.
- Kind 3 is for contacts or ‘follow lists’
- Kind 6 is for reposts
There are many more kinds defined by the protocol. A full list can be found on the Nostr GitHub repository.
Event Kinds and Relay Behavior
Event kinds categorize events and dictate how relays handle them, providing flexibility for various use cases: Events are further classified by kind ranges, influencing storage and replacement policies:
Kind Range | Type | Example Kind |
---|---|---|
0-9999 | Regular: Standardized Kinds | Kind 1 (text note) |
10000-19999 | Replaceable Events | Kind 10,000 (mute list) |
20000-29999 | Ephemeral Events | Kind 20200 (temp status update) |
30000-39999 | Parameterized Replaceable Events | Kind 30001 (Bookmarks or categorized posts) |
40000-49999 | Application specific or custom use | TBC |
Event Structure
Nostr events are simple JSON objects with a defined structure. The fields include:
Field | Description |
---|---|
id | 32-bytes lowercase hex-encoded SHA256 hash of the serialized event data |
pubkey | 32-bytes lowercase hex-encoded public key of the event creator |
created_at | Unix timestamp in seconds |
kind | Integer between 0 and 65535, specifying event type |
tags | Array of arrays, each with strings, e.g., ["e", "event_id", "relay_url"] |
content | Arbitrary string, the main content of the event |
sig | 64-bytes lowercase hex of the signature, matching the id |
The event is serialized using UTF-8 encoding. The id
is the SHA256 hash of this serialized string, and the sig
is a Schnorr signature on the ‘secp256k1’ curve, verifying the event’s authenticity. This approach, detailed in here, ensures that only the holder of the corresponding private key could have created the event.
Event JSON Example
{
"content": "Whats the most exciting thing happening in and around Nostr right now?\n\n#AskNostr",
"created_at": 1740858071,
"id": "a4eaed043dc40569dabfcd99f53aad8dd6e9450ed64eb61be005db0ec645b042",
"kind": 1,
"pubkey": "a60e79e0edad5100d7543b669e513dbc1c2170e8e9b74fdb8e971afd1e0e6813",
"sig": "8f9a27944518e93469eddbbce1659fe53ac903d78091a491dc083ce803a6a3cd00c5fcbec2954ea09a6c2617387f05d01eb063b6e96f57e1297aa101dc44e2a5",
"tags": [["t", "AskNostr"]]
}
Event Tags
Tags enhance event functionality and are what make Nostr more than just a stream of notes. Clients interpret tags to render replies, mentions, or hashtags in the UI. Relays index tags for filtering and querying events (e.g., fetch all events with t:AskNostr).
"e"
: Refers to another event, e.g.,["e", "event_id", "relay_url"]
, for threading or replies."p"
: Refers to a user, e.g.,["p", "pubkey"]
, for mentions or follows."a"
: Refers to an addressable event, e.g.,["a", "kind:pubkey:d_tag", "relay_url"]
, for specific updates.


Communication Protocol
WebSockets enable real-time communication between clients and relays using JSON-formatted messages: clients publish events like notes or metadata with ["EVENT", <event>]
, subscribe to specific event streams (e.g., posts with a hashtag) using ["REQ", <subscription-id>, <filters>]
, and receive matching events from relays as ["EVENT", <subscription-id>, <event>]
, with relays signaling the end of a data stream via ["EOSE", <subscription-id>]
; clients can also terminate subscriptions with ["CLOSE", <subscription-id>]
. all encoded in UTF-8 text for seamless, bidirectional updates.
Disclaimer
I am by no means a developer, let alone one that specializes in the Nostr protocol. Much of this page is a distillation of my rudimentary understanding, combined with research from other more technical writings about the protocol. If you spot any mistakes or gaping holes, please get in touch.
If you found this post useful, please share it with your peers and consider following and zapping me on Nostr. If you write to me and let me know that you found me via this post, I’ll be sure to Zap you back! ⚡️