Chapter 07 - Client API
07
Nostr Implementation Possibility - Hex № 07

window.nostr Capability for Web Browsers

A standard JavaScript object that lets a web page ask a browser extension to hold the user's key and sign events, without the page ever touching the private key.

1 Overview

A Nostr web client needs a private key to sign events. Pasting a raw private key into every website is dangerous. One malicious or buggy site can steal the key and impersonate the user forever.

NIP-07 solves this problem. A browser extension (or, in principle, the browser itself) injects an object named window.nostr into every page. The page calls methods on this object instead of handling the key directly. The extension holds the key, asks the user to approve each request, and signs on the page's behalf.

This turns the extension into a small, trusted signer that many different Nostr web apps can share. The user installs one extension, approves it once per site, and every NIP-01 compatible web client can publish signed events through it.

A web page must check that window.nostr exists before using it. Not every browser has an extension installed, and not every browser will ever support this capability.

Hint!

NIP-07 is one of three ways to keep a private key out of an app. Compare it with NIP-46 (a remote signer reached over the network) and NIP-55 (a signer app on the same Android device). All three follow the same idea: the app asks, the signer decides.

2 The window.nostr Object

An extension that implements NIP-07 must define window.nostr with two required methods. It may also define four optional encryption methods, grouped under nip04 and nip44 namespaces. Every method is asynchronous and returns a Promise.

Wire format - required methods
async window.nostr.getPublicKey(): string
  // returns the user's public key, as lowercase hex

async window.nostr.signEvent(event: {
  created_at: number,
  kind:       number,
  tags:       string[][],
  content:    string
}): Event
  // takes an unsigned event, adds id, pubkey, and sig, and returns the full event

3 Required Methods

Required methods
MethodReturnsRule
getPublicKey()string (64-char hex)Returns the public key of the account the extension currently has selected.
signEvent(event)EventTakes an unsigned event object. Adds id, pubkey, and sig following the NIP-01 rules, then returns the complete event.

The event argument to signEvent is an unsigned event. The page supplies only the fields it controls. The extension computes the rest.

Unsigned event object - argument to signEvent
FieldTypeSupplied by
created_atnumber, unix secondsthe page
kindnumberthe page
tagsarray of arrays of stringsthe page
contentstringthe page
pubkeyhex stringthe extension, on return
idhex stringthe extension, on return
sighex stringthe extension, on return

A page must never construct id, pubkey, or sig itself and pass them into signEvent. The extension owns those three fields. See NIP-01 for the full event format and the id-serialization rule.

4 Optional Encryption Methods

Aside from the two required methods, an extension may also implement encryption helpers. These let a page encrypt and decrypt content for direct messages without the page seeing the shared secret.

Wire format - optional encryption methods
async window.nostr.nip04.encrypt(pubkey, plaintext): string
  // returns ciphertext and iv, as specified in nip-04 (deprecated)

async window.nostr.nip04.decrypt(pubkey, ciphertext): string
  // takes ciphertext and iv, as specified in nip-04 (deprecated)

async window.nostr.nip44.encrypt(pubkey, plaintext): string
  // returns ciphertext, as specified in nip-44

async window.nostr.nip44.decrypt(pubkey, ciphertext): string
  // takes ciphertext, as specified in nip-44
Optional methods
MethodReturnsRule
nip04.encrypt(pubkey, plaintext)stringEncrypts plaintext for pubkey using the NIP-04 scheme. Deprecated; kept only for legacy content.
nip04.decrypt(pubkey, ciphertext)stringDecrypts a NIP-04 payload from pubkey. Deprecated; kept only for legacy content.
nip44.encrypt(pubkey, plaintext)stringEncrypts plaintext for pubkey using the current NIP-44 versioned scheme.
nip44.decrypt(pubkey, ciphertext)stringDecrypts a NIP-44 payload from pubkey.
Warning!

NIP-04 is deprecated in favor of NIP-17. A page should prefer nip44 for new encrypted content. Keep nip04 calls only to read old messages.

Tech Note

Older revisions of this NIP also defined window.nostr.getRelays(), which returned the user's relay read/write policy. It was removed from the spec in 2025 without a replacement method. A page that needs a user's relay list should read the kind:10002 event defined in NIP-65 instead of relying on an extension method.

5 The Trust Boundary

The whole point of NIP-07 is a boundary. The page and the extension run in separate JavaScript contexts. The page can call methods; it cannot read the extension's memory or storage. The private key stays inside the extension at all times.

Trust boundary Web Page page JS - no key access Extension isolated context - approval UI Key Store encrypted, local getPublicKey() / signEvent(event) sign internally pubkey / signed event (id, sig) return to the page The raw private key never crosses the dashed line back to the page.
Fig. 7-1 — Trust boundary. The page calls the extension; the extension calls the key store. Only public results cross back.

Recommendation to Extension Authors

A page may check for window.nostr as soon as it loads. If the extension injects its script too late, the page finds nothing and assumes no signer is installed.

Hint!

Chromium and Firefox extension authors should set "run_at": "document_end" in the extension manifest. This makes sure window.nostr is ready before the page finishes loading.

This NIP does not define an implementation. A list of known browser extensions and other signers is kept at aljazceru/awesome-nostr on GitHub.

6 Worked Example

The code below runs in a web page. It checks for the extension, reads the public key, builds an unsigned kind-1 note, and asks the extension to sign it. A rejected or failed request throws, so the page wraps the call in try/catch.

Page script - request a signed note
if (window.nostr) {
  const pubkey = await window.nostr.getPublicKey();

  const unsignedEvent = {
    created_at: Math.floor(Date.now() / 1000),
    kind: 1,
    tags: [],
    content: "Signed in the browser. The key never left the extension."
  };

  try {
    const signedEvent = await window.nostr.signEvent(unsignedEvent);
    // signedEvent now has id, pubkey, and sig - see NIP-01
    relaySocket.send(JSON.stringify(["EVENT", signedEvent]));
  } catch (err) {
    // the user clicked Reject, or the extension is locked
    console.log("Signing request refused:", err);
  }
} else {
  // no NIP-07 extension found - fall back to another signer, e.g. NIP-46
}
Page Extension User window.nostr.signEvent(event) show approval popup Approve Promise resolves: signed event If the user clicks Reject instead, the promise rejects and the page never sees the key.
Fig. 7-2 — Approval flow. Every signing or decryption request can prompt the user before it resolves.

7 Version History

NIP-07 has changed mostly by adding and later trimming its optional methods. Notable changes, newest first:

  • 20252025-02Removed getRelays() from the required interface (#1779). No replacement method was defined; use NIP-65.
  • 20242024-07Added the recommendation for extension authors to load scripts with "run_at": "document_end".
  • 20242024-02Added nip44.encrypt and nip44.decrypt to the optional methods.
  • 20232023-12NIP-44 version 2 was published; nip44 calls were briefly added to this NIP, then reverted until the encryption spec stabilized.
  • 20232023-10Clarified the type of the unsigned event object passed into signEvent.
  • 20232023-01Extensions were required to also add id and pubkey when signing, not only sig.
  • 20222022-12Added nip04.encrypt and nip04.decrypt and marked all extra methods optional.
  • 2022-2023variousMany community extensions (Alby, nos2x-fox, Blockcore, Flamingo, Horse, and others) were added to the implementations list.
  • Origin2022-05-06fiatjaf published NIP-07 with getPublicKey and signEvent.

Original Proposal - Credits

Authorfiatjaf
First commit2022-05-06 — “publish NIP-07: window.nostr.”
Statusdraft - optional
Source07.md on GitHub - full change history

8 See Also

  • NIP-01 — the event format that signEvent produces and the id/sig rules the extension follows.
  • NIP-04 — the deprecated encryption scheme behind nip04.encrypt/nip04.decrypt.
  • NIP-44 — the current encryption scheme behind nip44.encrypt/nip44.decrypt.
  • NIP-46 — a remote signer reached over the network, for when no browser extension is available.
  • NIP-55 — the same idea as this NIP, but for Android apps instead of web pages.