ActivityPub is a standard for publishing structured social network data on the Web in JSON-LD format. This document describes various methods for discovering the ActivityPub object described by an HTML page, and conversely the HTML page for an ActivityPub object.

This is a draft of the Social Web Incubator Group (SocialCG) Discovery Task Force.

Introduction

ActivityPub is a standard for publishing structured social network data on the Web in JSON-LD format and sharing that data from client to server and from server to server. This document describes several methods for discovering the ActivityPub object described by an HTML page, and conversely the HTML page for an ActivityPub object.

Social data in the ActivityPub model is a "resource" like a person, image, or place. That resource has an ActivityPub JSON-LD representation (if it doesn't, it's not covered by this document!) and may have an HTML representation. The ActivityPub representation and the HTML representations each have an URL -- possibly the same URL.

"Forward discovery" is any process that, given the resource's HTML representation, will return the resource's ActivityPub representation.

There are multiple techniques for forward discovery which will be documented in this report. Some discovery techniques require the full HTML document -- its markup and content. Others only need the URL of the HTML representation.

When a consumer is doing discovery, it can start with the input it has -- document or URL -- and convert to the other input if it needs to.Converting an HTML document to an HTML URL usually requires access to the document's context, like a browser environment. Converting an HTML URL to a document requires fetching the document and parsing it.

"Reverse discovery" is any process that, given the resource's ActivityPub representation, will return the resource's HTML representation.

As with forward discovery, some reverse discovery techniques require the full ActivityPub document -- its JSON properties and content. Others only need the URL of the ActivityPub representation.

When a consumer is doing reverse discovery, it can start with techniques that use the input it has -- document or URL -- and switch to techniques that use the other input only if needed. Converting an ActivityPub document to an ActivityPub URL requires extracting the id property of the object. Converting an ActivityPub URL to a document requires fetching the document and parsing it.

Some resources have a relationship to another resource, which is its author or creator. The author resource has an ActivityPub JSON-LD representation (again, if it doesn't, it's not covered by this document!) and may have an HTML representation.

"Author discovery" is any process that, given the resource's HTML representation, will return the author resource's ActivityPub representation. Author discovery is important because many ActivityPub processes require delivering activities to the author.

There are a few paths for author discovery:

Note that a resource may have an author resource with an ActivityPub representation, but not have its own ActivityPub representation. An example is an article published in a content-management system (CMS) that is ascribed to an actor with an ActivityPub account who wants to receive credit and/or feedback for the work.

As with forward and reverse discovery, the consumer may start with the URL of the resource HTML or the content of the resource HTML.

For some kinds of resources, especially ActivityPub actors, a Webfinger is a more readable alternative to the ActivityPub representation's URL. In these cases, the forward discovery process and author discovery process might include the Webfinger discovery process as an intermediate step:

In this document, the terms "publisher" and "consumer" are used as in Activity Streams 2.0 Core. The terms are extended to include implementations that publish or consume HTML representations of resources with ActivityPub representations.

In this document, we describe several methods of forward discovery, reverse discovery, and author discovery. Different methods are implemented by different publishers and consumers, and have different trade-offs in terms of complexity, performance, and reliability. A section on verification explains how to verify that the discovered information is accurate. The final sections define best practices for publishers and consumers to maximize interoperability and minimize development effort.

Applicable resource types

Unless otherwise specified, the techniques described below can be used with any Activity Streams 2.0 types. The best-defined groups of AS2 types for HTML discovery are actor types:

and digital content types:

Other ActivityPub types are less likely to have their own HTML representations, such as activity types.

Collection types are often better represented by an object they are closely related to. For example, an actor's outbox collection is often provided on the actor's profile page, which is a representation of the actor. Similarly, the likes or replies of an Image object are often provided on the object's page, and don't have an independent HTML representation. That said, this document does not preclude the possibility of HTML representations for collection types.

Motivating user stories

These are some of the user stories that motivate this work.

URLs in examples

This document uses a consistent format for example URLs:

        https://{name}.example/{path}/{type}-{ordinal}{?ext}
      

Where:

The structure used in the examples is merely mnemonic and non-normative. None of the techniques described in this document depend on a particular URL structure, unless otherwise specified.

HTML document to ActivityPub object

Alternately, a consumer may start with the full contents of an HTML document, including markup and other content. For example, a browser-based application may have access to the HTML loaded in the browser window. It's also usually possible to extract the URL from the environment -- for example, using the document.location property in a JavaScript environment. But using the document content for discovery can return the ActivityPub equivalent without the HTTP requests that discovery by URL requires, saving some time and network traffic.

HTML <a> element

The a element is an element used in the <body> section of an HTML document. It can be used to define relationships with other documents, with the benefit that the link is (usually) visible and clickable by a reader.

To indicate its equivalent ActivityPub object, the HTML page at https://html.example/profiles/person-1.html could include the following a element:

        <!doctype html>
        <html>
          <head>
            <title>Person 1</title>
          </head>
          <body>
            <a
              rel="alternate"
              type="application/activity+json"
              href="https://ap.example/users/person-1.jsonld" >
              Actor data for Person 1
            </a>
            <!-- rest of the page -->
          </body>
        </html>
      

Consumers will need to parse the HTML to find the a element with the alternate relation and an ActivityPub-compatible media type as type. This can be even more slow and complicated than with the link header. The link header is usually in the first few kilobytes of a document, and will usually be nested only 2 levels below the document in the DOM tree. An a element may be anywhere in the body, maybe nested very deep in the tree.

a element failure

As with the link element, some servers may include an a element with an alternate relation and with a JSON type or JSON-LD type that does not link to an ActivityPub resource.

In addition, many content management systems allow end users to set rel and other properties on a elements, which may result in false matches. Even more than with other methods, using the a element for discovery requires reverse discovery for confirmation (see Best practices for consumers).

Embedded JSON-LD

HTML documents can include JSON-LD data in a <script> element in the <head> section of the document. This data can be used to provide metadata about the document, including its equivalent ActivityPub object.

Given a page that shows an image at https://html.example/gallery/image-17.html, the HTML for the page could look like this:

        <!DOCTYPE html>
        <html lang="en">
          <head>
            <title>Image 17</title>
            <script type="application/ld+json">
            {
              "@context": "https://www.w3.org/ns/activitystreams",
              "type": "Image",
              "id": "https://ap.example/api/images/image-17.jsonld",
              "url": [
                {
                  "type": "Link",
                  "mediaType": "text/html",
                  "href": "https://html.example/gallery/image-17.html"
                }
              ]
            }
            </script>
          </head>
          <body>
              <h1>Image 17</h1>
              <p><img src="https://html.example/images/image-17.png"></p>
          </body>
        </html>
      

This embedded JSON-LD specifies that an ActivityPub object with the ID https://ap.example/api/images/image-17.jsonld exists, and that it has an HTML page url at https://html.example/gallery/image-17.html, that is, the current page's URL. This is a roundabout, but clear, way to specify the ActivityPub ID of the current page.

Consumers need to parse the HTML page, and the embedded JSON-LD, to extract the ActivityPub object ID. An advantage to this technique is that other properties of the ActivityPub object can be embedded as well; however, to confirm those properties, the consumer will need to fetch the object from its canonical URL, the ID, anyways.

Embedded JSON-LD failure

Complicated structures for the url property may make it hard to confirm that the object's URL is the same as the current page's.

Embedded JSON-LD is very popular for embedding Schema.org metadata. This can lead to false positives when looking for ActivityPub objects.

Resource URL to ActivityPub object

These discovery techniques require an URL as input. Consumers may start with URLs if they are extracting links from RSS feeds or microblogging content, or when converting from other social networking platform content.

Content negotiation

Content negotiation is a catch-all term for ways of negotiating the representation of a resource through the HTTP protocol. In this document, it will specifically cover proactive negotiation using the Accept header.

Given the URL for an HTML document, such as https://mixed.example/some/path/to/note-1, a consumer could attempt to retrieve the corresponding ActivityPub JSON-LD object using this HTTP request:

        GET /some/path/to/note-1 HTTP/1.1
        Host: mixed.example
        Accept: application/activity+json, application/ld+json, application/json
      

A compliant server may respond with the ActivityPub JSON-LD object in the body of the response:

        HTTP/1.1 200 OK
        Content-Type: application/activity+json

        {
          "@context": "https://www.w3.org/ns/activitystreams",
          "id": "https://mixed.example/some/path/to/note-1",
          "type": "Article",
          "content": "This is a note."
        }
      

This is typically used when the ActivityPub server and the HTML server are implemented in the same software package. Because this has historically been the case for many implementations, some consumers expect this behavior to be the default.

Alternately, the server may respond with a 308 Permanent Redirect to indicate the location of the JSON-LD representation.

        HTTP/1.1 308 Permanent Redirect
        Location: https://mixed.example/different/path/to/note-1.jsonld
      
Content negotiation failure

If the server does not support content negotiation, it may respond with a 406 Not Acceptable status code.

          HTTP/1.1 406 Not Acceptable
          Content-Type: text/plain

          No representation matching this request could be found.
        

Less compliant servers may ignore the Accept header altogether and return the HTML content regardless:

          HTTP/1.1 200 OK
          Content-Type: text/html

          <html>
          <head>
          <title>Note 1</title>
          </head>
          <body>
          <p>This is a note.</p>
        

A more difficult failure mode to detect arises when the server does not support ActivityPub, but does support content negotiation for another JSON format. Such a server returns a 200 OK status code with a JSON object that does not use JSON-LD, or JSON-LD object that does not use the Activity Streams 2.0 vocabulary:

          HTTP/1.1 200 OK
          Content-Type: application/json

          {
            "property": "value",
            "otherProperty": "otherValue"
          }
        

Webfinger

Webfinger is a standard for discovering metadata about a resource identified with an URL. Finding the ActivityPub URL for an actor identified with an acct: URL is well documented in the ActivityPub and Webfinger report. However, Webfinger can be used to find metadata about other resources, including HTML pages with https: URLs.

Given an URL for a document, like https://html.example/group-1.html, a GET request can be made to an URL in the /.well-known/ path of the domain for the URL, as follows:

        GET /.well-known/webfinger?resource=https%3A%2F%2Fhtml.example%2Fgroup-1.html HTTP/1.1
        Host: html.example
      

Note that the /.well-known/webfinger path is fixed and required for Webfinger.

A compliant server will respond with the metadata for the resource:

        HTTP/1.1 200 OK
        Content-Type: application/jrd+json

        {
          "subject": "https://html.example/group-1.html",
          "links": [
            {
              "rel": "alternate",
              "type": "application/activity+json",
              "href": "https://ap.example/api/groups/group-1.jsonld"
            }
          ]
        }
      

Note that unlike other URLs used in the examples in this report, the /.well-known/webfinger path is fixed and required for Webfinger.

The JRD JSON format includes a number of properties, as defined in the Webfinger RFC 7033. The relevant data structure in this example is the object in the links array with the rel property set to alternate and the type property set to application/activity+json, an ActivityPub-compatible media type. The href property of this link is URL of the ActivityPub equivalent for the HTML page.

Webfinger failure

Not all Webfinger-aware servers return JRD documents for https URLs. Others might only return JRD documents for URLs that represent actors, such as registered users.

As with other link-relation-based discovery mechanisms, like the HTTP Link header or the <link> element, a JSON or JSON-LD media in the link's type property might not indicate an ActivityPub URL, but some other JSON or JSON-LD object.

ActivityPub ID to HTML Document

These techniques require the ActivityPub object's URL as input. Often, the object's URL is obtained either as a property of another ActivityPub object, or from the id property of the ActivityPub JSON-LD document.

If these techniques aren't successful, the consumer can use the URL to fetch the ActivityPub JSON-LD document, and then use a reverse discovery technique that takes a document as input.

Content negotiation

As with forward discovery, it's possible for the HTML and JSON-LD representations of an object to be found at the same URL.

Examples

Given the URL for an ActivityPub object, such as https://mixed.example/some/path/to/note-1, a consumer could attempt to retrieve the corresponding HTML resource using this HTTP request:

          GET /some/path/to/note-1 HTTP/1.1
          Host: mixed.example
          Accept: text/html
        

A compliant server may respond with the HTML document in the body of the response:

          HTTP/1.1 200 OK
          Content-Type: text/html

          <html>
          <head>
          <title>Note 1</title>
          </head>
          <body>
          <p>This is a note.</p>
        

Alternately, the server may respond with a 308 Permanent Redirect to indicate the location of the HTML representation.

          HTTP/1.1 308 Permanent Redirect
          Location: https://mixed.example/different/path/to/note-1.html
        
Content negotiation failure

If the server does not support content negotiation, it may respond with a 406 Not Acceptable status code.

          HTTP/1.1 406 Not Acceptable
          Content-Type: text/plain

          No representation matching this request could be found.
        

Less compliant servers may ignore the Accept header altogether and return the JSON-LD content regardless:

          HTTP/1.1 200 OK
          Content-Type: application/activity+json

          {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://mixed.example/some/path/to/note-1",
            "type": "Note",
            "content": "This is a note."
          }
        

Webfinger

The Webfinger protocol can be used to find an HTML page related to an ActivityPub object in a number of ways.

The consumer can identify the resource for a Webfinger query in two ways. First, the id property, usually an https URL, can be passed as the resource parameter for the Webfinger query. Alternately, if the ActivityPub object is an actor, an acct URL in the format acct:username@domain.example can be constructed using the technique for Webfinger reverse discovery. This acct URL can be used as the resource parameter for the Webfinger query.

The publisher can provide a link to the HTML representation of the object in the JRD output of the Webfinger query in at least two ways.

First, the links property of the output object can contain a link object with a rel property set to alternate and the type property set to text/html. If such a link exists, its href property is the URL of the related HTML page.

Second, the links property of the JRD output object may include an object with a rel property set to http://webfinger.net/rel/profile-page. This is defined to be "the main home/profile page that a human should visit when getting info about that webfinger account." (https://webfinger.net/rel/) It is not guaranteed to be HTML, but a type property can further define that. Per the definition, "it's likely text/html if it's for users."

An advantage of using Webfinger for discovery is that it is widely implemented by ActivityPub publishers to enable using acct URLs as identities.

Examples

Given an ActivityPub Place object at https://ap.example/geo/place-7.jsonld, a consumer could use a Webfinger query to find the HTML page for the object:

          GET /.well-known/webfinger?resource=https%3A%2F%2Fap.example%2Fgeo%2Fplace-7.jsonld HTTP/1.1
          Host: ap.example
        

Note that the /.well-known/webfinger path is fixed and required for Webfinger.

The publisher could return the following JRD output:

          {
            "subject": "https://ap.example/geo/place-7.jsonld",
            "links": [
              {
                "rel": "alternate",
                "type": "text/html",
                "href": "https://html.example/map/nl/ams/17921.html"
              }
            ]
          }
        

In this example, the links property of the JRD object contains a single object with a rel property set to alternate and a type property set to text/html. The href property of this object is the URL of the HTML page representing the object.

Alternately, given an ActivityPub Person object at https://ap.example/profiles/person-19.jsonld, the consumer could construct an acct URL as acct:person-19@ap.example and use it as the resource parameter for the Webfinger query:

          GET /.well-known/webfinger?resource=acct%3Aperson-19%40ap.example HTTP/1.1
          Host: ap.example
        

Note that the /.well-known/webfinger path is fixed and required for Webfinger discovery.

The publisher could return the following JRD output:

          {
            "subject": "acct:person-19@ap.example",
            "links": [
              {
                "rel": "http://webfinger.net/rel/profile-page",
                "type": "text/html",
                "href": "https://html.example/profiles/person-19.html"
              }
            ]
          }
        

In this output, the http://webfinger.net/rel/profile-page relationship identifies an HTML page for the Person object.

Webfinger failure

Some servers may not return JRD documents for https URLs. Others might only return JRD documents for URLs that represent actors, such as registered users.

ActivityPub object to HTML document

These techniques require the ActivityPub JSON-LD document as the input for the process. The document can be obtained through delivery via the ActivityPub protocol, or through the ActivityPub API, or by other means.

If none of these techniques are successful, the consumer can obtain the URL of the object from the id property, and then try one or more of the techniques that require an URL.

url property

ActivityPub objects can have an optional url property, which "[i]dentifies one or more links to representations of the object." The property is the preferred way to indicate a corresponding HTML page for an ActivityPub object.

As with many Activity Vocabulary properties, this can have several formats:

Examples

In this example, the url property is only a string.

        {
          "@context": "https://www.w3.org/ns/activitystreams",
          "id": "https://ap.example/some/path/person-1.jsonld",
          "type": "Person",
          "name": "Person One",
          "url": "https://html.example/profile/person-1.html"
        }
        

In the next example, the url property is a full Link-type object with mediaType property equal to "text/html".

        {
          "@context": "https://www.w3.org/ns/activitystreams",
          "id": "https://ap.example/geo/place-17.jsonld",
          "type": "Place",
          "nameMap": {
            "en": "Berlin"
          },
          "url": {
            "type": "Link",
            "mediaType": "text/html",
            "href": "https://html.example/map/de/ber/ber.html"
          }
        }
        

In this final example, the url property is an array of Link-type objects with different mediaType properties.

        {
          "@context": "https://www.w3.org/ns/activitystreams",
          "id": "https://ap.example/photos/gallery/image-3.jsonld",
          "type": "Image",
          "summary": "Jason and Carol at the lake house",
          "url": [
            {
              "type": "Link",
              "mediaType": "text/html",
              "href": "https://html.example/gallery/3.html"
            },
            {
              "type": "Link",
              "mediaType": "image/webp",
              "href": "https://upload.example/files/08/17/2021/lakehouse.webp"
            }
          ]
        }
        
url property failure

When the url property is only a string, it may not represent an HTML page. Especially for objects with binary content types, like Image, Video, and Audio, the url property is often used for the URL of the respective binary representation of the object.

The mediaType of Link-type objects in the url property is not always defined, and when it is defined, it is not always "text/html".

The property is defined only for representations of the current object. However, the Link-type object can have a link relation property, rel. Publishers may misuse the url property to including links that aren't a representation of the object, but instead a related object, like "next" or "author".

Resource URL to author ActivityPub object

This section describes techniques for discovering an ActivityPub author object using the HTML representation's URL as an input. The HTML representation's URL may come from ActivityPub properties, links in other content, or by other means.

If these techniques do not succeed, consumers can fetch the content of the HTML document and use one of the techniques that require HTML as input.

HTML document to author ActivityPub object

These techniques require the contents of the HTML representation. This could already be available in a browser environment, or through other means.

If these techniques don't succeed, it's sometimes possible to obtain the URL of the document through the environment, for example, using the document.location property in a browser JavaScript program. Consumers can then try techniques that use the URL as input.

OpenGraph Protocol

The OpenGraph Protocol is a set of metadata properties that can be included in an HTML document to provide information about the document. Several of these properties can be used to identify the author of the document; usually with the URL of an HTML page that has a profile type.

For these properties, the most likely discovery path is to follow the property's URL value to an HTML page for the profile, and then use forward discovery to find the ActivityPub object corresponding to that profile.

Another option is the fediverse:creator property, developed by Mastodon. This property is used to identify the creator of a document. Its value is the Webfinger ID of the creator, prefixed with an @ character, not an acct: URL. The consumer can use the Webfinger protocol to find the ActivityPub object corresponding to the creator.

The advantage of using OGP for metadata is that it is widely implemented by Web publishing systems.

Example

Given an HTML page at https://html.example/files/article-40.html that represents a Article object, the consumer can use the OpenGraph metadata to identify the URL of HTML profile for the author of the article:

          <!doctype html>
          <html>
            <head>
              <title>Article 40</title>
              <meta property="article:author" content="https://html.example/profiles/person-7.html" />
            </head>
            <body>
              <!-- rest of the page -->
            </body>
          </html>
        

In this example, the article:author property is the author of the document. (Note that OpenGraph Protocol uses the non-standard property attribute for metadata.)

Given an HTML page at https://html.example/files/video-40.html that represents a Video object, the consumer can use OpenGraph metadata to identify the Webfinger ID of the creator of the video:

          <!doctype html>
          <html>
            <head>
              <title>Video 40</title>
              <meta property="fediverse:creator" content="@person-22@ap.example" />
            </head>
            <body>
              <!-- rest of the page -->
            </body>
          </html>
        

In this example, the fediverse:creator property is the Webfinger ID of the creator of the video, prefixed with the @ symbol. The Webfinger ID can be used to find the ActivityPub object corresponding to the creator.

OpenGraph Protocol failure

The OpenGraph Protocol is not widely used for identifying authors of documents. When it is used, the profile page may not support discovery of an ActivityPub object.

Verification

Publishers of HTML representations and ActivityPub representations include data or metadata to help with discovery of related representations or resources. This data is a claim that the linked resource really has the relationship stated.

Unfortunately, not all claims are true. Consumers need to verify the claims made by publishers, using the verification techniques described here. Some techniques are direct and can be used with confidence; others are heuristics that provide some level of support to the claims, but are not foolproof.

Representation verification

Verification is a process of confirming that an HTML page and an ActivityPub object represent the same resource. This is necessary to ensure that the publisher of one representation is not falsely connecting two unrelated resources. These forms of verification are valid for both forward and reverse discovery.

Two-way discovery

The most reliable verification method is two-way discovery. This consists of first doing discovery in one direction, and then doing discovery in the other direction with the results. For example, doing forward discovery from an HTML page to an ActivityPub object, and then doing reverse discovery from the ActivityPub object to, hopefully, the same HTML page.

Example

Given an HTML page with the URL https://html.example/downloads/image-14.html, the consumer could discover the ActivityPub JSON-LD URL using the Link header method:

            HEAD /downloads/image-14.html HTTP/1.1
            Host: html.example
          

The publisher would respond with the HTTP headers:

            HTTP/1.1 200 OK
            Content-Type: text/html
            Link: <https://ap.example/api/images/image-14.jsonld>; rel="alternate"; type="application/activity+json"
          

Then, the consumer could fetch the ActivityPub object at https://ap.example/api/images/image-14.jsonld and look for the URL of the HTML page in the url property:

            {
              "@context": "https://www.w3.org/ns/activitystreams",
              "id": "https://ap.example/api/images/image-14.jsonld",
              "name": "Image 14",
              "type": "Image",
              "url": "https://html.example/downloads/image-14.html"
            }
          

By comparing the URL in the ActivityPub object with the original HTML page URL, the consumer can confirm that the two representations relate to the same resource.

Two-way discovery failure

Two-way discovery can fail if the discovered representation does not have a path back to the origin representation.

Two-way discovery can become complicated if more than one discovered representation is found, which may connect back to more than one original representation. Verifying multiple relationships, and ignoring those that cannot be verified, complicates this process for the consumer.

Same origin

Another mechanism for verifying a discovery process is to compare the origins of the original representation's URL and the discovered representation's URL. The origin is the combination of the scheme, host, and port of a URL. If the origins are the same, the two representations can be considered related.

Content negotiation without a redirect will always have the same origin, as the URL of the HTML page and the URL of the JSON-LD representation are the same.

Example

Given an HTML page with the URL https://mixed.example/profiles/person-3, the consumer could discover the ActivityPub JSON-LD URL using the <link> element method:

            <link
              rel="alternate"
              type="application/activity+json"
              href="https://mixed.example/api/person/person-3" />
          

The href property of the <link> element is the URL of the ActivityPub JSON-LD representation. The origin of the URL of the HTML page is https://mixed.example, and the origin of the URL of the ActivityPub JSON-LD representation is https://mixed.example, so the origins match and the discovery is verified.

Same origin failure

Same origin verification assumes that a single publisher controls an entire domain. Although this is often true for machine-readable formats like JSON-LD, having multiple publishers in control of parts of a domain is more common for HTML documents. For example, documents with URLs starting with https://html.example/home/user1/ might be created by one user, and those starting with https://html.example/home/user2/ might be created by another. A carefully crafted <link> or other mechanism could be used by one user to link their HTML page to an ActivityPub object created by another.

Same origin verification will give a false negative if the publisher is using different domains for HTML pages and ActivityPub JSON-LD objects. This can happen if ActivityPub features are added on to an existing published Web site, or if the publisher needs to keep the domains separate for implementation reasons. If same origin verification gives a negative result, other methods such as two-way verification should be used.

Allowlist

Another means of verification, or more precisely an excuse for skipping verification, is an allowlist. This is a list of origins or, possibly, other properties of the representation that can be used to confirm trust in the publisher and skip verification.

Assuming that each origin is controlled by a single publisher, if the consumer trusts the publisher, they can skip verification of discovery when the original representation has an URL with that origin.

Example

Given an HTML page with the URL https://html.example/profiles/person-3, the consumer could discover the ActivityPub JSON-LD URL using the Embedded JSON-LD method:

            <script type="application/ld+json">
            {
              "@context": "https://www.w3.org/ns/activitystreams",
              "id": "https://ap.example/api/person/person-3",
              "type": "Person",
              "name": "Person Three"
              "url": "https://html.example/profiles/person-3"
            }
            </script>
          

Given that the consumer trusts the publisher of https://html.example, they can skip verification of the discovery process, and accept https://ap.example/api/person/person-3 as the ActivityPub JSON-LD representation's URL.

Allowlist failure

Maintaining an allowlist is time-consuming. The number of domains with Web sites is in the hundreds of millions; identifying even a tiny fraction to be trusted takes a lot of human effort.

Depending on allowlists as the only means of verification severely limits the number of domains that can be interacted with.

Author verification

Verification of author discovery is necessary to ensure that attackers cannot maliciously ascribe content to an actor that did not create it.

Outbox verification

Unfortunately, the only current way to fully verify the authorship of an HTML object is by scanning the outbox property of the actor object. With tens or hundreds of thousands of items in the outbox not unusual, this is a time-consuming process that is subject to possible errors.

outbox properties in ActivityPub are OrderedCollection objects, often with OrderedCollectionPage objects that represent pages of content. Scanning this collection from newest to oldest members, the consumer can look for Create activities with the url property set to the HTML representation URL being verified, or for activity objects with the url property set to the HTML representation being verified.

Example

The consumer has discovered that https://ap.example/user/person-6.jsonld is the author of the resource represented by the HTML document at https://html.example/blog/article-9.html. The consumer retrieves the ActivityPub JSON-LD for the person:

          {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://ap.example/user/person-6.jsonld",
            "type": "Person",
            "name": "Person Six",
            "inbox": "https://ap.example/user/person-6/inbox",
            "outbox": "https://ap.example/user/person-6/outbox",
            "following": "https://ap.example/user/person-6/following",
            "followers": "https://ap.example/user/person-6/followers",
            "liked": "https://ap.example/user/person-6/liked"
          }
          

The consumer then fetches the URL that is the value of the outbox property:

          {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://ap.example/user/person-6/outbox",
            "type": "OrderedCollection",
            "totalItems": 3803,
            "first": "https://ap.example/user/person-6/outbox/page/39"
          }
          

It fetches the first page of the collection:

          {
            "@context": "https://www.w3.org/ns/activitystreams",
            "id": "https://ap.example/user/person-6/outbox/page/39",
            "type": "OrderedCollectionPage",
            "partOf": "https://ap.example/user/person-6/outbox",
            "next": "https://ap.example/user/person-6/outbox/page/40",
            "items": [
              {
                "type": "Create",
                "actor": "https://ap.example/user/person-6.jsonld",
                "object": {
                  "id": "https://ap.example/object/article-10.jsonld",
                  "type": "Article",
                  "name": "Article Ten",
                  "url": "https://html.example/blog/article-10.html"
                }
              },
              {
                "type": "Create",
                "actor": "https://ap.example/user/person-6.jsonld",
                "object": {
                  "id": "https://ap.example/object/article-9.jsonld",
                  "type": "Article",
                  "name": "Article Nine",
                  "url": "https://html.example/blog/article-9.html"
                }
              },
              {
                "type": "Create",
                "actor": "https://ap.example/user/person-6.jsonld",
                "object": {
                  "id": "https://ap.example/object/article-8.jsonld",
                  "type": "Article",
                  "name": "Article Eight",
                  "url": "https://html.example/blog/article-8.html"
                }
              }
            ]
          }
          

The second object in the items array is a Create activity with an object property with a value that includes an url property with same value as the HTML URL we are trying to verify. Since the claim from the HTML and the ActivityPub JSON-LD support each other, the relation is verified.

Outbox author verification failure

This method is error-prone and depends on fetching hundreds or thousands of pages. Not all pages will include the full representation of the objects in their items array; some may just include the id property of each, requiring even more requests.

Some authors may not include a Create activity for every object they create on the Web. They may use an author discovery process to identify the author, but not include the object in the ActivityPub representation.

Same origin verification

This method, or heuristic, assumes that the claims of authorship are mutually supporting if the URLs of the representations have the same origin. The origin of an URL includes its protocol, domain name, and port number. The method assumes that the same entity (like a person or organization) controls all URLs published with this origin, and therefore would not make claims to contradict itself.

Example

Given an HTML page with the URL https://mixed.example/profiles/person-3.html, the consumer could discover the ActivityPub JSON-LD URL using the Embedded JSON-LD method with the value https://mixed.example/api/person-3.jsonld. Because both URLs have the origin https://mixed.example, the consumer will assume that the discovery is verified.

Same origin verification failure

Assuming that the same entity controls creation of all URLs on a server is somewhat risky. For HTML creation, especially, some servers divide up into per-user paths. Other servers allow user-uploaded data, including JSON and HTML.

Allowlist verification

Consumers may include a list of origins or other properties of the representation that don't require verification. This assumes an externally-established trust relationship.

Example

Given an HTML page with the URL https://html.example/profiles/person-3.html, the consumer could discover the ActivityPub JSON-LD URL using the Embedded JSON-LD method with the value https://ap.example/api/person-3.jsonld. If the consumer trusts the publisher of https://html.example, they can skip verification of the discovery process, and accept https://ap.example/api/person-3.jsonld as the ActivityPub JSON-LD representation's URL.

Allowlist verification failure

Establishing trust relationships out-of-band is labor intensive, and most consumers will only have a small number of trusted domains or other entities.

Best practices for consumers

This section describes some best practices for consumers of HTML and ActivityPub.

Discovery techniques

Forward discovery

Consumers with the HTML representation's URL as input should try these techniques:

  • HTTP Link.This is a fast and easy way to identify a related ActivityPub object. It will work whether the HTML and ActivityPub representations are on the same server or on different ones. It will also work with binary media types like images or videos.
  • Content negotiation. For servers that implement both the HTML and ActivityPub representations of the object, this is an easy method to use. It will not work for ActivityPub implementations assembled from different server systems, so it should be supplemented with other methods.

Consumers with the HTML document as input should try these techniques:

  • HTML <link>. This element is somewhat easier to fetch than others, and requires no further parsing. It's always at the second level of element hierarchy, below the head element.
  • HTML <a>. This element is often buried deep in the element tree of an HTML document.

If discovery with the URL as input is unsuccessful, fetching the document may provided better information. Similarly, if discovery with the document does not succeed, obtaining the URL and using it for discovery may work.

Consumers with rigourous discovery requirements, like indexers or search engines, can try additional discovery methods like Webfinger or embedded JSON-LD. However, it's unlikely that publishers that haven't implemented one of the above methods would implement more obscure methods.

Reverse discovery

Consumers with the ActivityPub representation's URL as input should try the following techniques:

  • HTTP Link This method is simple, doesn't require a lot of parsing, and works even if the HTML and ActivityPub representations are on different servers.
  • Content negotiation. Many servers that implement both the ActivityPub and HTML representations of a resource use this method.

Consumers with the ActivityPub document as input should try these techniques:

  • url property. This is a straightforward method that is defined within the Activity Streams 2.0 specification.

If these techniques are exhausted, using the other type of input is a good next step.

Other discovery techniques are unlikely to be used by publishers if these are not.

Author discovery

Consumers with the URL of HTML representation as input should start with these techniques:

  • HTTP Link. Similar to forward discovery, this is a very quick and easy way to identify a related author when an URL is available. It will work for several types of server configuration.

Consumers with the HTML document as input should try these techniques:

  • HTML <link>. There's a long tradition of using this property for linking metadata. It's also easy to parse with the DOM, and doesn't require using queries or deep traversal of element trees.
  • HTML <a>. This is an easy property for content management system users to set directly.
  • OpenGraph. The OpenGraph properties, like fediverse:creator, are easy to fetch from the head of the document.

Failing these, the next best option is to discover the ActivityPub representation of the resource, and then use ActivityPub properties to discover the author.

More demanding consumers may want to continue with discovery of the author's HTML profile page, but this can be a complicated process.

Verification

Consumers should do verification of forward and reverse discovery.

Two-way discovery is the most reliable way to verify results, but can have resource overhead.

Same-origin verification can be reliable for reverse discovery, but is less so for forward discovery or author discovery.

Allowlists are a last resort when other verification is not possible.

Best practices for publishers

Publishers that want consumers to be able to discover ActivityPub object and their authors should consider these methods.

Publishing ActivityPub JSON-LD

When publishing an Activity Streams 2.0 JSON-LD object for ActivityPub, publishers should consider these best practices.

Publishing HTML

When publishing HTML representations of an ActivityPub resource, include these discovery options: