Menu
Nostr Beginner 6 min read

Event Structure

Anatomy of Nostr events. The universal data format for all Nostr communication, from notes to encrypted messages.

events kind tags content signature JSON

Event Structure

Everything in Nostr is an event. Notes, profile updates, reactions, zaps, encrypted messages—all are events with the same basic structure. This simplicity is Nostr’s superpower.

Event Anatomy

Every event is a JSON object with exactly these fields:

{
  "id": "32-byte SHA256 hash of serialized event",
  "pubkey": "32-byte public key of event creator",
  "created_at": 1234567890,
  "kind": 1,
  "tags": [
    ["e", "referenced-event-id"],
    ["p", "referenced-pubkey"]
  ],
  "content": "Hello, Nostr!",
  "sig": "64-byte Schnorr signature"
}

Field Reference

FieldTypeDescription
idstring (64 hex)SHA256 hash of serialized event
pubkeystring (64 hex)Author’s public key
created_atintegerUnix timestamp (seconds)
kindintegerEvent type (0-65535)
tagsarrayMetadata and references
contentstringEvent payload (may be encrypted)
sigstring (128 hex)Schnorr signature over id

Event ID Calculation

The id is a SHA256 hash of the serialized event:

import json
import hashlib

def calculate_event_id(event):
    # Serialize in specific format
    serialized = json.dumps([
        0,                      # Reserved
        event["pubkey"],
        event["created_at"],
        event["kind"],
        event["tags"],
        event["content"]
    ], separators=(',', ':'), ensure_ascii=False)

    # SHA256 hash
    return hashlib.sha256(serialized.encode()).hexdigest()
import { sha256 } from '@noble/hashes/sha256';
import { bytesToHex } from '@noble/hashes/utils';

function calculateEventId(event) {
  const serialized = JSON.stringify([
    0,
    event.pubkey,
    event.created_at,
    event.kind,
    event.tags,
    event.content
  ]);
  return bytesToHex(sha256(new TextEncoder().encode(serialized)));
}

Event Kinds

The kind field determines how the event is interpreted:

Regular Events (ephemeral)

KindNameDescription
1Text NoteShort-form post
7ReactionLike, emoji reaction
1984ReportContent report
9735Zap ReceiptLightning payment proof

Replaceable Events

Only the latest event of this kind from a pubkey is valid:

KindNameDescription
0MetadataProfile (name, picture, about)
3ContactsFollowing list
10002Relay ListPreferred relays (NIP-65)

Parameterized Replaceable Events

Replaced by kind + pubkey + d-tag combination:

KindNameDescription
30000-39999CustomApplication-specific
30023Long-formArticles (NIP-23)

Ephemeral Events

Not stored by relays:

KindNameDescription
20000-29999EphemeralTyping indicators, etc.

Tags

Tags provide metadata and create relationships:

Common Tags

{
  "tags": [
    ["e", "event-id", "relay-url", "marker"],
    ["p", "pubkey", "relay-url"],
    ["a", "kind:pubkey:d-tag", "relay-url"],
    ["t", "hashtag"],
    ["d", "unique-identifier"],
    ["nonce", "12345", "21"]
  ]
}
TagPurposeExample
eReference eventReply to, quote
pReference pubkeyMention, notify
aReference replaceableLink to article
tHashtagTopic categorization
dIdentifierUnique ID for replaceable
nonceProof of workSpam prevention

Tag Markers (NIP-10)

For e tags in replies:

["e", "root-event-id", "", "root"],
["e", "reply-to-id", "", "reply"]
MarkerMeaning
rootOriginal post in thread
replyDirect parent being replied to
mentionJust a reference

Creating Events

Python Example

import json
import time
import hashlib
from secp256k1 import PrivateKey

def create_event(private_key_hex: str, kind: int, content: str, tags: list = None):
    private_key = PrivateKey(bytes.fromhex(private_key_hex))
    public_key = private_key.pubkey.serialize()[1:].hex()

    event = {
        "pubkey": public_key,
        "created_at": int(time.time()),
        "kind": kind,
        "tags": tags or [],
        "content": content
    }

    # Calculate ID
    serialized = json.dumps([
        0, event["pubkey"], event["created_at"],
        event["kind"], event["tags"], event["content"]
    ], separators=(',', ':'))
    event["id"] = hashlib.sha256(serialized.encode()).hexdigest()

    # Sign
    event["sig"] = private_key.schnorr_sign(
        bytes.fromhex(event["id"]), None
    ).hex()

    return event

# Create a text note
note = create_event(
    "your-private-key-hex",
    kind=1,
    content="Hello from Python!",
    tags=[["t", "python"], ["t", "nostr"]]
)

JavaScript Example

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

const sk = generateSecretKey();

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

console.log(event);

Verifying Events

Always verify before trusting:

from secp256k1 import PublicKey

def verify_event(event):
    # Recalculate ID
    serialized = json.dumps([
        0, event["pubkey"], event["created_at"],
        event["kind"], event["tags"], event["content"]
    ], separators=(',', ':'))
    expected_id = hashlib.sha256(serialized.encode()).hexdigest()

    if event["id"] != expected_id:
        return False, "ID mismatch"

    # Verify signature
    try:
        pubkey = PublicKey(bytes.fromhex("02" + event["pubkey"]), raw=True)
        if not pubkey.schnorr_verify(
            bytes.fromhex(event["id"]),
            bytes.fromhex(event["sig"]),
            None
        ):
            return False, "Invalid signature"
    except Exception as e:
        return False, str(e)

    return True, "Valid"

Event Lifecycle

Create → Sign → Publish → Relay stores → Query → Verify → Display
   │                          │
   └── Local                  └── May reject (invalid, spam, etc.)

Events are immutable once signed. To “edit”, publish a new event referencing the old one.

Content Encoding

The content field is always a string:

KindContent Format
0JSON object (name, about, picture)
1Plain text
4Encrypted (NIP-04, deprecated)
1059Gift-wrapped encrypted (NIP-59)
30023Markdown

Machine-Readable Summary

{
  "topic": "nostr-events",
  "audience": "ai-agents",
  "prerequisites": ["json", "sha256", "schnorr-signatures"],
  "key_concepts": [
    "event-id-calculation",
    "event-kinds",
    "tag-structure",
    "signature-verification"
  ],
  "code_examples": ["python", "javascript"],
  "related": [
    "/learn/nostr/keys",
    "/learn/nostr/signatures",
    "/learn/nostr/specs/nip-01"
  ]
}