Toggle document index

Overview

The source tracking APIs (/trackers) defined by TAS are for a candidate source tracking system based on fractional attribution.

With fractional attribution, an end result (such as someone applying for a job) can be mapped back to a whole chain of user interactions such as social sharing, emailing links, etc. This provides more detailed sourcing information than just tracking the last click that led the candidate to apply for the job.

There are two types of trackers:

  • Session trackers, representing a unique candidate visit to a web site. Any candidate-facing web site (like a careers site, microsite, etc.) that is tracker aware should create session trackers when candidates use the site.
  • Campaign trackers, representing a known starting point, such as an automated email alert or a job board posting.

Trackers are organized into trees. The path from any single leaf to the trunk of the tree represents the chain of sourcing events that led to a candidate outcome (like applying for a job).

Session trackers

Any candidate-facing web site (like a careers site, microsite, etc.) that is tracker aware should maintain session trackers when candidates use the site. This means:

  • Creating a session tracker for each new unique visit by a candidate
  • Connecting that tracker back to any tracker passed in as a url parameter

For example, when a candidate first follows a link like this to a page in a job board/careers site:


https://acme.supersite.com/marketing.html?tracker=1234

..then the job board should create a session tracker and attach it to the tracker 1234. The resulting tracker tree will be like this:


1234
+-- 1287 (session tracker)

After this, the job board should attach the session tracker to all links that it emits, even for internal navigation around the site itself. For example, the candidate above might search for jobs at the site, and then click through to a specific job using a link like this:


https://acme.supersite.com/job=23&tracker=1287

Now the candidate might share the job via email using the browser's native share button. They email the job to a friend. The friend clicks through to the job. Because the friend represents a new unique visit, a new session tracker is created by the job board, linking back to tracker 1287. Now the tracker tree looks like this:


1234
+-- 1287 (session tracker)
    +-- 1344 (session tracker)

If the friend now goes on to apply for the job, then the tracker will be passed into the applicant tracking system (ATS), giving it a deep insight into how the candidate came to hear about this job.

Sample logic for maintaining session trackers

The following example (in Java) shows one approach to maintaining session trackers.

This algorithm:

  • handles the case where the app does not add the tracker parameter to its own internal links (though ideally apps should).
  • optimises to avoid some unnecessary (not not harmful) calls to POST /trackers, but only remembers the most recent incoming tracker. This means, for example, that if the candidate has bookmarked a couple of links to the site, each with different values for the tracker url parameter, and then they pound those links repeatedly and alternately, then the app will repeatedly call POST /trackers (though this won't cause an explosion of trackers as existing ones will just be re-used).

// get details of tracker (if any) passed in as a url parameter 
Integer trackerParam = httpRequest.getParms("tracker");

// get details of existing session tracker (if any) and its parent (if any) 
HttpSession session = httpRequest.getSession(true);
Integer sessionTracker = session.getAttribute("sessionTracker");
Integer parentTracker = session.getAttribute("parentTracker");

// we test if session tracker missing or mismatched with an incoming tracker param - if so, potentially need to create a new one.
// there are two ways to do this, based on whether the app emits the tracker parameter on all its internal links (which might still
// end up getting shared - so it should) or not.
//
// conditional when the app does emit tracker on all links
if (sessionTracker == null || ! Objects.equals(parentTracker, trackerParam)) {
// conditional when the app does not emit tracker on all links
// if (sessionTracker == null || (trackerParam != null && (parentTracker == null || parentTracker != trackerParam)) {
  // prepare a request body for use with either create tracker API call
  String request =
  {
    "kind": "sessionCreation",
    "ip": httpRequest.getIP(),
    "Accept-Language": httpRequest..,
    "userAgent": httpRequest..
  }
  if (trackerParam == null) {
    // create a session tracker with no parent
    POST /trackers/root
      Request: <- request
      Location: -> trackerID
    session.setAttribute("sessionTracker", trackerID);
    session.removeAttribute("parentTracker");
  } else {
    // create a session tracker chained to the incoming tracker
    POST /trackers/byID/9022/trackers
      Request: <- request
      Location: -> trackerID
    session.setAttribute("sessionTracker", trackerID);
    session.setAttribute("parentTracker", parentTracker);
  }
}    

// from here on, we will add (or replace) the sessionTracker parm on any trackerized links we emit, e.g. "?tracker=9023" 
// one tiny problem is that the current URL (i.e. this entry page) has the original (if any) tracker param on it.
// That creates a problem in that if this page contained a non-TAS social sharing plugin (one that just referenced the current page rather
// than having tracker param passed into it), this new session tracker would not be seen by it. The solution is
// to try and force navigation to any new page as quickly as possible - the new page's links will contain the proper tracker param.

Web site analytics considerations for session trackers

Attaching the tracker parameter to all URLs that your site emits can affect your web site analytics. You may want to instruct analytics code to ignore the tracker parameter - for Google, see https://support.google.com/analytics/answer/1010249?hl=en.

Web site search engine optimization (SEO) considerations for session trackers

Attaching the tracker parameter to all URLs that your site emits can affect your web site's SEO. You may want to instruct search engines to ignore the tracker parameter - for Google, use Webmaster tools.

Scenarios

Scenario 1 - a legacy ATS uses tracker to pass through legacy source tracking information

Candidate arrives at the job details page on the legacy ATS, via a legacy link

The legacy ATS maintains candidateSource and device values in its session for incoming candidates.

A candidate arrives at:


acme/legacyats.com/jobs/Creative-Director-GR4022?candidateSource=facebook

The legacy ATS knows it will be handing off the apply process to some other microservices.

This means the apply process is out of the legacy ATS's control. But eventually the legacy ATS will regain control, when the apply app calls POST /candidates to push the candidate and job application in.

Therefore the legacy ATS needs to pack its legacy detail into a tracker, and pass that tracker in to the apply app, relying on the apply app eventually passing that tracker back in again.

ATS creates a session tracker

Like any good candidate-facing app, the legacy ATS maintains a session tracker, as per the logic above.

In this case the ATS also embeds the legacy sourcing data in the userArea field:


POST /trackers
Response: Location: 16044

Not shown, but legacy ATS passes this tracker ID in as a URL parameter to the apply app in its job details page.

Candidate clicks to apply at the simpleapply microservice

ATS renders job details page. In it, a deep link like this leads to simpleapply:


acme.simpleapply.com/jobs/16670?tracker=16044

Simpleapply is tracker-aware and so creates a session tracker for the candidate (16045) and chains it to the existing tracker.

Once the candidate has completed the application, simpleapply passes that tracker (16045) back into the ATS via the POST /candidates API call.

The ATS produces the API POST /candidates

The legacy ATS uses the following logic to extract its legacy source details from the incoming tracker:

The ATS searches up the tracker chain until it reaches a tracker with a "legacyATS" field in the userArea

if found
	use that for source details
		
else
	the ATS attempts to manufacture its own source details from the immediately incoming tracker. It can only do a very limited job.
	1) the ATS searches up the tracker chain until it reaches any session tracker
		device = derive from user-agent as per existing ATS behaviour
	2) The legacy ATS starts again and searches up the tracker chain until it reaches either:
		a session tracker with a "sessionCreation.referer" value that matches one of the rows in its own table of media
		a campaign tracker with a "campaign.referrer" value that matches one of the rows in its own table of media
		a campaign tracker with a "campaign.referrer" value that matches one of the rows in its own table of media
		if found
			candidateSource = the media 

A sample chain is shown here though it doesn't yet show the data discussed above.


GET /trackers/16045/chain
Response:

Scenario 2 - a job is advertised on a job board

A root campaign tracker is created:


POST /trackers/root
Response: Location: 9022

Scenario #2a - a job is advertised in a print publication. Someone searches by code at the careers site and then applies

Scenario #3 - a job is emailed to a candidate who shares it with another candidate

An automated alert for a new job is emailed to a candidate

A root tracker is obtained by the mailout app:


POST /trackers/root
Response: Location: 9022

The outgoing email has a link like this:


<a href="/jobs/1004532?tracker=9022>Creative director</a>

That candidate visits the careers site app to view the job

The careers site is tracker-aware so creates a new session tracker.

Not interested themselves, candidates clicks to share the job details page on Facebook

The careers site may force navigation to a new page, so that any sharing plugins which rely on current URL have access to the up to date session tracker (see comments above).

One way is to hide the sharing plugins when the job details page is being generated for a new session, and instead show a button "Enable sharing" that just navigates to the current page - after navigation the page will not be being generated for a new session, so the sharing plugins will show as they should.

The candidate uses Facebook share button which shares the current URL

A friend sees their Facebook post and clicks through to view the job

The friend clicks through to the target page at /jobs/1004532?tracker=9023.

The same logic as above fires, and the careersite app creates a new session tracker (9024), and attaches it to all links it emits.

The friend is not interested in that job, but while browsing the careers site they find another job they are interested in

This job is at /jobs/26?tracker=9024

Leaving for work, they do not have time to apply, so they share the link with themselves via email

they use sharing via email to send the link to themselves

On their way to work on the bus, they open the email and return to the careers site on their mobile phone

they click through to the target page at /jobs/26?tracker=9024

the same logic as above fires, and another new session tracker (9025) is created and used for all links.

They complete an application for the job

  • the tenant has set up simpleapply to handle job applications
  • the candidate goes to acme.simpleapply.com/jobs/26?tracker=9025
  • simpleapply is tracker aware as well, so another new session tracker is created (9026)
  • simpleapply passes the application to the ATS, along with tracker=9026

Scenario #4 - a job is shared via employee referral to a candidate who applies

The employee referral system (refly) has a list of open jobs along with their apply links (via GET /jobs API)

refly creates a new campaign tracker that will hold its own specific details, in this case that referral has been made by Fred.


POST /trackers
Response: Location: 9041

refly now facilitates getting that link in front of candidate(s):


<a href="/jobs/1004532?tracker=9041>

The candidate clicks on the link and arrives at the careers site

As previously, a new session tracker (9046) is created by the careers site and chained to the existing tracker.


POST /trackers/byID/9041/trackers
Request
.. create session tracker
Response:
Location: 9046

The candidate applies at simpleapply

The tracker is passed into POST /candidates with the job application.

The ATS producing POST /candidates now notifies the agent insight app that some change has happened to an application (i.e. creation in this case):


POST /applications/66022/deltaPings

agent insight gets the application's tracker


GET /applications/byID/66022/tracker
Response:
Location: 9046

agent insight now asks for every installed app that had some part in sourcing this application - i.e. that created a tracker upstream from 9046


GET /trackers/byID/9046/chain
Response:

agent insight now uses the list of sourcing apps to alert each of the tracker owners that an application they have sourced has changed. Theoretically a sourcing app may appear multiple times in the chain:


POST /applications/bySourcingApp/acme/supersoft/refly/66022/deltaPings
{
	"operation": "insert",
	"trackers": [ 9041 ]
}

refly asks for specific information about the application. Agent insight internally re-creates the chain of trackers and verifies that refly is allowed to ask for this application before responding


GET /applications/bySourcingApp/acme/supersoft/refly/66022/sourcerDetails
Response:
{
	"sourcerBucket": "interview",
	"trackers": [ 9041 ]
}

refly now has all the information it needs about the application that it had a part in sourcing