Activity-Relay v2.5.0 retries each relay fan-out target five times after
the initial attempt and retains the shared activity body for the full retry
horizon. Receiver-side
ActivityPub implementations may synchronously fetch actors, keys, objects,
parents, contexts, or collections before returning from /inbox; transient
timeouts therefore no longer become immediate terminal loss at the relay.
Retries remain at-least-once and a receiver must tolerate duplicate delivery
when it commits an activity after the relay client has timed out.
Structured worker diagnostics record public identifiers, hashed body identity, attempt and elapsed-time data, receiver and origin domains, HTTP status, error classification, and bounded error responses. They intentionally exclude raw activity bodies, signatures, and key material.
This document records interoperability behavior validated for stable
Activity-Relay releases through v2.5.1 and retained acceptance evidence for
the 3.0 RC1/RC2 cycle. Candidate behavior is not promoted to stable merely
because it is implemented or passes CI. Historical v2.4.0 and v2.5.0
results are retained for comparison; no matrix can guarantee that every version
or configuration of every ActivityPub server behaves identically.
Activity-Relay implements both relay families retrospectively described by FEP-ae0c, plus documented extensions for open publishers, broad server-actor compatibility, NodeBB embedded-Announce normalization, and authorized fetch.
The exact comparison, historical to versus cc visibility question,
document-proof distinction, loop-prevention audit, and machine-readable
characterization cases are maintained in
FEP-AE0C-COMPATIBILITY.md. Activity-Relay 3.0
resolves the public-address question through explicit
PUBLIC_ADDRESS_DISTRIBUTION_POLICY configuration while retaining the old
behavior when that setting is omitted.
Activity-Relay 3.0 distinguishes an explicitly public primary audience from an
activity that places the ActivityStreams Public collection only in cc.
PUBLIC_ADDRESS_DISTRIBUTION_POLICY accepts:
explicit_public_only: public fan-out requires Public into; Public only inccis acknowledged and publisher-accounted without relay fan-out.public_and_unlisted: Public in eithertoorccenters public fan-out, preserving the pre-3.0 behavior.
The fresh configuration example selects explicit_public_only. Omitted
configuration falls back to public_and_unlisted for compatibility, and an
upgrade retaining the old behavior should set that value explicitly. The
broader value does not make followers-only or direct activities eligible.
Explicitly relay-addressed LitePub traffic is evaluated through its own
relationship path before the cc-only exclusion.
Activity-Relay supports both common relay subscription models:
- Traditional relay subscribers register an inbox with the relay endpoint at
/inbox. - Follower-style servers follow the relay actor at
/actorand receive anAcceptplus a reciprocal relayFollow.
Activity-Relay publishes its own relay actor as Application, with
preferredUsername relay at /actor. This matches current Friendica relay
discovery while preserving the established actor ID, collections, endpoints,
and #main-key identity.
The relay actor may be followed by standards-style remote Application or
Service actors at implementation-defined actor paths. Legacy /relay and
/friendica server actors remain supported.
The following paths were validated with real signed activities before the stable v2.4.0
release:
| Publisher path | Relay behavior | Receiving software | Result |
|---|---|---|---|
Friendica public Create |
Validate, record, and fan out | NodeBB | Received and displayed |
NodeBB category public embedded Announce |
Replace with one relay-signed Announce referencing the canonical object |
Friendica | Fetched and displayed |
NodeBB category public embedded Announce |
Replace with one relay-signed Announce referencing the canonical object |
Mastodon | Fetched, imported, tagged, and displayed |
NodeBB category public embedded Announce |
Replace with one relay-signed Announce referencing the canonical object |
Another NodeBB server | Canonical object fetched |
The NodeBB-to-Mastodon test was performed without relying on an indirect Friendica follow path.
The v2.5.0 line was exercised through an isolated relay after the Machinery v2 reliable-claims migration and again after the Friendica actor-profile correction:
| Publisher or fetch path | Receiving software or endpoint | Result |
|---|---|---|
NodeBB category public embedded Announce |
Mastodon | Accepted, fetched, imported, and displayed |
Mastodon public Create with ordinary fetch policy |
NodeBB | Delivered and displayed |
Mastodon public Create with secure mode enabled |
Friendica 2026.05 through Activity-Relay | Relay registration, delivery, canonical-object and media retrieval, hashtags, and presentation passed |
Unsigned canonical-object GET |
Mastodon secure-mode endpoint | Rejected with HTTP 401 Request not signed |
Relay-signed canonical-object GET using the deployed actor identity |
Same Mastodon secure-mode endpoint | Returned HTTP 200 ActivityPub JSON for the intended object |
The ordinary bidirectional NodeBB/Mastodon test ruled out another configured
relay as the delivery path. The secure-mode control used the same relay actor
key and HTTP-signature implementation as the deployed candidate. Friendica was
freshly registered only after it fetched the corrected Application actor
profile.
The production RC2 soak confirmed that new posts continued reaching a secure-mode Mastodon receiver. Relay-signed inbox deliveries were accepted, actor and key identity remained continuous, and no relay-specific signature, private-address, Redis, queue, worker, or resource regression was observed. Receiver-side 502 responses during the window were traced to a separate PHP-FPM incident and are not classified as an Activity-Relay failure.
NodeBB 4.14.x, including 4.14.5 testing, accepted the relay-signed delivery but returned HTTP 424 when the referenced Mastodon object required authorized fetch. Receiving-side evidence showed NodeBB's application-context canonical-object request was unsigned and the secure-mode server returned HTTP 401. The same object was returned when fetched with the Activity-Relay signature implementation.
NodeBB upstream addressed that application-actor path in commit
8e61543b0ae19fd741bd4175d478aab6c79982ca, released in the 4.15 line.
A fresh 4.15.1 retest passed during Activity-Relay 3.0 RC1 acceptance. The test
used the stock upstream NodeBB 4.15.1 container and the canonical RC1 relay
image with OUTBOUND_SIGNATURE_PROFILE: dual. A new public Mastodon post was
delivered through the relay, NodeBB completed the canonical-object retrieval
and displayed the post, the relay's NodeBB receiving-success counter advanced
without a new failure, and neither side logged a corresponding 401, 403, or
424 error. The upstream application-actor signing fix is therefore treated as
verified for this path.
The separate NodeBB category-actor RFC 9421 GET compatibility behavior below
remains independently testable and is not implied by this result.
The RC2 acceptance topology exercised two independent relays without directly peering them. Friendica subscribed to both relays, while Mastodon and NodeBB used the test relay and WordPress used the production relay. A second WordPress author that was not configured as a Friendica "also me" identity provided an isolation control: the post reached Friendica through the production relay but did not appear on the test relay, Mastodon, or NodeBB. This is retained as a cross-relay isolation pass rather than evidence of relay-to-relay propagation.
The test relay was then temporarily forced to OUTBOUND_SIGNATURE_PROFILE: rfc9421 for a Mastodon canary. Both API and worker processes confirmed the
forced profile. Friendica accepted the relay delivery with HTTP 202. Stock
NodeBB 4.15.1 returned HTTP 400 for the same relay-generated Announce on all six
bounded attempts, each recorded as signature_profile=rfc9421; the retry job
then exhausted normally. This receiver-specific result is tracked upstream as
NodeBB #14732 and is not treated
as an Activity-Relay release blocker without evidence that the relay's RFC 9421
wire format is invalid. The test relay was returned to dual afterward.
Some servers, including NodeBB category actors, publish locally created content
as a public Announce with an embedded Article or Note. For a same-domain
embedded object, Activity-Relay creates one relay-authored Announce whose
object is the embedded object's canonical ID and sends that same authenticated
wrapper to every receiver style.
This keeps the HTTP signer and JSON activity actor aligned for strict receivers while preserving the original author, content, media, and tags when the receiver fetches the canonical object.
The relay deliberately does not fan out:
- public
Announceactivities whose object is only a URL; or - public embedded
Announceactivities whose object belongs to another domain.
Those activities remain publisher-accounting events. This avoids relay loops and unintended amplification of ordinary boosts.
NodeBB 4.14.5 may return a generic HTTP 400 when an unknown remote client uses
an RFC 9421 signed GET for a category actor, while returning the same valid
ActivityPub Group actor for an unsigned or legacy-signed GET. In dual mode,
Activity-Relay treats that exact 400 as a bounded compatibility signal for an
unknown idempotent fetch and retries once with the legacy profile. It records a
short-lived legacy preference only when the retry succeeds.
This compatibility path does not parse response text, does not apply to other
status codes or transport failures, and never retries a delivery POST under a
different signature profile.
ActivityPub public-key identifiers commonly append a fragment such as
#main-key to the actor URL. URI fragments are not transmitted as part of an
HTTP request target. Before deriving an RFC 9421 @target-uri, Activity-Relay
therefore removes Fragment and RawFragment from the request URL while
preserving the original key ID used to locate and bind the published key.
This distinction is required for secure-mode Mastodon interoperability: signing
the fragment-bearing identifier while sending the fragment-free actor request
causes the receiver to reconstruct a different signature base and reject the
request. The established legacy (request-target) profile was not affected
because its request-target component already excludes the fragment.
Outbound HTTP signatures bind the signed Host value to the exact authority
sent on the wire, including a non-default port.
The relay actor publishes publicKeyPem as X.509 SubjectPublicKeyInfo PEM:
-----BEGIN PUBLIC KEY-----
Inbound verification accepts both SubjectPublicKeyInfo and legacy PKCS#1 RSA
public keys. Changing the public serialization does not rotate actor.pem, the
relay actor ID, or the #main-key key ID.
The relay-generated wrapper references the publisher's canonical object. The
receiving server fetches that object and is responsible for importing its
content, author, attachments, and ActivityStreams Hashtag entries.
A successful import does not guarantee that a post appears in every discovery surface. Local hashtag review, trend approval, moderation, language, and timeline settings may affect visibility independently of relay delivery.
Start with the public relay request status:
202 Acceptedmeans the relay accepted the signed activity for processing.400 Bad Requestcommonly indicates actor resolution, signature or digest verification, or JSON decoding failed. Version 2.5.0 logs the bounded failure reason with request metadata, but never logs request bodies, signatures, or key material.
For NodeBB specifically, verify that:
- the relay relationship is active;
- the topic belongs to a publicly readable positive-numbered category;
- the category actor URL returns a valid ActivityPub actor and public key;
- the canonical post URL returns the expected
ArticleorNote; and - retry records identify the relay inbox as their destination.
During pre-release validation, NodeBB 4.14.2 exposed two useful failure signatures:
- Uncategorized topics could serialize a numeric audience such as
"to": [-1], which is not a valid string-valued ActivityPub audience. - A category actor endpoint could fail if its configured icon file was absent, preventing the relay from resolving the signing key.
Check the current NodeBB release before assuming those version-specific issues remain unresolved.
When a receiver appears not to show a post, distinguish transport from local presentation: confirm the canonical object was fetched, then inspect the receiver's stored status and tags before concluding relay delivery failed.