The Complete Guide to ActivityPub Development in 2025
ActivityPub has become the backbone of the decentralized social web. With over 13 million users across Mastodon, Lemmy, PeerTube, Pixelfed, and dozens of other platforms, understanding ActivityPub is essential for developers building the next generation of social applications.
This guide provides everything you need to start building with ActivityPub — from core concepts to production deployment.
What is ActivityPub?
ActivityPub is a W3C Recommendation that defines how servers communicate in a federated social network. Unlike centralized platforms like Twitter or Facebook, ActivityPub enables independent servers to communicate with each other, creating an interconnected "Fediverse."
Key benefits:
- No single point of control — Users can choose their server or run their own
- Interoperability — A Mastodon user can follow a PeerTube channel
- Data portability — Users can migrate between servers
- Open standard — Anyone can implement it
Core Concepts
Before diving into code, understand these fundamental concepts:
Actors
An Actor is any entity that can perform actions — usually a user account, but also bots, groups, or services.
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Person",
"id": "https://example.com/users/alice",
"inbox": "https://example.com/users/alice/inbox",
"outbox": "https://example.com/users/alice/outbox",
"preferredUsername": "alice",
"name": "Alice"
}
Activities
Activities represent actions: Create, Follow, Like, Announce (boost), Delete, and more.
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Create",
"actor": "https://example.com/users/alice",
"object": {
"type": "Note",
"content": "Hello, Fediverse!"
}
}
Federation
Federation is how servers exchange activities. When Alice follows Bob on a different server, her server sends a Follow activity to Bob's inbox.
Getting Started: Your First ActivityPub Server
Ready to build? Here's the minimal implementation path:
Step 1: WebFinger Discovery
Implement the /.well-known/webfinger endpoint so other servers can find your users:
app.get('/.well-known/webfinger', (req, res) => {
const resource = req.query.resource;
const [, user, domain] = resource.match(/acct:(.+)@(.+)/);
res.json({
subject: resource,
links: [{
rel: 'self',
type: 'application/activity+json',
href: `https://${domain}/users/${user}`
}]
});
});
→ Full guide: WebFinger Implementation
Step 2: Actor Endpoint
Return your user's profile in ActivityPub format:
app.get('/users/:username', (req, res) => {
res.json({
'@context': [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
type: 'Person',
id: `https://example.com/users/${req.params.username}`,
inbox: `https://example.com/users/${req.params.username}/inbox`,
outbox: `https://example.com/users/${req.params.username}/outbox`,
preferredUsername: req.params.username,
publicKey: {
id: `https://example.com/users/${req.params.username}#main-key`,
owner: `https://example.com/users/${req.params.username}`,
publicKeyPem: PUBLIC_KEY
}
});
});
→ Full guide: Building an Actor
Step 3: Inbox for Receiving Activities
Accept incoming activities from other servers:
app.post('/users/:username/inbox', async (req, res) => {
// Verify HTTP Signature
await verifySignature(req);
const activity = req.body;
switch (activity.type) {
case 'Follow':
await handleFollow(activity);
break;
case 'Create':
await handleCreate(activity);
break;
// ... handle other types
}
res.status(202).send('Accepted');
});
→ Full guide: Handling Incoming Activities
Step 4: HTTP Signatures
All inbox requests must be cryptographically signed. This proves the request came from who it claims:
const crypto = require('crypto');
function signRequest(privateKey, keyId, method, url, body) {
const digest = crypto.createHash('sha256').update(body).digest('base64');
const date = new Date().toUTCString();
const signingString = [
`(request-target): ${method.toLowerCase()} ${new URL(url).pathname}`,
`host: ${new URL(url).host}`,
`date: ${date}`,
`digest: SHA-256=${digest}`
].join('\n');
const signature = crypto.sign('RSA-SHA256',
Buffer.from(signingString), privateKey).toString('base64');
return { date, digest: `SHA-256=${digest}`, signature };
}
→ Full guide: Authentication and Security
Essential Implementation Patterns
Following and Followers
The follow flow is fundamental to federation:
- Alice sends
Followactivity to Bob's inbox - Bob's server sends
Acceptback to Alice's inbox - Alice adds Bob to her following list
- Bob's future posts are delivered to Alice's inbox
→ Full guide: Following and Followers
Creating Posts
Wrap content in a Create activity and deliver to followers:
async function createPost(author, content) {
const note = {
type: 'Note',
id: `https://example.com/notes/${uuid()}`,
attributedTo: author.id,
content: content,
to: ['https://www.w3.org/ns/activitystreams#Public'],
cc: [`${author.id}/followers`]
};
const activity = {
type: 'Create',
actor: author.id,
object: note,
to: note.to,
cc: note.cc
};
await deliverToFollowers(activity, author);
}
→ Full guide: Posts and Replies
Platform Compatibility
Different Fediverse platforms have specific requirements:
| Platform | Key Considerations |
|---|---|
| Mastodon | Uses toot: namespace, requires discoverable flag |
| Lemmy | Uses Group actors for communities |
| PeerTube | Video-focused with custom extensions |
| Pixelfed | Image-focused, supports Image objects |
→ Full guides: Mastodon Compatibility, Lemmy Compatibility
Libraries and Tools
Don't build from scratch. Use established libraries:
JavaScript/TypeScript
- activitypub-express — Express middleware
- @fedify/fedify — Type-safe framework
Python
- bovine — ActivityPub toolkit
- little-boxes — Minimal implementation
Go
Rust
- activitypub-federation — Lemmy's library
→ Full directory: Ecosystem Libraries
Testing Your Implementation
Before going live:
- Use ngrok to expose your local server
- Test WebFinger with the lookup tool
- Try following from a real Mastodon account
- Check signatures with the signature tester
→ Full guide: Testing Your Implementation
Production Checklist
Before deploying:
- HTTPS only (required for federation)
- HTTP Signatures implemented
- Rate limiting on inbox
- Content sanitization (prevent XSS)
- Delivery retry queue
- Dead server tracking
→ Full checklist: Compliance Checklist
Community Resources
Join the ActivityPub developer community:
- SocialHub Forum — Primary discussion forum
- W3C Social CG — Standards development
- Fediverse Enhancement Proposals — Protocol extensions
- Social Web Foundation — Advocacy and funding
Next Steps
Ready to build? Start with these resources:
- What is the Fediverse? — Conceptual foundation
- Building an Actor — Your first implementation
- ActivityPub Specification — Protocol deep-dive
The decentralized social web is growing fast. Whether you're building the next Mastodon competitor, adding federation to an existing app, or creating something entirely new, ActivityPub provides the foundation.
Welcome to the Fediverse. Let's build together.
Have questions? Join the discussion on SocialHub or open an issue on GitHub.