Where and how to send join activity?


I just want to make sure I have understood everything correctly. If not, please correct me.

When a user wants to join a group, the client publishes a join activity to the user’s outbox, with the user as the actor, the group as the object, and the group as the recipient (to)?

 "@context": ["https://www.w3.org/ns/activitystreams",
              {"@language": "en"}],
 "type": "Join",
 "actor": "https://some-social-network.org/alice",
 "name": "Alice joins 'Example Group'",
 "object": "https://other-network.co.uk/groups/example-group",
 "to": ["https://other-network.co.uk/groups/example-group"],

The server then looks up the group’s actor object to get its inbox and sends the activity to it. The group’s server must now add the user to a collection (is there a collection like members, or should I use followers here?) and post an accept activity to the server’s outbox (or, if the user is not added to a collection, post a reject activity).

  "@context": ["https://www.w3.org/ns/activitystreams",
               {"@language": "en"}],
  "type": "Accept",
  "actor": "https://other-network.co.uk/groups/example-group",
  "name": "Alice joined 'Example Group'",
  "object": {
    "type": "Join",
    "actor": "https://some-social-network.org/alice",
    "object": "https://other-network.co.uk/groups/example-group",
  "to": ["https://some-social-network.org/alice",

I am also wondering: Should the user’s server match the accept/reject activity with the previous join activity? And should I use IDs here for faster lookup?
I am also considering edge cases where accepting/rejecting is a manual process and a user might revoke their join activity before a response is received.

The Join activity is not part of ActivityPub. Only Follow is.

This is extension territory. There is no specified way within Activity Streams 2.0 or special collection within ActivityPub to signal members of a group. You should not use followers unless you are dealing with Follow activities.

(The following is theoretical conjecture:)

If you wanted to pursue this further, the “weakest” way of signaling group members is to use vcard:hasMember from the VCard Ontology that is recommended by AS2 (and its prefix is part of the AS2 context document). I say “weakest” because this is literally just a JSON array and comes with none of the advantages of a collection:

  "@context": "https://www.w3.org/ns/activitystreams",
  "type": "Group",
  "vcard:hasMember": [
    {"id": "https://social.example/users/1"},
    {"id": "https://social.example/users/2"},
    // ...
    {"id": "https://social.example/users/100"},

Probably the “better” way is to write a FEP for a members collection and how it would work. This is pretty open-ended for now, so you would need to pretty concisely bound behavior in such a way as to be useful. For example, by defining the following provisions:

  • The members collection indicates that the actor accepts Join/Leave activities?
  • Some property like manuallyAcceptsJoins to indicate that the Accept Join will not be automatically received ~immediately? This mirrors the manuallyApprovesFollowers extension property in popular usage with current implementations in the fediverse.
  • Heavy consideration needs to be given to the privileges granted by being a member vs being a follower, or being both! Or being followed by the group, etc etc.
  • More consideration needs to be given ahead-of-time to how to remove a member from a group, and who can do so. Something like deciding “the attributedTo actor (or some other further extension moderator?) can issue a Remove activity targeting the group’s members collection, or otherwise send a Reject Join at any future point, or [other potential scenarios]”.

This is an open issue for Follow activities too, and the guidance is basically like so:

  • The sender of the Follow activity SHOULD include an id, just like any other non-transient activity.
  • The receiver of a Follow activity SHOULD include the id received, either directly, or by embedding a (possibly partial) representation of the Follow activity they just received (within object).
  • The receiver of an Accept Follow or Reject Follow may not receive an inner id on their Follow object from certain implementations, so they will have to keep track of an internal follow-request state machine and match against pending follow requests.
1 Like

Thanks for your response.

Ah okay. Then I missunderstood

ActivityPub uses [ActivityStreams] for its vocabulary.

So when something is listed in Activity Vocabulary but not listed in ActivityPub it’s not ActivityPub?

ActivityPub refers to sending activities to the inbox/outbox in order to trigger certain side effects. It “uses AS2 for its vocabulary”, but that doesn’t mean that it uses all of AS2. For example, if you receive a TentativeAccept of an Offer, that doesn’t have any defined side effects in ActivityPub, because ActivityPub only defines side effects for Create / Update / Delete / Follow / Like / Announce / Add / Remove, as well as for Accept Follow / Reject Follow / Undo Follow / Undo Like / Undo Block. These side effects are defined in Sections 6 and 7 of the ActivityPub spec. Some extensions also use the AS2 Vocabulary while defining new side effects for them; for example, Mastodon defines some extensions using the AS2 vocab, such as Move for signaling account migrations and Flag for reporting objectionable content.