19 KiB
NIP-29
Relay-based Groups
draft optional relay
This NIP defines a standard for groups that are only writable by a closed set of users. They can be public for reading by external users or not.
Groups are identified by a random string of any length that serves as an id.
There is no way to create a group, what happens is just that relays (most likely when asked by users) will create rules around some specific ids so these ids can serve as an actual group, henceforth messages sent to that group will be subject to these rules.
Normally a group will originally belong to one specific relay, but the community may choose to move the group to other relays or even fork the group so it exists in different forms -- still using the same id -- across different relays.
Relay-generated events
Relays are supposed to generate the events that describe group metadata and group admins. These are addressable events signed by the relay keypair directly, with the group id as the d tag.
Group identifier
A group may be identified by a naddr1... code to its kind:39000 metadata event, with the public key set to the relay self public key, the d identifier set to the group id, the kind set to 39000 and a relay hint to the relay that is hosting the group.
An invite code may be appended to the group identifier:
naddr1...?invite=<code>
Clients should use this code in the code tag of the kind:9021 join request. Since the bech32 charset doesn't include ?, everything before the ? is still a valid identifier on its own, so clients that don't understand the suffix can just ignore it.
The h tag
Events sent by users to groups (chat messages, text notes, moderation events etc) MUST have an h tag with the value set to the group id.
Timeline references
In order to not be used out of context, events sent to these groups may contain references to previous events seen from the same relay in the previous tag. The choice of which previous events to pick belongs to the clients. The references are to be made using the first 8 characters (4 bytes) of any event in the last 50 events seen by the user in the relay, excluding events by themselves. There can be any number of references (including zero), but it's recommended that clients include at least 3 and that relays enforce this.
This is a hack to prevent messages from being broadcasted to external relays that have forks of one group out of context. Relays are expected to reject any events that contain timeline references to events not found in their own database. Clients should also check these to keep relays honest about them.
Late publication
Relays should prevent late publication (messages published now with a timestamp from days or even hours ago) unless they are open to receive a group forked or moved from another relay.
Group management
Groups can have any number of users with elevated access. These users are identified by role labels which are arbitrarily defined by the relays (see also the description of kind:39003). What each role is capable of not defined in this NIP either, it's a relay policy that can vary. Roles can be assigned by other users (as long as they have the capability to add roles) by publishing a kind:9000 event with that user's pubkey in a p tag and the roles afterwards (even if the user is already a group member a kind:9000 can be issued and the user roles must just be updated).
The roles supported by the group as to having some special privilege assigned to them should be accessible on the event kind:39003, but the relay may also accept other role names, arbitrarily defined by clients, and just not do anything with them.
Users with any roles that have any privilege can be considered admins in a broad sense and be returned in the kind:39001 event for a group.
Live audio/video (AV) spaces
Groups may indicate they support live audio and/or video chat by means of a LiveKit server by having the tag livekit in the group announcement event. An empty supported_kinds tag should be used if the group doesn't support text messages, only live AV chat. This setup allows for both Discord-style independent AV rooms and text rooms and Telegram-style single-roomgroup with support for both text and AV.
Upon seeing a livekit tag in a group, clients that want to take part in the AV space should call the token endpoint at the relay path /.well-known/nip29/livekit/<group-id> with a NIP-98-style Authorization header to get back the LiveKit JWT and the URL of the LiveKit server and proceed with the standard LiveKit flow there.
The Authorization event should be of kind 27235 and contain a tag ["u", "https://relay.tld/.well-known/nip29/livekit/<group-id>"].
Relays should enforce access control at the LiveKit JWT creation time according to the group settings.
Relays MUST set the sub property on the issued JWT (called "identity" in the standard LiveKit libraries) such that the initial 64 characters correspond to the lowercase hex public key of the Nostr user. The same user can be issued multiple JWTs and join the same LiveKit room multiple times, so relays are expected to appending a random identifier to those 64 characters.
In order to inform clients about relay support for AV (so they can display that option for users when creating or editing groups) relays should serve a status code 204 at https://relay.tld/.well-known/nip29/livekit.
Event definitions
These are the events expected to be found in NIP-29 groups.
Normal user-created events
Groups may accept any event kind, including chats, threads, long-form articles, calendar, livestreams, market announcements and so on. These should be as defined in their respective NIPs, with the addition of the h tag.
User-related group management events
These are events that can be sent by users to manage their situation in a group, they also require the h tag.
- join request (
kind:9021)
Any user can send a kind 9021 event to the relay in order to request admission to the group. Relays MUST reject the request if the user has not been added to the group. The accompanying error message SHOULD explain whether the rejection is final, if the request is pending review, or if some other special handling is relevant (e.g. if payment is required). If a user is already a member, the event MUST be rejected with duplicate: as the error message prefix.
{
"kind": 9021,
"content": "optional reason",
"tags": [
["h", "<group-id>"],
["code", "<optional-invite-code>"]
]
}
The optional code tag may be used by the relay to preauthorize acceptance, together with the kind:9009 create-invite moderation event.
- leave request (
kind:9022)
Any user can send one of these events to the relay in order to be automatically removed from the group. The relay will automatically issue a kind:9001 in response removing this user.
{
"kind": 9022,
"content": "optional reason",
"tags": [
["h", "<group-id>"]
]
}
Group state -- or moderation
These are events expected to be sent by the relay master key or by group admins -- and relays should reject them if they don't come from an authorized admin. They also require the h tag.
- moderation events (
kinds:9000-9020) (optional)
Clients can send these events to a relay in order to accomplish a moderation action. Relays must check if the pubkey sending the event is capable of performing the given action based on its role and the relay's internal policy (see also the description of kind:39003).
{
"kind": 90xx,
"content": "optional reason",
"tags": [
["h", "<group-id>"],
["previous", /*...*/]
]
}
Each moderation action uses a different kind and requires different arguments, which are given as tags. These are defined in the following table:
| kind | name | tags |
|---|---|---|
| 9000 | put-user |
p with pubkey hex and optional roles |
| 9001 | remove-user |
p with pubkey hex |
| 9002 | edit-metadata |
all the fields of group-metadata |
| 9005 | delete-event |
e with event id hex |
| 9007 | create-group |
|
| 9008 | delete-group |
|
| 9009 | create-invite |
arbitrary code |
| 9010 | update-pin-list |
zero or more e with event id hex or a with event address |
It's expected that the group state (of who is an allowed member or not, who is an admin and with which permission or not, what are the group name and picture etc) can be fully reconstructed from the canonical sequence of these events.
Group metadata events
These events contain the group id in a d tag instead of the h tag. They MUST be created by the relay master key only (as stated by the NIP-11 "self" pubkey) and a single instance of each (or none) should exist at all times for each group. They are merely informative but should reflect the latest group state (as it was changed by moderation events over time).
- group metadata (
kind:39000) (optional)
This event defines the metadata for the group -- basically how clients should display it. It must be generated and signed by the relay in which is found. Relays shouldn't accept these events if they're signed by anyone else.
If the group is forked and hosted in multiple relays, there will be multiple versions of this event in each different relay and so on.
{
"kind": 39000,
"content": "",
"tags": [
["d", "<group-id>"],
["name", "Pizza Lovers"],
["picture", "https://pizza.com/pizza.png"],
["banner", "https://pizza.com/banner.png"],
["about", "a group for people who love pizza"],
["private"],
["closed"],
["supported_kinds", "9", "11"]
]
// other fields...
}
-
name,picture,bannerandaboutare basic metadata for the group for display purposes. -
privateindicates that only members can read group messages. Omitting this tag indicates that anyone can read group messages. -
restrictedindicates that only members can write messages to the group. Omitting this tag indicates that anyone can send messages to the group. -
hiddenindicates that relays should hide group metadata from non-members. Omitting this tag indicates that anyone can request group metadata events. -
closedindicates that join requests are ignored. Omitting this tag indicates that users can expect join requests to be honored. -
livekitindicates that a group supports LiveKit-powered media rooms (live audio/video). -
supported_kindsis a list of stringified kinds numbers the group supports. When not specified all kinds are assumed to be supported. It can contain no items, in which case no kinds are supported (this is the case for AV-only groups, for example). -
group admins (
kind:39001) (optional)
Each admin is listed along with one or more roles. These roles SHOULD have a correspondence with the roles supported by the relay, as advertised by the kind:39003 event.
{
"kind": 39001,
"content": "list of admins for the pizza lovers group",
"tags": [
["d", "<group-id>"],
["p", "<pubkey1-as-hex>", "ceo"],
["p", "<pubkey2-as-hex>", "secretary", "gardener"],
// other pubkeys...
],
// other fields...
}
- group members (
kind:39002) (optional)
It's a list of pubkeys that are members of the group. Relays might choose to not to publish this information, to restrict what pubkeys can fetch it or to only display a subset of the members in it.
Clients should not assume this will always be present or that it will contain a full list of members.
{
"kind": 39002,
"content": "list of members for the pizza lovers group",
"tags": [
["d", "<group-id>"],
["p", "<admin1>"],
["p", "<member-pubkey1>"],
["p", "<member-pubkey2>"],
// other pubkeys...
],
// other fields...
}
- group roles (
kind:39003) (optional)
This is an event that MAY be published by the relay informing users and clients about what are the roles supported by this relay according to its internal logic.
For example, a relay may choose to support the roles "admin" and "moderator", in which the "admin" will be allowed to edit the group metadata, delete messages and remove users from the group, while the "moderator" can only delete messages (or the relay may choose to call these roles "ceo" and "secretary" instead, the exact role name is not relevant).
The process through which the relay decides what roles to support and how to handle moderation events internally based on them is specific to each relay and not specified here.
{
"kind": 39003,
"content": "list of roles supported by this group",
"tags": [
["d", "<group-id>"],
["role", "<role-name>", "<optional-description>"],
["role", "<role-name>", "<optional-description>"],
// other roles...
],
// other fields...
}
- livekit participants (
kind:39004) (optional)
This is an event that MAY be published by the relay informing users and clients about who is live in an AV room.
This should be updated whenever a new user leaves or joins a group, clients are expected to be actively subscribed to it.
The event MUST contain zero or more participant tags with the participant's public key as lowercase hex.
{
"kind": 39004,
"content": "",
"tags": [
["d", "<group-id>"],
["participant", "<pubkey>"],
// other participants...
],
// other fields...
}
- group pinned events (
kind:39005) (optional)
It's a list of events pinned in the group, in the order they should be displayed. The kind:9010 moderation event carries the full list as e tags (regular events) and a tags (addressable events), so pinning, unpinning, reordering and clearing pins are all done by submitting a new list. Whenever one is accepted the relay regenerates this event to mirror it. The relay may choose to limit the number of pins.
Clients may display these events at the top of the group view.
{
"kind": 39005,
"content": "",
"tags": [
["d", "<group-id>"],
["e", "<event-id1-as-hex>"],
["a", "<kind>:<pubkey>:<d-identifier>"],
["e", "<event-id2-as-hex>"],
// other event references...
],
// other fields...
}
Subgroups
Groups MAY be organized hierarchically. A subgroup is an ordinary NIP-29 group (same kind:39000, same moderation events, same h tag mechanics) whose metadata carries a parent tag pointing at its parent's d identifier:
// root group
{ "kind": 39000, "tags": [["d", "tech"], ["name", "Tech"], ["child", "nostr"]] }
// subgroup
{ "kind": 39000, "tags": [["d", "nostr"], ["name", "Nostr"], ["parent", "tech"], ["child", "nip29"]] }
// sub-subgroup
{ "kind": 39000, "tags": [["d", "nip29"], ["name", "NIP-29"], ["parent", "nostr"]] }
A group without a parent tag is a root group. To build the tree, clients fetch {"kinds":[39000]} and assemble it locally: events without a parent tag are roots; the rest are grouped under the d referenced by their parent tag. The tree is scoped to a single relay. When aggregating across relays a client MAY see conflicting parent values for the same d; in that case the kind:39000 from the relay currently serving the group's events is authoritative.
Relay support detection
A relay that supports subgroups SHOULD advertise it in its NIP-11 relay information document under a nip29 object:
{
"supported_nips": [29],
"nip29": { "subgroups": true }
}
Rules
- Parenting and promotion to root are triggered by a
kind:9002(edit-metadata) event from an authorized admin with the desiredparentvalue (or noparenttag to detach); the relay then updates the group'skind:39000accordingly. The relay also updates the parent'skind:39000to include the newly added child as achildtag (and vice-versa). If a group is switching from one parent to another parent the relay also updates the previous parent accordingly. - Historical events under the group's
htag remain valid across these transitions. - A
kind:9002MAY carry at most oneparenttag. The resultingkind:39000therefore also carries at most oneparenttag. - Absence of the
parenttag makes the group a root. - Relays MUST reject a
kind:9002whoseparentvalue would create a cycle, including self-reference. - If a group's declared parent does not exist on the relay the
kind:9002MUST be rejected. - When a parent is deleted (
kind:9008), its remaining children automatically become roots. - Relays MUST reject any
kind:9002carrying["parent", "X"]unless the event author is also an admin ofX(perX'skind:39001). - A parent group's
kind:39000MUST include["child", "<id>"]tags as an ordered list of its children; the position of each tag in the array is the order. By default relays can just append new children to the list as they are created, but the list can be rearranged by the parent's admin via akind:9002carrying the desired["child", ...]tags in order, and each edit replaces the previous list.kind:9002metadata edits that do not include all the children aschildtags MUST be rejected.
Lifecycle example
// "social" adopts "nostr" (was under "tech")
{ "kind": 9002, "tags": [["h", "nostr"], ["name", "Nostr"], ["parent", "social"]] }
// Relay emits:
{ "kind": 39000, "tags": [["d", "nostr"], ["name", "Nostr"], ["parent", "social"]] }
{ "kind": 39000, "tags": [["d", "social"], ["child", "nostr"]] }
// Admin promotes "nostr" to root: edit-metadata without a parent tag
{ "kind": 9002, "tags": [["h", "nostr"], ["name", "Nostr"]] }
// Relay emits:
{ "kind": 39000, "tags": [["d", "nostr"], ["name", "Nostr"]] }
{ "kind": 39000, "tags": [["d", "social"]] }
Membership
Each subgroup has its own independent membership: users join via kind:9021 → kind:9000 like any other group. Nothing is inherited across the parent-child link. Membership does not cascade: being a member of the parent grants no membership in any child, and each group's member list is defined solely by its own kind:9000/kind:9001 events. Admin roles likewise do not inherit; each group's kind:39001 is authoritative for its own scope, and being an admin of the parent grants no authority over any child unless the child's own kind:39001 lists the same pubkey.
This spec defines no automatic propagation of membership between parent and child. A relay that wants such behavior MAY emit the corresponding kind:9000/kind:9001 moderation events itself, but clients MUST NOT assume it: the absence of a kind:9000 in a child means the user is not a member of that child, regardless of parent membership.
Implementation quirks
Checking your own membership in a group
The latest of either kind:9000 or kind:9001 events present in a group should tell a user that they are currently members of the group or if they were removed. In case none of these exist the user is assumed to not be a member of the group.
Adding yourself to a group
Anyone can send a kind:9021 join request to a group. The relay may then generate a kind:9000 event immediately, or defer that decision to an administrator. If a group is closed, join requests are not honored unless they include an invite code.
Storing your list of groups
A definition for kind:10009 was included in NIP-51 that allows clients to store the list of groups a user wants to remember being in.