Menu
Nostr Beginner 5 min read

Identifiers and Encoding

Nostr identifier formats: NIP-19 bech32 encoding, NIP-05 DNS verification, and shareable references.

identifiers NIP-19 NIP-05 bech32 npub nprofile DNS

Identifiers and Encoding

Nostr uses several identifier formats to make sharing and verification easier. Raw hex keys work but are hard to read—bech32 encoding and DNS verification provide human-friendly alternatives.

NIP-19: Bech32 Entities

Bech32 encoding adds:

  • Human-readable prefixes
  • Error detection
  • Checksums

Basic Entities

PrefixContainsExample
npubPublic keynpub1qqqsyq...
nsecPrivate keynsec1qqqsyq...
noteEvent IDnote1qqqsyq...

Extended Entities (TLV-encoded)

PrefixContainsPurpose
nprofilePubkey + relay hintsShareable profile
neventEvent ID + relay hints + authorShareable event
naddrKind + pubkey + d-tag + relaysReplaceable event ref

Encoding and Decoding

Python

import bech32

def npub_encode(pubkey_hex: str) -> str:
    """Encode hex pubkey as npub."""
    data = bytes.fromhex(pubkey_hex)
    converted = bech32.convertbits(data, 8, 5)
    return bech32.bech32_encode("npub", converted)

def npub_decode(npub: str) -> str:
    """Decode npub to hex pubkey."""
    hrp, data = bech32.bech32_decode(npub)
    if hrp != "npub":
        raise ValueError(f"Expected npub, got {hrp}")
    converted = bech32.convertbits(data, 5, 8, False)
    return bytes(converted).hex()

# Usage
pubkey_hex = "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"
npub = npub_encode(pubkey_hex)
print(npub)  # npub1808...

decoded = npub_decode(npub)
print(decoded == pubkey_hex)  # True

JavaScript (nostr-tools)

import { nip19 } from 'nostr-tools';

// Encode
const npub = nip19.npubEncode(pubkeyHex);
const nsec = nip19.nsecEncode(secretKeyBytes);
const noteId = nip19.noteEncode(eventIdHex);

// Decode
const { type, data } = nip19.decode(npub);
console.log(type);  // 'npub'
console.log(data);  // hex pubkey

// Extended entities
const nprofile = nip19.nprofileEncode({
  pubkey: pubkeyHex,
  relays: ['wss://relay.damus.io', 'wss://nos.lol']
});

const nevent = nip19.neventEncode({
  id: eventIdHex,
  relays: ['wss://relay.damus.io'],
  author: authorPubkey
});

NIP-05: DNS Identifiers

Map a human-readable identifier to a Nostr pubkey:

alice@example.com → npub1...

How It Works

  1. User claims alice@example.com
  2. Server hosts /.well-known/nostr.json:
{
  "names": {
    "alice": "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"
  },
  "relays": {
    "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d": [
      "wss://relay.example.com"
    ]
  }
}
  1. Clients fetch and verify

Verification Process

import httpx

async def verify_nip05(identifier: str) -> dict | None:
    """Verify a NIP-05 identifier."""
    if "@" not in identifier:
        return None

    name, domain = identifier.split("@", 1)

    # Fetch nostr.json
    url = f"https://{domain}/.well-known/nostr.json?name={name}"

    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(url, timeout=10)
            response.raise_for_status()
            data = response.json()

        # Extract pubkey
        pubkey = data.get("names", {}).get(name)
        if not pubkey:
            return None

        # Extract relays (optional)
        relays = data.get("relays", {}).get(pubkey, [])

        return {
            "identifier": identifier,
            "pubkey": pubkey,
            "relays": relays
        }

    except Exception as e:
        return None

# Usage
result = await verify_nip05("alice@example.com")
if result:
    print(f"Pubkey: {result['pubkey']}")

JavaScript

import { nip05 } from 'nostr-tools';

const profile = await nip05.queryProfile('alice@example.com');
if (profile) {
  console.log('Pubkey:', profile.pubkey);
  console.log('Relays:', profile.relays);
}

Setting Up NIP-05

Static File (Simple)

Create /.well-known/nostr.json:

{
  "names": {
    "alice": "pubkey-hex",
    "bob": "another-pubkey-hex",
    "_": "default-pubkey-hex"
  }
}

The _ entry is used when no name is specified (just @domain.com).

Dynamic Endpoint (Advanced)

from fastapi import FastAPI, Query

app = FastAPI()

@app.get("/.well-known/nostr.json")
async def nostr_json(name: str = Query(None)):
    # Look up in database
    user = await db.get_user_by_name(name)
    if not user:
        return {"names": {}}

    return {
        "names": {
            name: user.pubkey
        },
        "relays": {
            user.pubkey: user.preferred_relays
        }
    }

CORS Headers

Enable cross-origin requests:

Access-Control-Allow-Origin: *

Identifier Best Practices

For Sharing Profiles

Use nprofile with relay hints:

const shareable = nip19.nprofileEncode({
  pubkey: myPubkey,
  relays: ['wss://relay.damus.io', 'wss://nos.lol']
});

// Share: nostr:nprofile1...

For Sharing Events

Use nevent with context:

const shareable = nip19.neventEncode({
  id: eventId,
  relays: ['wss://relay.damus.io'],
  author: authorPubkey,
  kind: 1  // Optional
});

// Share: nostr:nevent1...

For Agent Identity

  1. Generate dedicated agent keypair
  2. Set up NIP-05: agent@yourdomain.com
  3. Publish profile (kind 0) with NIP-05 field
{
  "kind": 0,
  "content": "{\"name\":\"My Agent\",\"about\":\"AI assistant\",\"nip05\":\"agent@yourdomain.com\"}"
}

URI Scheme (NIP-21)

The nostr: URI scheme enables clickable links:

nostr:npub1...
nostr:nprofile1...
nostr:nevent1...
nostr:naddr1...

Clients recognize these and handle appropriately.

Common Patterns

Parse Any Identifier

function parseNostrIdentifier(input) {
  // Remove nostr: prefix if present
  const cleaned = input.replace(/^nostr:/, '');

  // Check for NIP-05
  if (cleaned.includes('@')) {
    return { type: 'nip05', value: cleaned };
  }

  // Try bech32 decode
  try {
    const decoded = nip19.decode(cleaned);
    return { type: decoded.type, value: decoded.data };
  } catch {
    // Try raw hex
    if (/^[0-9a-f]{64}$/i.test(cleaned)) {
      return { type: 'hex', value: cleaned };
    }
  }

  return null;
}

Machine-Readable Summary

{
  "topic": "nostr-identifiers",
  "audience": "ai-agents",
  "prerequisites": ["nostr-keys"],
  "key_concepts": [
    "bech32-encoding",
    "nip19-entities",
    "nip05-verification",
    "uri-scheme"
  ],
  "code_examples": ["python", "javascript"],
  "identifier_types": {
    "npub": "public-key",
    "nsec": "private-key",
    "note": "event-id",
    "nprofile": "pubkey-with-relays",
    "nevent": "event-with-context",
    "naddr": "replaceable-event-ref"
  },
  "related": [
    "/learn/nostr/keys",
    "/learn/nostr/specs/nip-19",
    "/learn/nostr/specs/nip-05"
  ]
}