@indiepub/activitypub
ActivityPub federation and Mastodon POSSE. When configured, your site becomes a fully-fledged ActivityPub actor — Fediverse users can follow your site directly from Mastodon, Misskey, Pixelfed, and other compatible clients.
Configuration
indiepub({ syndication: { mastodon: { instance: 'mastodon.social', handle: 'yourhandle' }, },})Required env vars: MASTODON_ACCESS_TOKEN, ACTIVITYPUB_PRIVATE_KEY, ACTIVITYPUB_PUBLIC_KEY
How it works
WebFinger
GET /.well-known/webfinger?resource=acct:yourhandle@yourdomain.com returns a JRD document pointing to your Actor URL. This is how Fediverse clients discover your identity.
Actor
GET /actor returns an ActivityPub Person document with your public key for HTTP Signature verification. Browser requests are redirected to your site home.
Inbox
POST /actor/inbox handles incoming ActivityPub activities:
- Follow — stores the follower in
ap_followers, fetches their inbox URL, sends a signedAcceptactivity back - Undo(Follow) — removes the follower from
ap_followers
Other activity types are accepted silently (202 response).
Delivery
When you publish a new post, deliverActivity() fans out a Create(Note) or Create(Article) activity to all follower inboxes in parallel using Promise.allSettled. Delivery failures for individual followers are non-fatal.
HTTP Signatures
All outgoing requests (Accept activities, post delivery) are signed with RSA-SHA256 per the HTTP Signatures spec as expected by Mastodon. The Digest header is also included for POST requests.
Generating a key pair
openssl genrsa 2048 | tee private.pem | openssl rsa -pubout -out public.pemAdd to your .dev.vars and Cloudflare Pages secrets:
ACTIVITYPUB_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----<contents of private.pem>-----END PRIVATE KEY-----"
ACTIVITYPUB_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----<contents of public.pem>-----END PUBLIC KEY-----"Mastodon POSSE API
syndicateToMastodon(entry, canonicalUrl, instance, accessToken) creates a status on your Mastodon account. Status text respects the 500-character limit.
import { syndicateToMastodon } from '@indiepub/activitypub';
const result = await syndicateToMastodon(entry, 'https://yourdomain.com/posts/slug', 'mastodon.social', token);// result: { id: string, url: string }Followers collection
GET /actor/followers returns an OrderedCollection of all follower Actor URLs, read from the ap_followers D1 table.