Server-side integration

Publishers primarily use server-side integration for managing readers and their entitlements. Primarily, publishers use UpdateReaderEntitlements to update Google's record of a Product ID entitlement for a PPID.

Google Cloud setup

Configuring Subscription Linking in Google Cloud includes two major components:

  1. Enabling the API for a given project
  2. Creating a service account for accessing the api

Enable the Subscription Linking API

To use a service account and manage a reader's entitlements, a Google Cloud project must have both the Subscription Linking API enabled, and a properly configured OAuth service account. To enable the Subscription Linking API for a project, navigate from the menu -> APIs & Services -> Library and search for Subscription Linking, or visit the page directly:


https://console.cloud.google.com/apis/library?project=gcp_project_id

api

Figure 1. Navigating to the API Library, and enabling the API for a Google Cloud project.

Create a Service Account

Service accounts are used to allow access from your application to the Subscription Linking API.

  1. Create a service account within your project's console.
  2. Create credentials for the service account, and store the credentials.json file in a secure location accessible to your application.
  3. Grant the IAM role "Subscription Linking Admin" to the service account you created. For granular control over the capabilities of the service account, you can assign the appropriate role from the following table.
Capability / Role Subscription Linking Admin Subscription Linking Viewer Subscription Linking Entitlements Viewer
Get reader entitlements
Get readers
Update reader entitlements
Delete readers

Use service accounts with the Subscription Linking API

Use service accounts to authenticate calls to the Subscription Linking API, either with the googleapis client library or by signing requests with the REST API. Client libraries automatically handle requesting the appropriate access_token, while the REST API requires retrieving an id_token and then exchanging it for an access_token.

Both the following client library and REST API examples use the getReader() endpoint. For a live demonstration of all of the API methods, see the Subscription Linking Demo site, or its code.

Sample request with the node.js googleapis client library

import {readerrevenuesubscriptionlinking_v1, Auth} from 'googleapis';
const subscriptionLinking = readerrevenuesubscriptionlinking_v1.Readerrevenuesubscriptionlinking;

class SubscriptionLinking {
  constructor() {
    this.auth = new Auth.GoogleAuth({
      keyFile: process.env.KEY_FILE,
      scopes: [
        'https://www.googleapis.com/auth/readerrevenue.subscriptionlinking.manage'
      ],
    })
  }

  init() {
    return new subscriptionLinking(
        {version: 'v1', auth: this.auth})
  }
}

const api = new SubscriptionLinking();
const client = api.init();

async function getReader(ppid) {
  const publicationId = process.env.PUBLICATION_ID;
  return await client.publications.readers.get({
    name: `publications/${publicationId}/readers/${ppid}`,
  });
};

async function updateEntitlements(ppid) {
  const publicationId = process.env.PUBLICATION_ID;
  const requestBody = {
    /*
    Refer to
    https://developers.google.com/news/subscribe/subscription-linking/appendix/glossary#entitlements_object
    */
    entitlements : [{
      product_id: `${publicationId}:basic`,
      subscription_token: 'abc1234',
      detail: 'This is our basic plan',
      expire_time: '2025-10-21T03:05:08.200564Z'
    }]
  };
  return await client.publications.readers.updateEntitlements({
    name: `publications/${publicationId}/readers/${ppid}`,
    requestBody
  });
};

Manually signing REST API requests

import fetch from 'node-fetch'
import jwt from 'jsonwebtoken'

function getSignedJwt() {
  /*
    Either store the credentials string in an environmental variable
    Or implement logic to fetch it.
  */
  const key_file = process.env.CREDENTIALS_STRING

  const issueDate = new Date()
  const expireMinutes = 60
  const offsetInSeconds = issueDate.getTimezoneOffset() * 60000
  const expireDate = new Date(issueDate.getTime() + (expireMinutes * 60000))
  const iat = Math.floor((issueDate.getTime() + offsetInSeconds) / 1000)
  const exp = Math.floor((expireDate.getTime() + offsetInSeconds) / 1000)

  const token = {
    iss: key_file.client_email,
    iat,
    exp,
    aud: 'https://oauth2.googleapis.com/token',
    scope:'https://www.googleapis.com/auth/readerrevenue.subscriptionlinking.manage',
  }
  return jwt.sign(token, key_file.private_key, {
    algorithm: 'RS256',
    keyid: key_file.private_key_id,
  })
}

async function getAccessToken(signedJwt) {
  let body = new URLSearchParams();
  body.set('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer')
  body.set('assertion', signedJwt)
  const request = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    body
  })

  const accessResponse = await accessFetch.json()
  return accessResponse.access_token
}

async function getReader(ppid) {
  const publicationId = process.env.PUBLICATION_ID
  const base_url = 'https://readerrevenuesubscriptionlinking.googleapis.com/v1'
  const endpoint = `${base_url}/publications/${publicationId}/readers/${ppid}`
  const signedJwt = await getSignedJwt()
  const accessToken = await getAccessToken(signedJwt)

  const reader = await fetch(endpoint, {
     method: 'GET',
     headers: {
       Authorization: `Bearer ${accessToken}`,
     },
   }).then((response) => {
    return response.json()
  })

  return reader
}