Vibefed kb-fediverse-following
git clone https://github.com/reiver/vibefed
T=$(mktemp -d) && git clone --depth=1 https://github.com/reiver/vibefed "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/kb-fediverse-following" ~/.claude/skills/reiver-vibefed-kb-fediverse-following && rm -rf "$T"
skills/kb-fediverse-following/SKILL.mdFediverse Following — Complete Reference
Overview
Following on the Fediverse is a formal ActivityPub handshake, not a simple database write. It involves activity delivery between servers, an explicit approval step, and ongoing delivery of content. Understanding all the steps — and what can go wrong — is essential for correct implementation.
1. The Actor Object
Before any following can happen, each user is represented as an actor — a JSON-LD object served at a stable URL. The actor contains the endpoints and collections needed for the follow flow:
{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Person", "id": "https://example.com/users/alice", "preferredUsername": "alice", "inbox": "https://example.com/users/alice/inbox", "outbox": "https://example.com/users/alice/outbox", "followers": "https://example.com/users/alice/followers", "following": "https://example.com/users/alice/following", "manuallyApprovesFollowers": false }
Key properties for following:
— the URL where activities are POSTed to reach this actorinbox
— anfollowers
of actors who follow this actorOrderedCollection
— anfollowing
of actors this actor followsOrderedCollection
—manuallyApprovesFollowers
= open/unlocked account (auto-accepts);false
= locked account (requires manual approval). Defined in the ActivityPub Miscellaneous Terms astrue
.as:manuallyApprovesFollowers
2. The Follow Handshake — Step by Step
Following is a four-message exchange in the general case. For unlocked accounts it collapses to three (the Accept is sent automatically).
Step 1: Alice sends a Follow
activity
FollowAlice's server POSTs a
Follow activity to Bob's inbox:
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://alice.example/activities/follow-1", "type": "Follow", "actor": "https://alice.example/users/alice", "object": "https://bob.example/users/bob" }
- The
is the actor being followed (Bob)object - The
is the one initiating the follow (Alice)actor - The
should be a unique, dereferenceable URI on Alice's server (though in practice many implementations use non-dereferenceable UUIDs)id - At this point, Alice's server may add Bob to Alice's
collection tentatively, or wait until thefollowing
arrives — implementations differAccept
Step 2: Bob's server receives the Follow
FollowOn receiving the
Follow in Bob's inbox, Bob's server:
- Validates the HTTP Signature on the request (to confirm it came from Alice's server)
- Checks whether Bob has
ormanuallyApprovesFollowers: truefalse - Either queues a follow request for Bob to approve (locked account), or
automatically proceeds to send an
Accept
At this stage the follow is pending — Alice is not yet in Bob's
followers collection.
Step 3: Bob's server sends an Accept
(or Reject
)
AcceptRejectFor unlocked accounts —
Accept is sent automatically:
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://bob.example/activities/accept-1", "type": "Accept", "actor": "https://bob.example/users/bob", "object": { "type": "Follow", "actor": "https://alice.example/users/alice", "object": "https://bob.example/users/bob" } }
The
object of the Accept is the original Follow activity (or just its
id URI — either form is acceptable).
For locked accounts — Bob manually approves or denies. If denied, a
Reject is sent with the same structure:
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://bob.example/activities/reject-1", "type": "Reject", "actor": "https://bob.example/users/bob", "object": "https://alice.example/activities/follow-1" }
Important spec note: Servers MAY choose to not send a
Reject at all
(e.g. to protect a user's privacy — not rejecting leaks the fact that the
account exists). This means the follow request may be silently ignored,
leaving Alice's server in an indefinite pending state. Implementations
should handle this gracefully.
Step 4: Alice's server receives the Accept
AcceptOn receiving the
Accept:
- Alice's server adds Bob to Alice's
collectionfollowing - Bob's server adds Alice to Bob's
collectionfollowers - The follow relationship is now active
From this point on, when Bob creates a post addressed to his followers, his server will deliver it to Alice's inbox.
3. Unfollowing — The Undo Follow
Undo FollowTo unfollow, Alice sends an
Undo activity wrapping the original Follow:
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://alice.example/activities/undo-follow-1", "type": "Undo", "actor": "https://alice.example/users/alice", "object": { "type": "Follow", "actor": "https://alice.example/users/alice", "object": "https://bob.example/users/bob" } }
This is POSTed to Bob's inbox. On receipt, Bob's server removes Alice from Bob's
followers collection. Alice's server removes Bob from Alice's
following collection.
The spec requires that the
Undo and the activity being undone have the
same actor. You cannot undo another actor's Follow.
4. The followers
and following
Collections
followersfollowingBoth are
OrderedCollection objects. They may be paginated with
OrderedCollectionPage. The actor object contains their URLs; fetching
those URLs with Accept: application/activity+json returns the collection.
{ "@context": "https://www.w3.org/ns/activitystreams", "id": "https://bob.example/users/bob/followers", "type": "OrderedCollection", "totalItems": 412, "first": "https://bob.example/users/bob/followers?page=1" }
Implementations may choose to hide the actual members of these collections for privacy (returning
totalItems but an empty items array, or omitting
first/next page links). This is valid and common.
5. How Content is Delivered to Followers
When Bob creates a public post, his server must deliver it to all his followers. This is where the follow relationship does its work.
Addressing
A public post addressed to followers is typically addressed as:
{ "to": ["https://www.w3.org/ns/activitystreams#Public"], "cc": ["https://bob.example/users/bob/followers"] }
A followers-only post omits the public audience:
{ "to": ["https://bob.example/users/bob/followers"], "cc": [] }
Delivery mechanics
Bob's server must POST the
Create activity to the inbox of every actor
in Bob's followers collection. For followers on many different servers,
this can mean hundreds of HTTP POST requests.
Shared inbox optimisation
To avoid sending N requests to the same server (when multiple followers are on the same instance), the actor object may declare a
sharedInbox:
{ "endpoints": { "sharedInbox": "https://alice.example/inbox" } }
When a
sharedInbox is available, the sending server can POST once to
that shared inbox instead of once per follower on that instance. The
receiving server then fans out internally to all relevant local followers.
Mastodon always provides a shared inbox. Using it is optional but strongly
recommended for performance.
6. Visibility Levels and the to
/cc
Fields
toccThe follow relationship determines what appears in a follower's Home timeline. The
to and cc fields on activities determine visibility:
| Visibility | | |
|---|---|---|
| Public | | followers collection |
| Unlisted | followers collection | |
| Followers-only | followers collection | (empty) |
| Direct (mention-only) | mentioned actors | (empty) |
The special URI
https://www.w3.org/ns/activitystreams#Public (often
written as as:Public) signals that the post is publicly accessible.
Posts addressed to followers-only are only delivered to followers' inboxes, not crawlable publicly. A server receiving such a post should only show it to users who follow the author.
7. Locked Accounts and Pending Requests
When
manuallyApprovesFollowers is true:
- Incoming
activities are queued, not immediately acceptedFollow - The follower sees a "pending" state in the UI
- The followed user sees a follow request notification
- They can
(sending anAccept
activity) orAccept
(sending aReject
activity, or silently ignoring it)Reject - Until accepted, the follower does NOT receive the account's posts
There is no standard way for a server to query whether a pending follow request has been accepted or rejected — implementations typically rely on eventually receiving an
Accept or Reject, or timing out.
8. Account Migration — The Move
Activity
MoveActivityPub does not natively support moving an account between servers, but a convention has emerged (led by Mastodon) using the
Move activity
and the alsoKnownAs / movedTo properties.
The process
-
New account declares
pointing to the old account:alsoKnownAs{ "type": "Person", "id": "https://new.example/users/alice", "alsoKnownAs": ["https://old.example/users/alice"] } -
Old account sends a
activity to all its followers:Move{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Move", "actor": "https://old.example/users/alice", "object": "https://old.example/users/alice", "target": "https://new.example/users/alice" } -
Receiving servers (i.e. servers that have followers of the old account) automatically re-issue a
from each of those followers to the new account, effectively migrating the follower list.Follow
Important limitations
- Only followers are migrated — not posts, likes, bookmarks, or lists
- The old account's posts remain on the old server (or disappear if the server is shut down)
- Not all implementations support
— GotoSocial notably lacked support for a timeMove - The
must be set on the new account before thealsoKnownAs
is sent from the old account, or receiving servers may reject itMove
9. Common Implementation Mistakes
- Not waiting for
before adding toAccept
: Adding the followee tofollowing
before thefollowing
arrives means the collection is inaccurate for locked accountsAccept - Not handling silent
: If a locked account never sends aReject
, the pending state must time out gracefullyReject - Not validating HTTP Signatures on incoming
activities: anyone could POST a fakeFollow
to your inboxFollow - Sending
to the wrong URL: TheAccept
must be delivered to theAccept
of the Follow'sinbox
, not theactorobject - Not using
: Sending one request per follower per instance instead of usingsharedInbox
causes serious performance problems at scalesharedInbox - Forgetting to handle
: When Alice unfollows Bob, Bob's server must remove Alice from theUndo Follow
collection, or Alice will keep receiving Bob's postsfollowers - Exposing full followers list without privacy consideration: Some
users expect their followers/following lists to be private; implementations
should support hiding collection members while still providing
totalItems
10. Summary: The Minimum a Correct Implementation Must Do
| Direction | Activity | Required action |
|---|---|---|
| Outgoing follow | Send to target's | Add to local pending state |
Incoming | Receive in own | Move from pending → active; add to |
Incoming (or silence) | Receive in own (or timeout) | Remove from pending |
Incoming | Receive in own | Queue or auto-accept; send / |
Outgoing | Send to follower's | Add follower to collection |
| Outgoing unfollow | Send to target's | Remove from |
Incoming | Receive in own | Remove actor from collection |
| Delivering posts | POST to each follower's (or ) | — |