Authenticated Previews

Overview

Authenticated Previews make your content preview correctly on Workplace. This entails providing support for authenticated preview metadata in a format Workplace expects, such that when URLs to your content are shared, Workplace can fetch that metadata and create a preview. This process is often called link unfurling.

While Facebook is able to get metadata to unfurl a public URL via the Open Graph protocol and the Facebook Crawler, this process is not possible for private URLs, which are more commonly shared into Workplace. Instead, when someone shares a private URL into Workplace, a webhook will be issued to a callback URL you define, and you'll be able to respond with a metadata payload describing the URL for the person sharing it, in order to render a link preview.

This same process will repeat for each person who views the shared URL on Workplace, allowing you to control the preview visibility on a per-user basis.

Authenticated for Workplace allow people to share information safely and easily on Workplace, in a way that respects the privacy of the source material. By supporting Authenticated Previews, you can ensure that your content previews correctly on Workplace, in a secure privacy-aware way.

Sharing Private URLs

On Workplace, people often share links to internal company resources, which should only be viewable by certain people. This is in contrast to Facebook, where people commonly share public content, like news articles or blog posts.

Public and private URLs on Workplace

In order for Workplace to generate a preview to company-private content, some metadata needs to be provided. As a provider, you can choose whether to provide metadata for the current viewer on Workplace, depending on whether they're allowed to view the content or not.

In this document

This document outlines the components of enabling Authenticated Previews, including:

Configuration

In order to enable authenticated previews, you'll need to configure your app for the following:

  • Be installed on a Workplace community, or in one or more groups
  • The Link Unfurling permission (currently available under whitelist)
  • A domain or set of domains, comprising the URLs that will be unfurled by your integration
  • A subscription to the webhook topic Link, for the field preview, and a callback URL for providing metadata

Webhooks

Receiving Webhooks

There are multiple situations where we will send a request to the provider:

  1. If a new url is shared that we have not yet seen before (always from composer).
  2. If a new user is seeing a piece of content and we do not know whether the user has access (always from feed).
  3. A existing piece of content is re-shared or has expired (from either composer or feed).

Webhook Request Format

In any of the above scenarios, a webhook will be sent as a POST request, in the following format:

{
  "object": "link",
  "entry": [
    {
      "time": int,
      "changes": [
        {
          "field": "preview",
          "value": {
            "community": {
              "id": string,
            },
            "user": {
              "id": string,
            },
            "link": string,
          }
        }
      ]
    }
  ]
}
    

This payload contains the following fields:

Field NameDescription

object

The webhook topic. This is always link in this context.

entry

A list of requests, is always exactly one.

entry.time

The time when the request was sent.

entry.changes

A list of changes in this request, is always exactly one.

entry.changes.field

The webhook field, is always preview.

entry.changes.value

The actual object containing the context of the request.

entry.changes.value.field.community

The community of the user that triggered the request.

entry.changes.value.field.user

The user that triggered the request.

entry.changes.value.field.link

The link that Workplace is attempting to render, which matches the domain and regex configured by the app.

Example

POST /callback HTTP/1.1
Host: third-party.com
Accept: application/json
Content-Type: application/json
User-Agent: Webhooks/1.0 (https://fb.me/webhooks)
X-Hub-Signature: sha1=bf3102e52efd0fd4bd26277030aa180d7b5cf587
...

{
    "object": "link",
    "entry": [{
        "time": 1501515097793,
        "changes": [{
            "field": "preview",
            "value": {
                "community": {
                    "id": "138169208138649"
                },
                "user": {
                    "id": "88575656148087"
                }
                "link": "https://company.third-party.com/document-about-this"
            }
        }]
    }]
}
    

Webhook Response Format

When you receive a webhook request, you will need to provide a metadata payload in a specific response format:

{
  "data": [
    {
      "link": string,
      ?"canonical_link": string,
      ?"title": string,
      ?"description": string,
      ?"icon": string,
      ?"download_url": string,
      "privacy": 'organization' | 'accessible' | 'inaccessible',
      ?"type": 'document' | 'folder' | 'task' | 'link',
      ?"additional_data": [
        {
          "title" => string,
          "format" => 'text' | 'date' | 'datetime' | 'user',
          "value" => string | number,
          ?"color" => 'blue' | 'green' | 'yellow' | orange' | 'red',
        },
      ],
    }
  ],
  ?"linked_user": boolean
}

This payload will be expected to contain the following fields:

Field NameDescription

data

A collection of items available to the user, this can be empty if the app chooses to not unfurl this link for this user at all.

linked_user

A boolean field indicating whether the third party is aware of the user - if this is set to false, we will show a linking dialog.

data.link

A unique identifying link for this item, must match the link in the request.

data.canonical_link

A canonical url representation of this content. If different from the link this content will be associated with the canonical content for easier querying of the related shares.

data.title

The title of this item, must be present except for items that have the privacy set to inaccessible.

data.description

A short description of the item that will be rendered in the rich preview.

data.icon

A asset for this content for places where Workplace shows a icon of the content. This must be a url and publicly accessible. For best results the asset should be a 16px square.

data.download_url

A URL from which Workplace can download a PDF representation of the item to convert it into a image post. This will be ignored for anything except document and link object types.

data.privacy

Denotes the object's privacy. This can either be organization or accessible or inaccessible. If organization, then Workplace assumes this item can be shown to everybody within the community, even without linking of the account; accessible means it is available to the logged in user, but not necessarily to anybody else; inaccessible means that this document is not available to this user.

data.type

This can either be document, folder, task or link. A folder is a collection of other folders or documents. Must be present except for items that have privacy set to inaccessible.

data.additional_data

A collection of metadata that will be rendered in the rich preview. Will be ignored for document and folder. Will only use the first three elements. For more details on the format of these fields, see the Additional Data section.

Full Response Example

HTTP/1.1 200 OK
Content-Type: application/json
X-Hub-Signature: sha1=b5a6f32f084100ae5b355174b9bb8398f5fbe983
...

{
  "data": [
    {
      "link": "https://taaskly.herokuapp.com/task/4",
      "title": "Launch Workplace Integration for F8",
      "privacy": "organization",
      "type": "task",
      "additional_data": [
        {
          "title": "Owner",
          "format": "user",
          "value": "319922278498384"
        },
        {
          "title": "Created",
          "format": "datetime",
          "value": "2018-02-28T03:35:40.827Z"
        },
        {
          "title": "Priority",
          "format": "text",
          "value": "high",
          "color": "red"
        }
      ]
    }
  ],
  "linked_user": true
}
    

Privacy Modes

The privacy mode determines the visibility, whether we require user auth for the current and subsequent users.

  1. organization: Visible To User, No user Auth required
    If it is visible to user we can show the content directly. This handles the case where a piece of content is supposed to be visible to anybody in the domain (company). We will not bother to send a webhook every time someone new is seeing the new content.
  2. accessible: Visible to current user, but user identity mapping may be required for others.
    The provider knows that this particular user is allowed to see that piece of content but this does not mean anyone else can necessarily see it. We show the preview but will continue sending webhooks for any other user.
  3. inaccessible: Not visible to user
    The provider knows the user and knows they are not allowed to see the content. We render a privacy notice (unavailable, private etc).

Additional Data

To add more information on a link preview, you can send up to **three** items of additional data. An additional data item is comprised of a set of key -alue elements. But the value can be formatted in different ways.

Currently four different formats are supported:

  • text: Will render the value as is, the value must be a string. For this format additional data can also contain a property color which must be one of the values blue, green, yellow, orange or red. If present, this will render the value with the color as a background.
  • date: Will parse the value as ISO-8601 date format without time and render it without time indication.
  • datetime: Will parse the value as ISO-8601 date format with time and timezone and render with with time indication in the users timezone.
  • user: Will parse the value as a user id and render the user's name.

Rendering File Previews (Optional)

If the privacy mode for your document is marked as either organization or accessible, and a download URL is provided, we will send an additional request to download the data.

GET /download/super-fancy-document HTTP/1.1
Host: provider.com
Accept: <some mime types>
User-Agent: Webhooks/1.0 (https://fb.me/webhooks)
X-Hub-Signature: sha1=bf3102e52efd0fd4bd26277030aa180d7b5cf587

Workplace will then take this file and convert it to photos to make a multi-photo post out of it.

Identity Mapping

As seen above, providing authenticated previews requires some kind of identity mapping. Identity mapping controls whether a Workplace user has the permission to see the content being previewed, and ensures that the access rules of your content are respected on Workplace.

Organization-Wide mapping

The simplest form of identity mapping is organization-wide mapping, where membership of a Workplace community is sufficient to allow previews to be shown to users on Workplace. This scenario is common when previewing links to a company-wide intranet, or any service where all objects (or at least their metadata) are visible to the entire company.

When your integration is installed on a Workplace community, the community ID can be checked via the /community endpoint, using the access token you retrieved upon install. You can store this community ID along with the token, and map it to a tenant or organization identifier inside your service. This creates a mapping between a Workplace community and the organization in your service.

Then, when responding to authenticated preview webhook requests, you can check that the webhook payload's community ID matches that organization-linked community ID, then decide whether it's safe to return a metadata payload. Marking that payload's privacy as ORGANIZATION ensures that we won't need to send additional webhooks for each user in that Workplace community.

Per-User Mapping

If more permission granularity is needed, you can support a per-user mapping, ensuring that you can choose whether to render metadata to each viewer on Workplace individually.Workplace sends over a user ID in each webhook payload for authenticated previews. To support per-user mapping, you'll need to know which user record in your system maps to the Workplace user ID sent in the webhook payload.

If this is your first encounter for a given Workplace user ID, you can respond with the boolean field linked_user set to false. This will instruct Workplace to show an Enable Preview button, prompting the user to link their account.

When the button is pressed, Workplace will open a dialog to an account linking endpoint that you define, where you can validate the user session in your service. Workplace will open this URL via a POST request, and will pass a signed request parameter, which contains the current user ID and community ID.

POST https://www.example.com/account_linking?redirect_uri=https%3A%2F%2Ffoxfabrics.facebook.com%2Flink_complete HTTP/1.1
Host: foxfabrics.third-party.com
Origin: http://www.facebook.com
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 Gecko/20100101 Firefox/57.0
...

signed_request=238fsdfsd.oijdoifjsidf899
    

In the payload is a parameter signed_request which contains information about this user. This request can be decoded as follows:

  1. Split the content into two parts delimited by a '.' character.
  2. Decode the first part - the signature - from base64url.
  3. Decode the second part - the payload - from base64url and then decode the resultant JSON object.
  4. Verify that the signature matches the HMAC of the encoded payload based on your App Secret.

The payload contains the following fields:

{
  "algorithm": "HMAC-SHA256",
  "user_id": "88575656148087",
  "community_id": "138169208138649"
}
    

At this point, you'll have a Workplace user ID and community ID, along with a validated user session in your service, and will be able to record the Workplace ID for this user beside their ID in your service. Upon completion, you should redirect to the url provided on the original request as query param redirect_uri.

GET {$redirect_uri} HTTP/1.1
Host: foxfabrics.facebook.com
User-Agent: Mozilla/5.0 Gecko/20100101 Firefox/57.0
Referer: https://www.example.com/account_linking
...
    

Workplace will then recognize that the identity mapping is complete, and will attempt to request authenticated preview metadata again. This time, you'll be able to respond with linked_user set to true, and provide the metadata required.

Note:

This entire round-trip should only happen once each time an unrecognised user tries to preview a piece of content for the first time. Once the identity mapping is completed, subsequent previews should be able to bypass this round-trip.

FAQ

No, Workplace Authenticated Previews do not have the same performance requirements or unsubscribe behavior as Messenger Platform webhooks.

So that people using Workplace have a good experience, the full HTTP roundtrip should take less than 5 seconds as measured from the Workplace side (i.e., the HTTP client side).

For estimating / expectation standpoint you can plan around this behavior. You may observe some differences in practice due to retries or race conditions.

I. When a Person Views Organization-Privacy Content

  1. Workplace sends a webhook request when first person views this content
  2. Generally, no further webhooks requests will be sent for this same content until 30-60 minutes has elapsed

II. When a Person Views Restricted- or Inaccessible-Privacy Content

  1. Workplace sends a webhook request every time a person views that content for the first time
  2. Generally, no further webhook requests will be sent for this same person+content combination until 30-60 minutes has elapsed

III. When a Person Creates a Post That Links to Content

  1. Every time a person creates a new post Workplace will always query for metadata about linked content.

No, Workplace will not automatically disable a link webhooks subscription.