FeaturesTemplatesShowcaseTownie
AI
BlogDocsPricing
Log inSign up
paulkinlan
paulkinlanpostherous
https://posthero.us - an email powered blogging system
Public
Like
6
postherous
Home
Code
22
backend
3
frontend
2
shared
2
.vtignore
ACTIVITYPUB-STATUS.md
ACTIVITYPUB-TROUBLESHOOTING.md
ACTIVITYPUB.md
README.md
SECURITY.md
SETUP.md
H
debug-config.ts
H
debug-signatures.ts
deno.json
E
email.ts
H
generate-keys.ts
H
test-activitypub-delivery.ts
H
test-activitypub-inbox.ts
H
test-activitypub.ts
H
test-follow.ts
H
test-http-signatures.ts
H
test-publish.ts
H
test-verification.ts
Branches
1
Pull requests
Remixes
4
History
Environment variables
9
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data โ€“ all from the browser, and deployed in milliseconds.
Sign up now
Code
/
ACTIVITYPUB.md
Code
/
ACTIVITYPUB.md
Search
7/11/2025
Viewing readonly version of main branch: v329
View latest version
ACTIVITYPUB.md

ActivityPub Implementation Guide

This document explains the ActivityPub and WebFinger implementation in the Email Blog platform.

๐ŸŒ Overview

ActivityPub is a decentralized social networking protocol that allows your blog to be discovered and followed from Mastodon, Pleroma, and other federated social networks. WebFinger is the discovery mechanism that allows users to find your blog using familiar @username@domain.com syntax.

โœ… Implemented Features

WebFinger Discovery

  • Endpoint: /.well-known/webfinger
  • Formats Supported:
    • acct:blog@your-domain.com
    • https://your-domain.com/actor
  • Response: JSON Resource Descriptor (JRD) format
  • CORS: Enabled for cross-origin requests

ActivityPub Actor

  • Endpoint: /actor
  • Type: Person
  • Username: blog
  • Content Negotiation:
    • application/activity+json โ†’ ActivityPub JSON document
    • application/ld+json โ†’ ActivityPub JSON document
    • text/html โ†’ Human-readable profile page
  • Discovery: Linked from home page with rel="alternate"

Home Page Content Negotiation

  • Endpoint: /
  • Content Negotiation:
    • text/html โ†’ Blog homepage with posts
    • application/activity+json โ†’ ActivityPub actor document
    • application/ld+json โ†’ ActivityPub actor document
  • Discovery: Includes Link header pointing to /actor

ActivityPub Collections

  • Outbox (/outbox): Published blog posts as ActivityPub Create activities
  • Followers (/followers): Collection of active followers (real data)
  • Following (/following): Collection of accounts being followed (placeholder)
  • Inbox (/inbox): Processes ActivityPub activities (Follow, Unfollow, Like, Announce, Create)

Activity Processing

  • Follow Activities: Adds followers to database, sends Accept response
  • Unfollow Activities: Removes followers from database
  • Like Activities: Tracks likes per post, displays counts
  • Announce Activities: Tracks shares/boosts per post, displays counts
  • Create Activities: Tracks replies to posts, displays counts

Database Storage

  • Followers Table: Stores follower information (actor ID, username, domain, inbox, etc.)
  • Activities Table: Stores all received activities (likes, shares, replies)
  • Activity Counts: Real-time counting and display on post pages

๐Ÿ”ง Configuration

Environment Variables

# Optional: Set a custom domain for ActivityPub ACTIVITYPUB_DOMAIN=your-custom-domain.com

If not set, the system will automatically detect the domain from the request headers.

๐Ÿš€ Usage Examples

Following from Mastodon

  1. Using WebFinger format: Search for @blog@your-domain.com in your Mastodon client
  2. Using direct URL: Paste https://your-domain.com/actor in the search box

Testing WebFinger

# Test with acct format curl "https://your-domain.com/.well-known/webfinger?resource=acct:blog@your-domain.com" # Test with direct URL format curl "https://your-domain.com/.well-known/webfinger?resource=https://your-domain.com/actor"

Testing ActivityPub Endpoints

# Get actor document curl -H "Accept: application/activity+json" https://your-domain.com/actor # Get outbox (published posts) curl -H "Accept: application/activity+json" https://your-domain.com/outbox # Get followers collection curl -H "Accept: application/activity+json" https://your-domain.com/followers

๐Ÿ“‹ WebFinger Response Format

{ "subject": "acct:blog@your-domain.com", "aliases": [ "https://your-domain.com/actor", "https://your-domain.com/" ], "properties": { "http://schema.org/name": "Email Blog" }, "links": [ { "rel": "http://webfinger.net/rel/profile-page", "type": "text/html", "href": "https://your-domain.com/" }, { "rel": "self", "type": "application/activity+json", "href": "https://your-domain.com/actor" }, { "rel": "http://ostatus.org/schema/1.0/subscribe", "template": "https://your-domain.com/authorize_interaction?uri={uri}" } ] }

๐Ÿ“‹ ActivityPub Actor Format

{ "@context": [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1" ], "type": "Person", "id": "https://your-domain.com/actor", "preferredUsername": "blog", "name": "Email Blog", "summary": "A blog powered by email publishing", "url": "https://your-domain.com", "inbox": "https://your-domain.com/inbox", "outbox": "https://your-domain.com/outbox", "followers": "https://your-domain.com/followers", "following": "https://your-domain.com/following", "publicKey": { "id": "https://your-domain.com/actor#main-key", "owner": "https://your-domain.com/actor", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\\n...\\n-----END PUBLIC KEY-----" } }

๐Ÿ”„ How Posts Become Activities

When you publish a blog post via email, it automatically becomes available in the ActivityPub outbox as a Create activity:

{ "@context": "https://www.w3.org/ns/activitystreams", "type": "Create", "id": "https://your-domain.com/post/my-post#create", "actor": "https://your-domain.com/actor", "published": "2024-01-01T12:00:00Z", "object": { "type": "Note", "id": "https://your-domain.com/post/my-post#note", "attributedTo": "https://your-domain.com/actor", "content": "<h2>My Post Title</h2><p>Post content...</p>", "published": "2024-01-01T12:00:00Z", "url": "https://your-domain.com/post/my-post", "to": ["https://www.w3.org/ns/activitystreams#Public"] } }

๐Ÿงช Testing Content Negotiation

You can test the content negotiation functionality using curl or any HTTP client:

Test Home Page Content Negotiation

# Request HTML (browser behavior) curl -H "Accept: text/html" https://your-domain.com/ # Request ActivityPub JSON (returns actor document) curl -H "Accept: application/activity+json" https://your-domain.com/ # Request JSON-LD (alternative ActivityPub format) curl -H "Accept: application/ld+json" https://your-domain.com/ # Mixed Accept header (browser with ActivityPub support) - prioritizes HTML curl -H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,application/activity+json;q=0.8,*/*;q=0.7" https://your-domain.com/ # ActivityPub client request - prioritizes ActivityPub curl -H "Accept: application/activity+json, text/html;q=0.8" https://your-domain.com/

Test Actor Endpoint Content Negotiation

# Request HTML profile page curl -H "Accept: text/html" https://your-domain.com/actor # Request ActivityPub actor document curl -H "Accept: application/activity+json" https://your-domain.com/actor # Request JSON-LD actor document curl -H "Accept: application/ld+json" https://your-domain.com/actor

Expected Responses

  • HTML requests: Return human-readable web pages with proper styling
  • ActivityPub requests: Return JSON-LD documents with Content-Type: application/activity+json
  • Link headers: HTML responses include Link headers pointing to ActivityPub alternatives
  • Smart prioritization: When both content types are present, prioritizes based on order in Accept header
  • Home page ActivityPub: Returns the same actor document as /actor endpoint for consistency

๐Ÿšง Current Status

โœ… Fully Implemented Features:

  1. HTTP Signatures: Cryptographic signatures for authentication โœ…
  2. Follow Processing: The inbox processes Follow/Unfollow activities โœ…
  3. Push Notifications: New posts are automatically pushed to followers โœ…
  4. Real Collections: Followers/following collections contain real data โœ…
  5. Public Key Management: Persistent RSA keys for signing โœ…

โš ๏ธ Setup Required:

  • Generate RSA key pair for HTTP signatures (see setup instructions below)

๐Ÿ› ๏ธ HTTP Signatures Setup

For proper ActivityPub federation, you need to set up persistent RSA keys for HTTP signatures:

1. Generate RSA Key Pair

Visit your /generate-keys.ts val to generate a new RSA key pair. This will provide you with:

  • ACTIVITYPUB_PUBLIC_KEY - Public key in PEM format
  • ACTIVITYPUB_PRIVATE_KEY - Private key in PEM format

2. Set Environment Variables

Add these to your Val Town environment variables:

ACTIVITYPUB_PUBLIC_KEY=-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... -----END PUBLIC KEY----- ACTIVITYPUB_PRIVATE_KEY=-----BEGIN PRIVATE KEY----- MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC... -----END PRIVATE KEY-----

3. Security Notes

๐Ÿ”’ IMPORTANT:

  • Keep the private key secret and secure
  • Do not share the private key in public repositories
  • Once set, do not change these keys unless necessary
  • Changing keys will break federation with existing followers

4. Verification

After setting the keys:

  1. Restart your val
  2. Check the logs for "โœ… RSA key pair loaded from environment variables"
  3. Test federation by following your blog from Mastodon

๐Ÿšง Fallback Behavior

If no persistent keys are set, the system will:

  • Generate temporary keys on startup
  • Log a warning about using temporary keys
  • Still function, but keys will change on restart
  • May cause federation issues with some servers

๐Ÿงช Testing

Use the /test-activitypub.ts endpoint to verify all ActivityPub endpoints are working correctly.

๐Ÿ“š References

  • ActivityPub Specification
  • WebFinger Specification
  • ActivityStreams Vocabulary
  • Mastodon API Documentation

Your blog is now discoverable and followable from the fediverse! ๐ŸŽ‰

Go to top
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Product
FeaturesPricing
Developers
DocsStatusAPI ExamplesNPM Package Examples
Explore
ShowcaseTemplatesNewest ValsTrending ValsNewsletter
Company
AboutBlogCareersBrandhi@val.town
Terms of usePrivacy policyAbuse contact
ยฉ 2025 Val Town, Inc.