Skip to main content

ActivityPub

ActivityPub is a W3C standard for federated social networking. It powers the "Fediverse" — Mastodon, Pixelfed, PeerTube, and thousands of other interconnected servers.

Core Concepts

Actors

Everything in ActivityPub is an Actor — people, bots, groups, services:

{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Person",
"id": "https://social.example/users/alice",
"name": "Alice",
"preferredUsername": "alice",
"inbox": "https://social.example/users/alice/inbox",
"outbox": "https://social.example/users/alice/outbox",
"followers": "https://social.example/users/alice/followers",
"following": "https://social.example/users/alice/following",
"publicKey": {
"id": "https://social.example/users/alice#main-key",
"owner": "https://social.example/users/alice",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----..."
}
}

Actor Types

TypeDescriptionExample
PersonHuman user@alice@social.example
ServiceBot or automated account@bot@social.example
ApplicationApp-level actorAPI integration
GroupCommunity/group@devs@social.example
OrganizationOrganization@company@social.example

Activities

Actions are represented as Activities:

{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Create",
"id": "https://social.example/activities/123",
"actor": "https://social.example/users/alice",
"published": "2024-01-15T12:00:00Z",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"cc": ["https://social.example/users/alice/followers"],
"object": {
"type": "Note",
"id": "https://social.example/notes/456",
"content": "Hello, Fediverse!",
"attributedTo": "https://social.example/users/alice"
}
}

Activity Types

ActivityPurposeSide Effect
CreateMake something newAdd object to target inbox
UpdateModify existing objectReplace object
DeleteRemove contentRemove object
FollowSubscribe to actorAdd to following list
AcceptAccept requestConfirm follow, etc.
RejectDecline requestDeny follow, etc.
LikeExpress appreciationAdd to liked collection
AnnounceShare/boost contentRedistribute activity
UndoReverse previous activityUnfollow, unlike, etc.

Inbox/Outbox

Every actor has:

EndpointMethodPurpose
InboxPOSTReceive activities from other servers
OutboxGETRead actor's activities
OutboxPOSTClient-to-server activity submission
FollowersGETCollection of followers
FollowingGETCollection of followed actors
LikedGETCollection of liked objects

How Federation Works

Addressing

Audience Fields

FieldVisibility
toPrimary recipients
ccSecondary recipients (visible)
bccHidden recipients
audienceTarget audience

Common Patterns

// Public post
{
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"cc": ["https://social.example/users/alice/followers"]
}

// Followers-only post
{
"to": ["https://social.example/users/alice/followers"]
}

// Direct message
{
"to": ["https://other.server/users/bob"]
}

// Mention in public post
{
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"cc": [
"https://social.example/users/alice/followers",
"https://other.server/users/bob"
],
"tag": [{
"type": "Mention",
"href": "https://other.server/users/bob",
"name": "@bob@other.server"
}]
}

HTTP Signatures

ActivityPub uses HTTP Signatures for authentication:

Signing a Request

POST /users/bob/inbox HTTP/1.1
Host: other.server
Date: Sun, 15 Jan 2024 12:00:00 GMT
Digest: SHA-256=base64(sha256(body))
Signature: keyId="https://social.example/users/alice#main-key",
algorithm="rsa-sha256",
headers="(request-target) host date digest",
signature="base64(signature)"
Content-Type: application/activity+json

{ ... activity ... }

Verification Flow

Collections

Paginated Collections

{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "OrderedCollection",
"id": "https://social.example/users/alice/outbox",
"totalItems": 1234,
"first": "https://social.example/users/alice/outbox?page=1",
"last": "https://social.example/users/alice/outbox?page=50"
}

Collection Page

{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "OrderedCollectionPage",
"id": "https://social.example/users/alice/outbox?page=1",
"partOf": "https://social.example/users/alice/outbox",
"next": "https://social.example/users/alice/outbox?page=2",
"orderedItems": [
{ "type": "Create", ... },
{ "type": "Announce", ... }
]
}

Discovery with WebFinger

WebFinger maps handles like @alice@social.example to ActivityPub actors:

GET /.well-known/webfinger?resource=acct:alice@social.example

Response:

{
"subject": "acct:alice@social.example",
"aliases": [
"https://social.example/users/alice",
"https://social.example/@alice"
],
"links": [
{
"rel": "self",
"type": "application/activity+json",
"href": "https://social.example/users/alice"
},
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://social.example/@alice"
}
]
}

ActivityStreams Vocabulary

ActivityPub uses ActivityStreams 2.0 vocabulary:

Object TypePurposeExample
NoteShort text postTweet/toot
ArticleLong-form contentBlog post
ImagePhoto/imagePhoto post
VideoVideo contentVideo upload
AudioAudio contentPodcast
EventCalendar eventMeetup
QuestionPollSurvey
PageWeb pageLink share

Common Federation Issues

IssueCauseSolution
Failed deliveryServer downRetry with backoff
Invalid signatureClock skewSync server time
Actor not foundDeleted accountHandle gracefully
Rate limitedToo many requestsImplement queue
TimeoutSlow serverSet reasonable timeout

Follow Flow

Implementations

Servers

ServerTypeProtocol Support
MastodonMicrobloggingFull
PixelfedPhoto sharingFull
PeerTubeVideo sharingFull
LemmyLink aggregationFull
PleromaMicrobloggingFull
FedBoxGeneric serverCore

Libraries

  • MicroFed — Minimal ActivityPub library
  • activitypub-express — Express middleware

Quick Example

Fetching an actor:

const response = await fetch("https://mastodon.social/users/gargron", {
headers: { "Accept": "application/activity+json" }
});
const actor = await response.json();
console.log(actor.name); // "Eugen Rochko"
console.log(actor.inbox); // Where to send activities

Posting to an inbox (with HTTP Signatures):

import { signRequest } from './http-signatures';

const activity = {
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Create",
"id": "https://my.server/activities/123",
"actor": "https://my.server/users/me",
"to": ["https://other.server/users/them"],
"object": {
"type": "Note",
"id": "https://my.server/notes/456",
"content": "Hello!",
"attributedTo": "https://my.server/users/me"
}
};

const body = JSON.stringify(activity);
const headers = await signRequest({
method: 'POST',
url: 'https://other.server/users/them/inbox',
body,
privateKey,
keyId: 'https://my.server/users/me#main-key'
});

await fetch("https://other.server/users/them/inbox", {
method: "POST",
headers: {
...headers,
"Content-Type": "application/activity+json"
},
body
});

Specifications

SpecDescription
ActivityPubW3C Recommendation
ActivityStreams 2.0Data format
HTTP SignaturesAuthentication
WebFingerDiscovery
NodeInfoServer metadata

Learn More