Skip to content

Conversation

@staab
Copy link
Member

@staab staab commented Jan 21, 2026

NIP-9a

Nostr Push Notifications

draft optional

This NIP defines a way for users to register with relays to facilitate push notifications.

Relays that implement this NIP MUST include 9a in its NIP 11 supported_nips, and MUST NOT serve kind 30390 events to anyone except their author.

To register, users may create a kind 30390 event with their notification preferences. This event MUST include the following tags:

  • d - a random string
  • p - the relay's NIP 11 self pubkey

All other tags MUST be encrypted and placed in the event's content field:

  • relay - the normalized url of the relay the event was sent to
  • filter - a nostr filter for matching events (may appear multiple times)
  • callback - a URL indicating a callback url to forward events to

If a relay does not intend to fulfill the subscription, it SHOULD respond with an OK message with false as the result and a human-readable message.

Because client developers are expected to be running their own push server, any other features such as registration of device tokens, notification cancellation, or client status should be implemented directly between the client and the push server.

When a relay receives a new event, it SHOULD match it against all current registrations and forward the event to authorized users by sending a POST request to the callback URL with Content-Type: application/json. The payload should be as follows:

{
  relay: string       // The normalized url of the relay sending the notification
  event: Event        // The signed nostr event
}

If the callback URL responds with anything other than a 200 status code, the relay SHOULD delete the subscription (optionally taking into account retries, temporary downtime, etc).

If a relay receives a request for kind 30390, it should respond only with the current user's subscription events (based on NIP 42 AUTH). Clients MAY wish to sync subscription events periodically if they wish to ensure they continue receiving push notifications. Clients MAY delete subscription events as specified in NIP 09, or they may delete subscriptions with the push server directly, relying on callback error responses to clean up relay subscriptions.

If a relay receives a kind 30390 event not p-tagged to itself, it SHOULD NOT store it. If a relay receives a kind 30390 event whose relay tag does not match the relay's own URL, the relay SHOULD discard it. However, special purpose relays MAY choose to implement this behavior on behalf of other relays that do not support this NIP.

@staab staab force-pushed the push-notifications branch from c63001a to 2c47af9 Compare January 21, 2026 02:22
@sanah9
Copy link

sanah9 commented Jan 21, 2026

How do you distinguish whether a device is online or offline? Is it determined by whether there is a connection to the server?

@staab
Copy link
Member Author

staab commented Jan 21, 2026

I'm going to add a pause_until thing and make the events replaceable. But don't apps normally ignore push notifications if they're in the foreground?

@staab staab force-pushed the push-notifications branch from 2c47af9 to 34624f7 Compare January 21, 2026 04:33
@sanah9
Copy link

sanah9 commented Jan 21, 2026

But don't apps normally ignore push notifications if they're in the foreground?

Yes, but on Android you have to use silent push.

@fiatjaf
Copy link
Member

fiatjaf commented Jan 21, 2026

What is that server URL that stuff should be pushed to?

@staab
Copy link
Member Author

staab commented Jan 21, 2026

That's the app's dedicated push server, which hold the various keys related to authenticating push notifications. Here's the flow:

client 1                     client 2                         relay                    push server
                                                                                                  
    │                            │       subscription setup     │                           │     
    ├────────────────────────────┼──────────────────────────────┼──────────────────────────►│     
    │                            │                              │                           │     
    │                            │       callback url           │                           │     
    │◄───────────────────────────┼──────────────────────────────┼───────────────────────────┤     
    │                            │                              │                           │     
    │                            │       subscription event     │                           │     
    ├────────────────────────────┼─────────────────────────────►│                           │     
    │                            │                              │                           │     
    │                            │       kind 1 event           │                           │     
    │                            ├─────────────────────────────►│                           │     
    │                            │                              │        notification       │     
    │                            │                              ├──────────────────────────►│     
    │                            │       push notification      │                           │     
    │◄───────────────────────────┼──────────────────────────────┼───────────────────────────┤     

@staab staab force-pushed the push-notifications branch from c4d74d0 to 0480065 Compare January 21, 2026 17:27
@staab
Copy link
Member Author

staab commented Jan 21, 2026

Just updated the NIP to leave as much up to the client/push server as possible. Now, clients will have to talk directly to push servers to set up device tokens, which reduces the amount of information relays have. Clients can also implement client activity status directly with the push server, solving @sanah9's problem. Push servers can also serve as a central point of control, since they can return a 404 error to indicate the subscription should be deleted.

@fiatjaf
Copy link
Member

fiatjaf commented Jan 21, 2026

We should make this a non-hex number just to make it easier on libraries that have a number type on the NIP-11 array of supported_nips.

@staab
Copy link
Member Author

staab commented Jan 21, 2026

Relay implementations have to be updated non-trivially anyway to support it, so they might as well convert supported_nips to a string type while they're at it.

@alltheseas
Copy link
Contributor

Example push notifications for android without requiring a server, or third party service/app install. Just use websockets/connect to relay:

damus-io/notedeck#1261

Might this change how the NIP is written?

@staab
Copy link
Member Author

staab commented Jan 22, 2026

I doubt it, from what I understand mobile platforms are picky about apps using background tasks because there's a necessary trade-off between keeping stuff active and battery life. Plus, this approach is a pain to implement, I was trying it earlier this week, but in capacitor you don't have access to websockets, which means you would have to write an http/websocket proxy, or write native code. It would be good if pokey could be improved to serve as a notifications hub for other nostr apps, but that comes with the UX tradeoff of users having to install another companion app. That's not a bad solution all-in-all though.

I've also built anchor.coracle.social, which doesn't work at all because it has to open a separate websocket connection for each subscriber/relay combo to avoid exfiltrating data related to relay access controls. It also only approximates access by using invite codes, which means lots of content will still be inaccessible.

Another alternative would be to share a bunker url pointing at the user's signer so that the push server could authenticate on the user's behalf. But this has the same problem with many websocket connections, and opens a blind signing attack vector.

This push solution is good because:

  • Relays know which user is subscribed and can authorize access by user internally
  • No long-lived connections are necessary between push servers and relays
  • It uses Apple/Android push servers which are the most reliable way to do this
  • Relays that don't do access control don't have to do anything, I'm working on an implementation that can bridge push servers and regular relays

The main tradeoffs are:

  • The client developer has to run a server
  • Relays have to add support
  • Relays have to implement potentially expensive authorization logic for every event received

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants