Compliance Checklist
Use this checklist to verify your ActivityPub implementation meets the specification requirements and works with other Fediverse software.
Core Requirements
WebFinger (Discovery)
Actor
Content Negotiation
[ ] Actor URL returns JSON when Accept: application/activity+json
[ ] Actor URL returns JSON when Accept: application/ld+json
[ ] Actor URL returns HTML for browsers (optional but recommended)
[ ] Content-Type header is application/activity+json
Inbox
Receiving Activities
┌────────────────────────────────────────────────────────────┐
│ INBOX │
├────────────────────────────────────────────────────────────┤
│ │
│ [ ] Accepts POST requests │
│ [ ] Accepts application/activity+json content │
│ [ ] Accepts application/ld+json content │
│ [ ] Returns 202 Accepted for valid activities │
│ [ ] Returns 401/403 for invalid signatures │
│ [ ] Returns 400 for malformed JSON │
│ │
│ Signature Verification: │
│ [ ] Parses Signature header │
│ [ ] Fetches public key from keyId │
│ [ ] Verifies signature against request │
│ [ ] Allows reasonable clock skew (~30 seconds) │
│ [ ] Verifies Digest header if present │
│ │
└────────────────────────────────────────────────────────────┘
Activity Handling
[ ] Create - creates new content
[ ] Update - updates existing content (check authorization)
[ ] Delete - deletes content (check authorization)
[ ] Follow - records follow request
[ ] Accept - confirms follow
[ ] Reject - denies follow
[ ] Undo - reverses previous activity
[ ] Like - records like
[ ] Announce - records boost/share
[ ] Block - handles block (optional)
Outbox
Sending Activities
┌────────────────────────────────────────────────────────────┐
│ OUTBOX │
├────────────────────────────────────────────────────────────┤
│ │
│ [ ] GET returns OrderedCollection │
│ [ ] Collection is paginated │
│ [ ] Activities include required properties │
│ [ ] Activities have @context │
│ [ ] Activities have unique id │
│ [ ] Activities have actor │
│ [ ] Activities have type │
│ │
│ Delivery: │
│ [ ] Delivers to recipient inboxes │
│ [ ] Uses shared inbox when available │
│ [ ] Signs requests with HTTP Signatures │
│ [ ] Includes Digest header │
│ [ ] Includes proper Date header │
│ [ ] Retries on temporary failures │
│ │
└────────────────────────────────────────────────────────────┘
HTTP Signatures
┌────────────────────────────────────────────────────────────┐
│ HTTP SIGNATURES │
├────────────────────────────────────────────────────────────┤
│ │
│ Generation: │
│ [ ] Uses RSA-SHA256 algorithm │
│ [ ] Includes (request-target) pseudo-header │
│ [ ] Includes host header │
│ [ ] Includes date header │
│ [ ] Includes digest header │
│ [ ] keyId points to actor's publicKey │
│ [ ] Signature is base64 encoded │
│ │
│ Verification: │
│ [ ] Parses Signature header correctly │
│ [ ] Fetches key from keyId URL │
│ [ ] Rebuilds signing string in correct order │
│ [ ] Uses correct algorithm │
│ [ ] Validates date is recent │
│ │
└────────────────────────────────────────────────────────────┘
Collections
[ ] Followers collection exists
[ ] Following collection exists
[ ] Collections return OrderedCollection or Collection
[ ] Collections include totalItems
[ ] Large collections are paginated
[ ] Page navigation (first, next, prev) works
[ ] Collection items are ordered (newest first typical)
Objects
Notes/Posts
[ ] type is Note (or Article, etc.)
[ ] id is unique HTTPS URL
[ ] attributedTo points to author
[ ] content is HTML-formatted
[ ] published is ISO 8601 datetime
[ ] to/cc arrays for addressing
[ ] inReplyTo for replies
[ ] tag array for mentions/hashtags
Activities
[ ] @context includes ActivityStreams
[ ] type is valid activity type
[ ] id is unique HTTPS URL
[ ] actor is author's ID
[ ] object is content or reference
[ ] published is ISO 8601 datetime
[ ] to/cc copied from object or set appropriately
Interoperability
Mastodon Compatibility
[ ] Uses toot: namespace for extensions
[ ] Includes blurhash for images
[ ] Handles sensitive content flag
[ ] Supports custom emoji
[ ] Handles manuallyApprovesFollowers
[ ] Supports discoverable flag
Federation Testing
[ ] Can follow Mastodon users
[ ] Can be followed by Mastodon users
[ ] Posts appear on Mastodon
[ ] Receives posts from Mastodon
[ ] Likes are received
[ ] Boosts are received
[ ] Replies thread correctly
Automated Testing
Test Script
async function runComplianceTests(domain, username) {
const results = {
passed: [],
failed: [],
warnings: []
};
// WebFinger test
try {
const wf = await testWebFinger(domain, username);
if (wf.success) {
results.passed.push('WebFinger');
} else {
results.failed.push(`WebFinger: ${wf.error}`);
}
} catch (e) {
results.failed.push(`WebFinger: ${e.message}`);
}
// Actor test
try {
const actor = await testActor(domain, username);
if (actor.success) {
results.passed.push('Actor');
} else {
results.failed.push(`Actor: ${actor.error}`);
}
} catch (e) {
results.failed.push(`Actor: ${e.message}`);
}
// Add more tests...
return results;
}
async function testWebFinger(domain, username) {
const url = `https://${domain}/.well-known/webfinger?resource=acct:${username}@${domain}`;
const res = await fetch(url);
if (res.status !== 200) {
return { success: false, error: `Status ${res.status}` };
}
const data = await res.json();
if (!data.subject) {
return { success: false, error: 'Missing subject' };
}
const selfLink = data.links?.find(l =>
l.rel === 'self' && l.type === 'application/activity+json'
);
if (!selfLink) {
return { success: false, error: 'Missing self link' };
}
return { success: true, actorUrl: selfLink.href };
}
async function testActor(domain, username) {
const wf = await testWebFinger(domain, username);
if (!wf.success) return wf;
const res = await fetch(wf.actorUrl, {
headers: { 'Accept': 'application/activity+json' }
});
if (res.status !== 200) {
return { success: false, error: `Status ${res.status}` };
}
const actor = await res.json();
const required = ['id', 'type', 'inbox', 'outbox'];
const missing = required.filter(k => !actor[k]);
if (missing.length > 0) {
return { success: false, error: `Missing: ${missing.join(', ')}` };
}
return { success: true, actor };
}
Test Results Template
# ActivityPub Compliance Report
**Server:** example.com
**Date:** 2024-01-15
**Tester:** [your name]
## Summary
- Passed: 25/30
- Failed: 3/30
- Warnings: 2
## Results
### Discovery
- [x] WebFinger endpoint
- [x] Self link present
- [ ] CORS headers - Missing Access-Control-Allow-Origin
### Actor
- [x] Required properties
- [x] Public key
- [ ] Shared inbox - Not configured
### Inbox
- [x] Accepts activities
- [x] Signature verification
- [x] Activity handling
### Outbox
- [x] Returns collection
- [x] Delivery works
- [ ] Retry logic - Not implemented
## Recommendations
1. Add CORS headers to WebFinger
2. Configure shared inbox for efficiency
3. Implement delivery retry queue
Related Tools
- WebFinger Lookup - Test discovery
- Actor Inspector - Validate actors
- Signature Tester - Debug signatures
- Debugging Tips - Troubleshooting