Creating Google AdSense Reports with Express

I wanted to send a daily report of my apps' AdMob income to my phone using the PushOver notification service, but couldn't find any (working) examples to fetch the Google AdSense (+AdMob) revenue using Node.js and Express.js. For most other languages there are code examples, but not for the Google-API-NodeJS-Client - we will change that.

#Setup

We will use the google-api-nodejs-client and authenticate through OAuth. I assume you already set up a Web application at the Google Developer Console, added the AdSense Management API and created the credentials. (You can create a localhost credential for testing purposes.) Download the credentials as a .json file (clientsecret*.apps.googleusercontent.com). The Getting Started guide describes how to do that.

Create a new project and install the dependencies with npm install --save express googleapis.

#Authentication

We will use Google OAuth for Authentication in our Express.js app. Therefore, we need to define the scope of our access (adsense.readonly) and provide a path (/auth/google) that redirects to Google's Consent - Grant Access - page. When the user confirms, Google passes the authentication tokens as parameters to a callback path defined as in the credentials file (redirect_uris). Don't worry, we only need to authenticate manually once to receive a refresh_token. We save it and can then always request fresh access_tokens when these expire.

import google from 'googleapis'
const config = require('./client_secret_*.apps.googleusercontent.com.json').web

const scope = 'https://www.googleapis.com/auth/adsense.readonly'

const oauth2Client = new google.auth.OAuth2(
  config.client_id,
  config.client_secret,
  config.redirect_uris[0],  // may NOT be an array. Otherwise, the consent site works, but silently fails in getToken.
)

const consentURL = oauth2Client.generateAuthUrl({
  access_type: 'offline', // 'online' (default) or 'offline' (gets refresh_token)
  scope,  // If you only need one scope you can pass it as string
  prompt: 'consent',    // always prompt for consent
})

app.get('/auth/google', (req, res) => {
  res.redirect(consentURL)
})

// oauth2callback as defined in config.redirect_uris[0] in the Google Dev Console
app.get('/oauth2callback', (req, res) => {
  getTokens(req.query.code,
    (tokens) => {
      // save tokens somewhere in a DB or a file
      res.send(`Received code: ${req.query.code}<br>Tokens: ${JSON.stringify(tokens)}<br>Save them.`)
    },
    (err, response) => res.send(`Received an error while trying to get tokens with code ${req.query.code}: ${err}\n${JSON.stringify(response)}`),
  )
})

function getTokens(code, sucCallback, errCallback) {
  oauth2Client.getToken(code, (err, tokens, response) => {
    if (!err) {
      // set the tokens here for future API requests
      oauth2Client.setCredentials(tokens)
      sucCallback(tokens)
    } else {
      errCallback(err, response)
    }
  })
}

#Fetching AdSense Reports data

Once your client is authenticated and has its tokens set, you can start fetching your revenue data using the adsense API of googleapis. Updating expired access_tokens if necessary is handled in oauth2Client.getAccessToken(callback). You can customize the query by specifying start and end dates, or split the total earnings into the individual Ads by providing a dimension. For playing around and figuring out how to use the AdSense API, the documentation provides a useful form.

// make sure oauth2Client's credentials are set
// with oauth2Client.setCredentials(tokens) as in getTokens
// or somewhere else with the saved tokens

app.get('/adsense', (req, res) => {
  getLatestReport(
    (err, reportString) => {
      if (err) {
        // Send error per push notification, E-Mail etc.
      } else {
        // Send report per push notification, E-Mail etc.
        // send(reportString)
      }
    })
})

function getLatestReport(callback) {
  const adsense = google.adsense('v1.4')
  // Get a non-expired access token, after refreshing if necessary https://github.com/google/google-auth-library-nodejs/blob/master/lib/auth/oauth2client.js
  oauth2Client.getAccessToken((err, accessToken) => {
    if (err) {
      callback(`getAccessToken Error: ${err}`)
      return
    }
    // create report for yesterday. Today's revenue info is still inaccurate
    const date = moment().add(-1, 'days').format('YYYY-MM-DD')
    const params = {
      accountId: 'pub-58**************',
      startDate: date,
      endDate: date,
      auth: oauth2Client,
      metric: 'EARNINGS',   // https://developers.google.com/adsense/management/metrics-dimensions
      dimension: 'AD_UNIT_NAME',
    }
    adsense.accounts.reports.generate(params, (errReport, resp) => {
      if (errReport) {
        callback(errReport)
      } else {
        callback(null, reportToString(resp))
      }
    })
  })
}

function reportToString(report) {
  const date = moment(report.endDate)
  let response = `AdMob Income for ${date.format('dddd MMM Do')}:`
  const numRows = report.totalMatchedRows
  const rows = report.rows
  const currency = report.headers.find(x => x.name === 'EARNINGS').currency
  for (let i = 0; i < numRows; i += 1) {
    // This depends on your naming convention of your Ad units
    const name = rows[i][0].split('_')[0]
    const earnings = rows[i][1]
    response += `\n${name}: ${earnings}${currency}`
  }
  // console.log(report)
  response += `\nTotal: ${report.totals[1]}${currency}`
  return response
}

I use a cronjob to access the script once a day which delivers the report as a notification through PushOver. The output then looks like this:

Google AdSense Report PushOver