Chapter 89 - Discovery
89
Nostr Implementation Possibility - Hex № 89

Recommended Application Handlers

A yellow-pages system for event kinds: when your client meets an event it cannot open, it finds an app that can — recommended by people you follow.

1 Overview

Nostr has thousands of event kinds. No single client implements all of them. A social client that meets a music-track event, a poll, or a wiki page needs a way to hand that event to a program that understands it. This NIP defines that hand-off.

It uses two addressable event kinds. A handler information event (kind:31990) is published by an application. It declares which event kinds the application handles and how to open an entity in it. A recommendation event (kind:31989) is published by a user. It says: for this event kind, I recommend these applications.

Discovery then rides on the social graph. When a client meets an unknown kind, it queries for recommendations from the user and the user's follows. Trust in the handler comes from trust in the recommenders, not from an app store.

Three actors take part in the workflow:

  • The application publishes kind:31990 with its redirect rules. It does not have to be a separate entity; it can share a pubkey with user A.
  • User A recommends an app for a specific event kind. User A publishes kind:31989.
  • User B seeks a recommendation. User B queries for kind:31989 and then, from the results, queries for kind:31990.
App Relay User A User B kind:31990 kind:31989 REQ / EVENT
Fig. 89-1 — Three actors. The app declares itself, user A recommends it, user B discovers both through relays.
Hint!

Both kinds sit in the 30000-39999 range, so they are addressable events (NIP-01). A relay keeps only the latest event per pubkey + kind + d-tag value. A user updates a recommendation by publishing a newer event with the same d value.

2 Recommendation Event — kind:31989

User A publishes this event to recommend applications for one event kind:

Wire format - kind:31989 recommendation
{
  "kind": 31989,
  "pubkey": <recommender-user-pubkey>,
  "tags": [
    ["d", <supported-event-kind>],
    ["a", "31990:app1-pubkey:<d-identifier>", "wss://relay1", "ios"],
    ["a", "31990:app2-pubkey:<d-identifier>", "wss://relay2", "web"]
  ]
  // other fields...
}
Tags in kind:31989
TagValueRule
devent kind, as a stringThe event kind this recommendation covers, for example "31337".
a31990:<app pubkey>:<d identifier>Address of one recommended handler event. Multiple a tags can appear on the same event. The next value SHOULD be a relay hint. The value after that SHOULD be the platform where the recommendation applies (for example web, ios).

3 Handler Information — kind:31990

The application publishes this event. It lists the event kinds the app supports and templates for opening entities in the app:

Wire format - kind:31990 handler information
{
  "kind": 31990,
  "pubkey": <application-pubkey>,
  "content": <optional-kind:0-style-metadata>,
  "tags": [
    ["d", <random-id>],
    ["k", <supported-event-kind>],
    ["latest", "35128:<application-pubkey>:<site-d-identifier>", "wss://relay1"],
    ["next", "35128:<application-pubkey>:<next-site-d-identifier>", "wss://relay1"],
    ["web", "https://..../a/<bech32>", "nevent"],
    ["web", "https://..../p/<bech32>", "nprofile"],
    ["web", "https://..../e/<bech32>"],
    ["ios", ".../<bech32>"]
  ]
  // other fields...
}
Fields and tags in kind:31990
ItemRule
contentOptional stringified JSON metadata object, in the kind:0 style of NIP-01. Use it when the publishing pubkey is not itself an application. If content is empty, clients use the pubkey's own kind:0 for display data (name, picture, web, LUD16, and so on).
dA random identifier. Unlike kind:31989, it does not carry the supported kind.
kOne supported event kind. Repeat the tag for each supported kind that shares the same handler URLs. The same pubkey can also publish multiple kind:31990 events for different apps that handle the same event kind.
latestSHOULD appear on web applications when applicable. Same shape as an a tag: value <kind>:<pubkey>:<d-tag>, then an optional relay hint. References the current kind:35128 site manifest (NIP-5A) for the application.
nextSame shape as latest. References the next site manifest intended for rollout or upgrade.
web / ios / ...Platform handler tags. The value is a URL template. <bech32> in the template MUST be replaced by the client with the NIP-19-encoded entity to load.

An app can register multiple platform tags. A tag can carry a NIP-19 entity name (for example nevent, nprofile) to scope the template to that entity type. A platform tag without an entity name SHOULD be treated as the generic handler for any NIP-19 entity that no other tag handles.

App descriptor events SHOULD tag or otherwise reference their related site manifest events when applicable. Clients can then discover the deployed site manifest for an application directly from the upstream app descriptor.

Tech Note

The source counts tag positions inconsistently. It calls the NIP-19 entity name "the second value of the array", and the relay hint in kind:31989 a tags likewise "the second value" — but in every example these sit in the third array slot, after the tag name and the primary value. Read the positions from the examples, as this page does, not from the prose.

4 The client Tag

When a client publishes an event on behalf of a user, it MAY add a client tag. The tag identifies the client that published the note. It is a tuple of a name, an address that identifies a handler event, and a relay hint for finding that handler event:

Example - client tag on a kind:1 note
{
  "kind": 1,
  "tags": [
    ["client", "My Client", "31990:app1-pubkey:<d-identifier>", "wss://relay1"]
  ]
  // other fields...
}
Warning!

The client tag has privacy implications: it reveals which software a user runs on every event. Clients SHOULD let users opt out of the tag.

5 Discovery Flow

The end-to-end flow works like this. User A uses a nostr app that is not centered on kind:1. User A chooses to announce and recommend that app as a handler for its kind.

Later, user B sees an event of an unknown kind in a social-media-centric client. The client still lets user B interact with the event, for example by tapping on it. The client may then query for handlers recommended by the user and by the user's follows.

User B client Relay ["REQ", …, {"kinds":[31989], "#d":["31337"], "authors":[follows]}] ["EVENT", …, {kind:31989, "a" → handler}] ["REQ", …, fetch the a-tagged kind:31990] ["EVENT", …, {kind:31990, "web" URL template}] Client replaces <bech32> in the template and opens the handler URL.
Fig. 89-2 — Handler discovery. Two lookups: recommendations first, then the handler event they address.

Alternative query, bypassing kind:31989

A client can also skip the recommendation step. It queries directly for kind:31990 events whose k tag matches the unknown kind:

Direct handler query
→ ["REQ", <id>, { "kinds": [31990], "#k": [<desired-event-kind>], "authors": [...] }]
Warning!

The direct query drops the social-graph filter. Any pubkey can publish a kind:31990 event. Clients SHOULD apply spam-prevention mechanisms or query high-quality restricted relays, so they do not direct users to malicious handlers.

6 Worked Example

User A uses Zapstr, a client centered on kind:31337 track events. With Zapstr, user A publishes a recommendation of Zapstr as a kind:31337 handler:

User A recommends a kind:31337 handler
{
  "kind": 31989,
  "tags": [
    ["d", "31337"],
    ["a", "31990:1743058db7078661b94aaf4286429d97ee5257d14a86d6bfa54cb0482b876fb0:abcd",
      <relay-url>, "web"]
  ]
  // other fields...
}

User B follows user A. In their timeline, user B sees a kind:1 note that tags a kind:31337 event. User B's client does not know how to handle kind:31337, so it displays the event with its alt tag (NIP-31). When user B clicks the event, the client queries for a handler:

Session transcript - handler lookup
-- find recommendations from user B and their follows --
→ ["REQ", <id>, { "kinds": [31989], "#d": ["31337"],
     "authors": [<user>, <users-contact-list>] }]
← ["EVENT", <id>, {"kind":31989, "tags":[["d","31337"],
     ["a","31990:1743058d…76fb0:abcd", "wss://relay1", "web"]], …}]
← ["EOSE", <id>]

-- fetch the a-tagged handler event --
→ ["REQ", <id2>, { "kinds": [31990],
     "authors": ["1743058d…76fb0"], "#d": ["abcd"] }]
← ["EVENT", <id2>, {"kind":31990, "tags":[["d","abcd"],["k","31337"],
     ["web","https://zapstr.example/track/<bech32>","nevent"]], …}]

User B's client reads the app and handler information from the a-tagged kind:31990 event. It replaces <bech32> in the URL template with the NIP-19 nevent for the track. Then it redirects user B to that URL.

Tech Note

The fallback display in this flow depends on the alt tag from NIP-31, which this manual marks as deprecated. The source text still cites it. Treat alt as a best-effort fallback only. The source also says the client "MIGHT" query the user's and follows' handlers — a non-standard keyword; read it as MAY.

7 Version History

NIP-89 arrived in mid-2023 and has stayed close to its original shape. The client tag and the site-manifest links came later. Notable changes, newest first:

  • 20262026-06Added latest and next tags linking handler events to kind:35128 site manifests (NIP-5A).
  • 20242024-06Fixed the REQ examples; assorted JSON and heading formatting fixes through 2026.
  • 20232023-11Made the client tag backwards compatible: the human-readable name moved to the first value, before the handler address.
  • 20232023-11Added the client tag for identifying the publishing client.
  • 20232023-08Reworded set_metadata to metadata; removed NIP-33 references after its merge into NIP-01.
  • Origin2023-06Pablo Fernandez proposed kinds 31989 and 31990 in PR #530.

Original Proposal - Credits

AuthorPablo Fernandez
First commit2023-06-07 — “NIP-89: Recommended Application Handlers (#530)”
Pull requestnostr-protocol/nips #530
Statusdraft - optional
Source89.md on GitHub - full change history

8 See Also

  • NIP-01 — addressable events, a tags, filters, and the kind:0 metadata shape reused in content.
  • NIP-19 — the bech32 entities (nevent, nprofile, …) substituted into handler URL templates.
  • NIP-31 — the alt tag used to display unknown kinds before a handler is found (deprecated).
  • NIP-5A — static websites; the kind:35128 site manifests referenced by latest and next tags.
  • NIP-02 — the follow list that scopes recommendation queries to trusted authors.