Skip to content
Open
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions 9a.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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](11.md) `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](11.md) `self` pubkey

All other tags MUST be encrypted to the relay's `self` pubkey 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)
- `ignore` - a nostr filter for ignoring matched 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.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add expiry information. If a relay accepts subscription they have to send expiry time of subscription. As currently it is unclear when client has to resync subscription with relay.

Copy link
Collaborator

@vitorpamplona vitorpamplona Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the relay should also delete the event when the subscription expires, then clients can check if the relay has the event if not send it again.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@prhasn how do you imagine this working? OK messages aren't good for structured data. The relay could publish a subscription status event, but that seems like overkill. Maybe we should require an expiration tag on the subscription event, and relays can reject it if it's too far out?

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is the case, why are we developing a standard for registration only? Why isn't the registration just another hardcoded payload between the client and their push server? Why are we "forcing" all clients to support filters and ignore if they can make their own parameters that would probably be more specific than those?

Like, for Amethyst, I only send notifications from non-zero Web of Trust and zaps only over 10 sats. The tags here wouldn't support what we need. Every client would have something a little different.

The kind 30390 event would not be reusable between apps, so I am not sure why we are standardizing this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because relays have to understand them in order to push them to the push server. The reason we need this is because relays might choose to implement authorization policies, so it needs to be up to them who they push stuff to.


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:

```typescript
{
id: string // The event's id
relay: string // The normalized url of the relay sending the notification
pubkey: string // The pubkey used to encrypt the ciphertext
ciphertext: string // JSON-encoded nostr event, nip 44 encrypted to the 30390's pubkey
}
```

Note that the event is encrypted and placed in the `ciphertext` to protect user privacy from push servers. However, the event's `id` is sent in plaintext to allow push servers to deduplicate events coming from multiple relays.

If the `callback` URL responds with a `404` status code, the relay SHOULD delete the subscription. Relays MAY delete subscriptions at their discretion (e.g. due to expiration, inactivity, callback `500` errors, 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](42.md) 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](09.md), 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.