1 Overview
Nostr has only two kinds of programs. A client creates and reads messages. A relay stores and forwards messages. This NIP defines how they talk.
The protocol has one object type: the event. A user signs each event with a private key. Anyone can verify the signature with the matching public key. No server owns your identity. No server owns your data.
Clients and relays exchange events over a plain WebSocket connection. A client sends three message types. A relay answers with five message types. That is the whole protocol.
New to Nostr? Learn this chapter first. All other NIPs add optional features on top of these rules. If a term looks unfamiliar later in the manual, it is probably defined here.
2 The Event Object
Every user has a keypair on the curve secp256k1. Signatures and public keys follow the BIP-340 Schnorr standard. An event has this exact wire format:
{
"id": <32-byte lowercase hex sha256 of the serialized event>
"pubkey": <32-byte lowercase hex public key of the creator>
"created_at": <unix timestamp, seconds>
"kind": <integer, 0 – 65535>
"tags": [[<string>, ...], ...]
"content": <arbitrary string>
"sig": <64-byte lowercase hex Schnorr signature of the id>
}
| Field | Type | Rule |
|---|---|---|
| id | hex string, 64 chars | The sha256 hash of the serialized event. Computed, not chosen. |
| pubkey | hex string, 64 chars | The public key of the creator. Lowercase hex. |
| created_at | integer | Unix timestamp in seconds. Set by the creator. |
| kind | integer | 0 to 65535. Tells clients how to interpret the event. See §4. |
| tags | array of arrays | Each tag is an array of one or more strings. See §3. |
| content | string | Arbitrary payload. Meaning depends on the kind. |
| sig | hex string, 128 chars | Schnorr signature of the id hash, made with the creator's key. |
Serialization: how to compute the ID
To get event.id, serialize the event into a UTF-8 JSON array. Then hash the string with sha256. The array has this fixed shape:
[0, pubkey, created_at, kind, tags, content]
Every implementation must produce the same bytes for the same event. Follow these rules exactly:
- Encode with UTF-8.
- Do not add whitespace, line breaks, or any formatting.
- In
content, escape only these characters: line break as\n, double quote as\", backslash as\\, carriage return as\r, tab as\t, backspace as\b, form feed as\f. Output all other characters verbatim.
Do not use your language's default JSON escaper. Many escape more characters than the list above (for example / or non-ASCII). One extra escape changes the hash, and the event ID will not verify.
id and sig fields are outputs, never inputs.3 Tags
A tag is an array of strings. The first string is the tag name. The second string is the tag value. Later elements have no fixed name.
"tags": [ ["e", "5c83da77af1d…226f36", "wss://nostr.example.com"], ["p", "f7234bd4c139…76e9ca"], ["a", "30023:f7234bd4c139…76e9ca:abcd", "wss://nostr.example.com"], ["alt", "reply"] ]
This NIP defines three standard tags. They keep the same meaning in every event kind:
| Tag | Points to | Full form |
|---|---|---|
| e | another event | ["e", <event id>, <relay URL, optional>, <author pubkey, optional>] |
| p | another user | ["p", <pubkey>, <relay URL, optional>] |
| a | an addressable or replaceable event | ["a", "<kind>:<pubkey>:<d value>", <relay URL, optional>] — for a normal replaceable event, leave the d value empty but keep the trailing colon. |
Relays index every tag whose name is one English letter (a–z, A–Z). Only the first value of each tag is indexed. Query indexed tags with filter keys like {"#e": [<event id>]}.
4 Kinds and Storage Classes
The kind number tells a client what an event means. It also tells a relay how to store the event. A tag can mean different things in different kinds. Each NIP may claim unused kind numbers and define their meaning.
This NIP defines one kind. Kind 0 — user metadata: the content is a stringified JSON object such as {"name": …, "about": …, "picture": …} that describes the author. NIP-24 lists extra metadata fields. A relay may delete an older kind-0 event when it receives a newer one from the same pubkey.
- Regular events: relays are expected to store all of them.
- Replaceable events: for each
pubkey+kindpair, relays must keep only the latest event. They may discard older versions. - Ephemeral events: relays are not expected to store them at all.
- Addressable events: for each
kind+pubkey+d-tag-value triple, relays must keep only the latest event.
Two replaceable events with the same timestamp? Keep the one whose id sorts first in lexical order. When a client requests replaceable events, the relay should return only the latest version, even if it stored several.
5 Client ↔ Relay Messages
A relay exposes one WebSocket endpoint. A client should open a single connection per relay and reuse it for all subscriptions. A relay may limit connections per IP or client. A relay must accept connections on only one endpoint when extra path segments do not change its behavior.
| Message | Shape | Purpose |
|---|---|---|
| EVENT | ["EVENT", <event>] | Publish an event. |
| REQ | ["REQ", <sub_id>, <filter1>, <filter2>, …] | Request stored events and subscribe to new ones. |
| CLOSE | ["CLOSE", <sub_id>] | Stop a subscription. |
<sub_id> is a non-empty string, at most 64 characters. It identifies a subscription per connection. Relays must track subscription IDs separately for each WebSocket connection; the IDs are not globally unique. A new REQ with the same ID replaces the old subscription.
Filters
{
"ids": <list of event ids>
"authors": <list of lowercase pubkeys>
"kinds": <list of kind numbers>
"#<letter>": <list of tag values, e.g. "#e": event ids>
"since": <unix ts — match if created_at >= since>
"until": <unix ts — match if created_at <= until>
"limit": <max events in the initial query>
}
Filter logic follows four rules:
- Inside one list, values are OR: one match is enough.
- Between attributes of one filter, conditions are AND: all must match.
- Between multiple filters in one
REQ, filters are OR: any filter may match. ids,authors,#e, and#pmust contain exact 64-character lowercase hex values.
The limit attribute applies only to the initial query and is ignored afterwards. The relay returns the last limit events ordered by created_at, newest first; ties sort by lowest id first. Returning fewer events is acceptable. Returning many more is not: it overwhelms clients.
| Message | Shape | Purpose |
|---|---|---|
| EVENT | ["EVENT", <sub_id>, <event>] | Deliver an event that matches a subscription. Sent only for subscriptions the client opened. |
| OK | ["OK", <event_id>, true|false, <message>] | Accept or reject a client's EVENT. Mandatory reply. |
| EOSE | ["EOSE", <sub_id>] | End of stored events. Everything after this is real-time. |
| CLOSED | ["CLOSED", <sub_id>, <message>] | The relay refused or terminated a subscription on its side. |
| NOTICE | ["NOTICE", <message>] | Human-readable errors and other information. No rules defined. |
The OK and CLOSED messages carry a machine-readable status. The message string starts with a single-word prefix, then :, then a human-readable text. When OK is true, the message may be empty. The standard prefixes are:
| Prefix | Meaning |
|---|---|
| duplicate | The relay already has this event. |
| pow | Proof-of-work status (see NIP-13). |
| blocked | The relay refuses this pubkey or event. |
| rate-limited | Slow down. |
| invalid | The event fails validation. |
| restricted | The client is not allowed to write. |
| mute | Nobody was listening to this ephemeral event; it was ignored. |
| error | Anything else, such as an internal failure. |
EOSE, then real-time delivery until CLOSE.6 Worked Example
The session below shows one full round trip. Client A publishes a kind-1 note. Client B subscribes and receives it. Orange lines go to the relay. Blue lines come from the relay.
-- Client A publishes -- → ["EVENT", {"id":"b1a649ebe8…","pubkey":"22a12a12…","created_at":1749762600, "kind":1,"tags":[],"content":"Running nostr.","sig":"e8551d85…"}] ← ["OK", "b1a649ebe8…", true, ""] -- Client B subscribes -- → ["REQ", "feed", {"kinds":[1], "authors":["22a12a12…"], "limit":10}] ← ["EVENT", "feed", {"id":"b1a649ebe8…", "kind":1, "content":"Running nostr.", …}] ← ["EOSE", "feed"] -- later, new matching events stream in live -- ← ["EVENT", "feed", {"id":"9f2c11ab…", …}] → ["CLOSE", "feed"] -- a rejection looks like this -- ← ["OK", "d2b649ffa1…", false, "rate-limited: slow down there chief"] ← ["CLOSED", "feed2", "unsupported: filter contains unknown elements"]
7 Version History
NIP-01 is a living document. It started as a short note by fiatjaf and has absorbed several other NIPs over time. Notable changes, newest first:
- 20262026-05Clarified that relays must accept connections on a single endpoint when path segments do not change behavior.
- 20252025-07Added the
mutestatus prefix for ignored ephemeral events. - 20252025-02Added the optional author pubkey as the 4th element of the
etag; added therestrictedprefix. - 20242024Clarified
limitordering, subscription-ID scope, and the trailing colon inatags for replaceable events. - 20232023-06Absorbed NIP-16 (event treatment), NIP-20 (
OKcommand results), and NIP-33 (addressable events). Kind ranges and storage classes became part of the core. - 20232023-01Absorbed NIP-12: generic single-letter tag queries became core filter behavior.
- 20222022-05Migrated into the dedicated
nostr-protocol/nipsrepository. Thelimitfilter attribute arrived days later. - Origin2020-11fiatjaf published the first protocol description in the original
fiatjaf/nostrrepository.
Original Proposal - Credits
| Author | fiatjaf |
| First commit | 2022-05-01 — “migrate nips from main nostr repo” (spec predates the repo, 2020) |
| Status | draft - mandatory — every client and relay must implement it |
| Source | 01.md on GitHub - full change history |
8 See Also
- NIP-10 — defines
kind:1text notes and reply threading. - NIP-19 — bech32 encodings (
npub,nsec,note) for the raw hex keys and IDs used here. - NIP-11 — the relay information document; how a relay describes its limits.
- NIP-24 — extra fields for the kind-0 metadata object.
- NIP-42 — authentication of clients to relays on top of this flow.