Direct tracking code injection to extension

Before we get started, it's important to understand what a tracking code is and why it is used.

When a user installs your extension, we need a way to identify the user to affiliate when they activate cash back through your extension, in order to attribute them properly when the sale completes its lifecycle. This is done at Wildfire by attaching a tc query parameter at the end of your affiliate url. Once the sale completes it's lifecycle, you can retrieve the value within the tc parameter. By going in to the admin tool, and posting the data to your callback url.

Example of a vanity url with a tc parameter:


Example of an offline vanity url with a tc parameter:


Many extensions use different models, whether it's an OAuth flow, or logging in directly through the extension. They all serve the purpose of having the user identify themselves, and saving that identifier in the extension's storage. This way is different because it doesn't require us making an http request within the extension after authenticating, and not storing any secrets in the extension when not needed.

Final things to note before we get started:

  • The identifiers in the example urls are uuids, your platform may have different ways of identifying a user, the premise is the same.
  • This method is only known to work in Chrome and Firefox. Safari does not allow the onMessageExternalmethod to be exposed for extensions.
  • During this tutorial we will assume you have some knowledge of how chrome extensions work. Please check the documentation for basic information on getting a chrome extension setup and how they work.
  • We will be using the v2 manifest version in the examples.
  • The example code will contain a directory depicting an example web page that is ran locally on your computer. You can run a web server from VSCode using the Live Server extension. You can access the example code at the bottom of this document
  • In the example extension we depict that the user is logged in based off whether or not the tracking code is stored within the extension. This behavior is common amongst our cash back extensions, primarily because we don't want the user activating cash back if we have no way of identifying who the user is.

With that out of the way let's get started. Take a look at the directory structure of the example code:

The extension directory contains the extension code, the website directory contains the code for your website. Your structure may look different.

Update your manifest.json with the following:

  1. Specify your web page as an externally_connectable web page. This enables the extension to communicate with tabs that have urls that match the matches property.
  2. Add a key in your manifest json to create a permanent extension ID. This ID will be used to communicate from your web page to your extension. For information on how to generate your own key see here. The extension ID in our example is aokkbecooohkgppimfffkongjlpihbol.
"manifest_version": 2,
"name": "Example",
"description": "Example",
"version": "1.0.0",
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5yYHytHDwMWWn3pVWriKJdycAYXScUNWpQqxatgbz8+835xfFBLFjDjjzqYO2rautzCUhp3CxunzJ7Ljm2WIvfsNcKTau/u23MfGf92gk0XZnj+DE0fHBUZDwaA649jqzrB5gm2V49SgROJAyy6KtwmVMj13A1+kOVrm3z1U33yN6NUYHLxtBT6BjHbXxmoE+LDT/owe2Tl5eAuvC37mUYpgJG9/DlGwW2UCTeYfmQ9gl/HymQtofWtl8oFNjR0cyrPoJNL+9bnFy2yeXbz98kuiM8g6iBM/ZnwZfYybf8z+iRpQMVXA7iIcu0qn2pcTAv4yJ6AVMGcNMY0HaPN7iQIDAQAB",
"background": {
"scripts": ["background.js"]

"content_scripts": [
"js": ["content.js"],
"matches": ["<all_urls>"]
"browser_action": {
"default_popup": "popup.html",
"default_icon": {
"16": "images/toolbar-icon-16.png",
"19": "images/toolbar-icon-19.png",
"32": "images/toolbar-icon-32.png",
"38": "images/toolbar-icon-38.png"
"externally_connectable": {
"matches": [

The "https://**" value is there as a placeholder for you to enter you own url. The [<*>](<*>) is there for testing. We will be running our webpage through live server via vscode, you may be using something like web pack dev server or another local server. If you are using something else, make sure to update the port number in your manifest.json.

We can now move to the extension code. This is our background.js file.

let trackingCode = null;

(message, sender, sendResponse) => {
switch (message.status) {
// The initial ping will let the web page know that we can communicate with the extension
case "PING":
console.log("Received ping from", sender.origin);
const { code } = message.payload;
console.log("Received: ", code);
trackingCode = code;

// Listener added to communicate with the popup/content script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
switch (message.status) {
case "IS_LOGGED_IN":

The onMessageExternal listener dispatches an event when an outside entity communicates with the extension. We have the PING message to send back true to let the webpage know that we are able to communicate. The SET_TRACKING_CODE message is there for us to save the tracking code within the extension. In our example we are saving it in memory, but you will more than likely saving it to browser storage.

The onMessage listener dispatches an event when the content script or the popup script sends a message to background script. The IS_LOGGED_IN message is there to validate whether or not the extension has a tracking code in memory.

This is our popup.js file.

chrome.runtime.sendMessage({ status: "IS_LOGGED_IN" }, (isLoggedIn) => {
const button = document.querySelector("button");
button.textContent = isLoggedIn ? "Logged In" : "Not Logged In"; = isLoggedIn ? "green" : "red";

When the popup is rendered, we check and see if the tracking code has been stored in the background. The idea behind this behavior is that we don't want our user using our extension without first identifying themselves through our webpage.

Let's move to our webpage and fire it up using Live Server. This file is website/index.html.

<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="style.css" />
<h1>Send Code</h1>
<input type="text" disabled value="ac7222b1-bf17-461e-9f52-c53d4d58a1c4"/>
<button disabled>Submit</button>
<script src="main.js "></script>


If your button and input fields are greyed out, then you did not set your key properly. You can change the extensionID on line 8 of the website/main.js file

Here is said file:

* Your extension ID to specify your extension when sending the message.
* For information on how to generate your own extension ID see here: <>
* Once you have your key generated, you can add the key to your manifest.json under the "key" property.
* If you have not set the ID properly, or made this webpage externally connectable you will get the following error:
* `Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.`
const extensionID = "aokkbecooohkgppimfffkongjlpihbol";

* Only here to make sure you are on the correct browser.
const isChromeApiAccessible =
typeof !== "undefined" &&
typeof !== "undefined" &&
typeof !== "undefined";

const submitButton = document.querySelector("button");
const inputElement = document.querySelector("input");
* Wrapped in an IFFE to stop execution when needed
(() => {
try {
if (!isChromeApiAccessible) {
return console.log("Stopping execution, chrome api is not accessible.");
{ status: "PING" },
(isConnected) => {
if (!isConnected) {
return console.log(
"Stopping execution, failed to connect to the extension"
console.log("Successfully pinged extension, ready to accept code");
submitButton.disabled = false;
inputElement.disabled = false;
submitButton.onclick = () => {
const code = inputElement.value;
{ status: "SET_TRACKING_CODE", payload: { code } },
(wasSuccessful) => {
if (!wasSuccessful) {
return console.log("Failed to send code");
console.log("Successfully sent code");
} catch (err) {
console.error("Failed to communicate with the extension", err);

This file does the following

  1. On initial load it checks to see if the chrome api is accessible. If your chrome api is not accessible you may be on the wrong browser.
  2. Selects the Input and Button elements for use later
  3. Fires off an immediately invoked function. We wrap in an IFFE here to be able to stop the execution of the rest of the code
  4. We send a ping to the extension to make sure we can communicate with it first. If we can't then the input and button remain disabled. If we are able to communicate with the extension, then we enable the input and button. Then we add an event listener on the button for you to send the tracking code to the extension side.

You can incorporate this logic into your own web pages. When to send the message is up to you, but it is probably best to do so when the user first logs in through your web page or first lands on your page if returning in a logged in state. This saves us the hassle of creating an OAuth login flow and saves you the danger of exposing secrets in your extension code.