Skip to main content

Events

Events are the atomic unit of data in Nostr. Every piece of information—posts, profiles, reactions, follows—is represented as an event.

Event Structure​

{
"id": "4376c65d2f232afbe9b882a35baa4f6fe8667c4e684749af565f981833ed6a65",
"pubkey": "6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93",
"created_at": 1673347337,
"kind": 1,
"tags": [
["e", "3da979448d9ba263864c4d6f14984c423a3838364ec255f03c7904b1ae77f206"],
["p", "bf2376e17ba4ec269d10fcc996a4746b451152be9031fa48e74553dde5526bce"]
],
"content": "This is a reply to another note!",
"sig": "908a15e46fb4d8675bab026fc230a0e3542bfade63da02d542fb78b2a8513fcd..."
}

Field Details​

FieldTypeDescription
idstring32-byte lowercase hex SHA256 hash of the serialized event
pubkeystring32-byte lowercase hex public key of the event creator
created_atintegerUnix timestamp in seconds
kindintegerEvent type identifier (0-65535)
tagsarrayArray of arrays containing tag data
contentstringArbitrary string content (interpretation depends on kind)
sigstring64-byte lowercase hex Schnorr signature

Computing the Event ID​

The event ID is a SHA256 hash of the serialized event data:

const serialized = JSON.stringify([
0, // Reserved for future use
event.pubkey, // Author's public key
event.created_at, // Unix timestamp
event.kind, // Event kind
event.tags, // Tags array
event.content // Content string
]);

const id = sha256(serialized);

The serialization must follow these rules:

  • No whitespace
  • UTF-8 encoding
  • No escaping of Unicode characters
  • Numbers without quotes

Signing Events​

Events are signed using Schnorr signatures (BIP-340):

import { schnorr } from '@noble/curves/secp256k1';

const signature = schnorr.sign(eventId, privateKey);

The signature covers only the event ID (which is a hash of all other fields).

Tags​

Tags are arrays of strings that add metadata to events.

Standard Tag Types​

TagFormatDescription
e["e", "<event-id>", "<relay-url>", "<marker>"]Reference to another event
p["p", "<pubkey>", "<relay-url>", "<petname>"]Reference to a user
a["a", "<kind>:<pubkey>:<d-tag>", "<relay-url>"]Reference to replaceable event
d["d", "<identifier>"]Identifier for replaceable events
t["t", "<hashtag>"]Hashtag
r["r", "<url>"]Reference to URL
nonce["nonce", "<nonce>", "<target>"]Proof of work
subject["subject", "<subject>"]Subject/title
expiration["expiration", "<timestamp>"]Expiration time

Event Reference Markers​

The e tag can include markers for threading:

["e", "<event-id>", "<relay>", "root"]    // Thread root
["e", "<event-id>", "<relay>", "reply"] // Direct parent
["e", "<event-id>", "<relay>", "mention"] // Mention (not a reply)

Example: Tagged Event​

{
"kind": 1,
"content": "Check out this article!",
"tags": [
["e", "abc123...", "wss://relay.example.com", "root"],
["p", "def456...", "wss://relay.example.com"],
["t", "nostr"],
["t", "development"],
["r", "https://example.com/article"]
]
}

Event Categories​

Events are categorized by how they should be stored:

Regular Events (most kinds)​

Stored by relays as independent events. Multiple events of the same kind from the same author are all kept.

Replaceable Events (kinds 0, 3, 10000-19999)​

Only the latest event (by created_at) is kept. Older events are discarded.

{
"kind": 0,
"content": "{\"name\": \"Alice\", \"about\": \"Nostr developer\"}",
"created_at": 1700000000
}

Parameterized Replaceable Events (kinds 30000-39999)​

Like replaceable, but identified by both kind and d tag. Each unique d value is a separate replaceable.

{
"kind": 30023,
"tags": [["d", "my-blog-post"]],
"content": "Long form article content...",
"created_at": 1700000000
}

Ephemeral Events (kinds 20000-29999)​

Should not be stored by relays. Used for real-time communication.

{
"kind": 20001,
"content": "User is typing...",
"created_at": 1700000000
}

Creating Events in Code​

JavaScript (nostr-tools)​

import { finalizeEvent, generateSecretKey } from 'nostr-tools/pure';

const sk = generateSecretKey();

const event = finalizeEvent({
kind: 1,
created_at: Math.floor(Date.now() / 1000),
tags: [
['t', 'nostr'],
['p', 'recipientPubkey']
],
content: 'Hello, Nostr!'
}, sk);

console.log(event);
// { id, pubkey, created_at, kind, tags, content, sig }

Rust (nostr-sdk)​

use nostr::prelude::*;

let keys = Keys::generate();
let event = EventBuilder::text_note("Hello, Nostr!")
.tags([Tag::hashtag("nostr")])
.sign_with_keys(&keys)?;

println!("{}", event.as_json());

Python (pynostr)​

from nostr.event import Event
from nostr.key import PrivateKey

private_key = PrivateKey()

event = Event(
kind=1,
content="Hello, Nostr!",
tags=[["t", "nostr"]]
)
event.sign(private_key.hex())

print(event.to_message())

Verifying Events​

Always verify events before trusting them:

import { verifyEvent } from 'nostr-tools/pure';

if (verifyEvent(event)) {
console.log('Valid event');
} else {
console.log('Invalid signature or ID');
}

Verification checks:

  1. Event ID matches the hash of serialized data
  2. Signature is valid for the claimed public key

Best Practices​

  1. Always verify incoming events before processing
  2. Use appropriate kinds for your content type
  3. Include relay hints in e and p tags when possible
  4. Set reasonable timestamps - don't backdate or future-date events
  5. Use markers for replies to enable proper threading

See Also​