Truv Quickstart Guide

The Quickstart covers the entire workflow from setting up Truv Bridge to retrieving data with the API.

Introduction

Welcome to Truv! This guide assists in quickly integrating Truv’s Income and Employment verification API into an application. Follow the steps below to get started.

📘

Tip

For no-code solutions, use the Truv Postman Collection to get started with Truv's API.

Prerequisites

Before you begin, ensure you have the following:

Sign up for Truv through the Dashboard and collect your API keys. Store these securely.

API Keys

API keys are located under the Development section of the Truv Dashboard.

KeyDescriptionExample
client_idPrivate identifier for the teamdd21ca23c12142b6a0970b1e0afeXXXX
access_keyPrivate key for each environmentsandbox-a57b3109f1f4a8b3f2ebbc1c526950f17954XXXX

🚧

Warning

Keep the Client ID and Access Key secure. They provide access to sensitive information.

Environments

Truv supports three environments:

  • Sandbox: For development with test credentials, unlimited requests, and free.
  • Development: For testing integration with live credentials, includes 50 free successful tasks.
  • Production: For your production environment, all successful tasks are billed.

📘

Tip

Contact [email protected] for assistance at any step.

Configuring the App

After obtaining the API keys, run the Quickstart from Truv's GitHub on a local machine. Truv's GitHub page has examples in various languages.

For mobile platforms, use the Quickstart apps for iOS and Android.

Step 1: Set Up Your Development Environment

Clone the Truv Quickstart repository to get started quickly.

# Clone the Quickstart repository
git clone https://github.com/truvhq/quickstart.git

# Open Quickstart
cd quickstart

# Create .env file 
make env

# Update the values in .env file by adding in your Client ID and Sandbox Access key. 
# API_CLIENT_ID=<Your Client ID here>
# API_SECRET=<Your Access key here>

# Also, in .env uncomment the line with API_PRODUCT_TYPE that you want to use
# API_PRODUCT_TYPE=employment
# API_PRODUCT_TYPE=income
# API_PRODUCT_TYPE=deposit_switch
# API_PRODUCT_TYPE=pll
# API_PRODUCT_TYPE=admin

# Run the make script for your coding language
make python_local
# Clone the Quickstart repository
git clone https://github.com/citadelid/quickstart.git

# Open Quickstart in your coding language
cd quickstart/python

# Copy the .env.example file to a new file in the same directory and name it .env. 
cp .env.example .env

# Update the values adding in the your Client ID and Sandbox Access key. 
#API_CLIENT_ID=<Your Client ID here>
#API_SECRET=<Your Access key here>
# Uncomment the line with API_PRODUCT_TYPE that you want to use
#API_PRODUCT_TYPE=employment

#Run the make script for your coding language
make python_docker
/*
Clone the Quickstart repository:
git clone https://github.com/truvhq/quickstart-ios.git

Open Quickstart:
cd quickstart-ios

Create constants file:
touch truv-quickstart/Constants.swift
*/
import Foundation

var TruvClientID = "<client_id>"
var TruvClientSecret = "<access_key>"
var TruvAPIUrl = "https://prod.truv.com/v1/"
var TruvProductType = "<employment or income>"

/*
Open the project in XCode and run the app
*/
/*
Clone the Quickstart repository:
git clone https://github.com/truvhq/quickstart-android.git

Open Quickstart:
cd quickstart-android

Create constants file:
touch local.properties
*/

truvClientId="<client_id>"
truvSecret="<access_key>"
truvApiUrl="https://prod.truv.com/v1/"
truvProductType="<employment or income>"

/*
Open the project in Android Studio and run the app
*/

Your app will run at http://localhost:5001. iOS and Android mobile apps launch in Xcode or Android Studio, respectively.

Visit localhost and log in with Sandbox credentials as indicated on Truv's Testing page

Step 2: Generate a Link Token

Most API requests interact with a Link, a Truv term for a connection to a payroll, insurance, tax, or financial provider. For example, a connection to a payroll provider of one employer is generally associated with one Link.

With the Quickstart configured, the next step is to create a Link to a provider in the Sandbox environment. Open the Quickstart app on localhost, click the Connect button, and select any employer. Use the Sandbox credentials to simulate a successful login.

Sandbox Credentials

Use the sandbox credentials below to simulate a successful login:

UsernamePasswordSSN
goodlogingoodpassword991-91-9991

MFA Use Case

UsernamePasswordSSNMFA code
goodloginmfa991-91-999112345

Once the credentials are entered and the next screen appears, a successful Link is created. After the successful connection, the next page within the Quickstart describes the steps to retrieve the requested data points. The following section will explain what happened and how the Quickstart works.

How It Works

The Truv workflow covers your application, Truv Bridge, your server, and the Truv API. The steps below explain the communication between each component.

  1. The application uses the bridge_token to make a request to your server.
  2. Your server makes a POST request with the user information to the Truv API.
  3. After the API processes the exchange, your application then prompts the user to connect payroll information in Truv Bridge.
  4. When successful, Truv Bridge returns the required credentials to your application.
  5. During this step, your server monitors webhook events from the Truv API.
  6. With the public_token, your application makes an exchange to your server and the Truv API for access.
  7. After your server collects the appropriate credentials, it can complete the request to get data from the Truv API.

Summary of Sequence Diagram

The chart below is an overview of the steps in the process.

Process Diagram

Requesting a Bridge Token

The bridge_token is the initial value required to authenticate the app with Truv Bridge. This is a temporary, single-use token for a user. When creating a new user, a request should be made to the "Create a bridge token" endpoint to obtain the bridge_token value.

📘

Note

The bridge_token is temporary and expires after 6 hours.

def create_user(self, **kwargs) -> dict:
    logging.info("TRUV: Requesting new user from https://prod.truv.com/v1/users/")
    payload = {
        "external_user_id": f"qs-{uuid4().hex}",
        "first_name": "John",
        "last_name": "Johnson",
        "email": "[email protected]",
        **kwargs,
    }
    return self.post("users/", json=payload)

def create_user_bridge_token(self, user_id: str) -> dict:
    logging.info(
        "TRUV: Requesting user bridge token from https://prod.truv.com/v1/users/{user_id}/tokens"
    )
    logging.info("TRUV: User ID - %s", user_id)

    payload = {
        "product_type": self.product_type,
        "tracking_info": "1338-0111-A",
    }

    if self.product_type in ["deposit_switch", "pll"]:
        payload["account"] = {
            "account_number": "16002600",
            "account_type": "checking",
            "routing_number": "12345678",
            "bank_name": "Example Bank",
        }

        if self.product_type == "pll":
            payload["account"].update(
                {
                    "deposit_type": "amount",
                    "deposit_value": "100",
                }
            )
    return self.post(f"users/{user_id}/tokens/", json=payload)
def create_user(self, **kwargs) -> dict:
    logging.info("TRUV: Requesting new user from https://prod.truv.com/v1/users/")
    payload = {
        "external_user_id": f"qs-{uuid4().hex}",
        "first_name": "John",
        "last_name": "Johnson",
        "email": "[email protected]",
        **kwargs,
    }
    return self.post("users/", json=payload)

def create_user_bridge_token(self, user_id: str) -> dict:
    logging.info(
      	"TRUV: Requesting user bridge token from https://prod.truv.com/v1/users/{user_id}/tokens"
    )
    logging.info("TRUV: User ID - %s", user_id)

    payload = {
        "product_type": self.product_type,
        "tracking_info": "1338-0111-A",
    }

    if self.product_type in ["deposit_switch", "pll"]:
        payload["account"] = {
            "account_number": "16002600",
            "account_type": "checking",
            "routing_number": "12345678",
            "bank_name": "Example Bank",
        }

        if self.product_type == "pll":
            payload["account"].update(
              {
                	"deposit_type": "amount",
                	"deposit_value": "100",
              }
            )
		return self.post(f"users/{user_id}/tokens/", json=payload)

func createUser() (string, error) {
	log.Println("TRUV: Requesting new user from https://prod.truv.com/v1/users/")
	uniqueNumber := time.Now().UnixNano() / (1 << 22)
	userRequest := UserRequest{
		ExternalUserId: fmt.Sprintf("qs-%d", uniqueNumber),
		FirstName:      "John",
		LastName:       "Johnson",
		Email:          "[email protected]",
	}
	userJson, _ := json.Marshal(userRequest)
	request, err := getRequest("users/", "POST", userJson)
	if err != nil {
		return "", err
	}
	client := &http.Client{}
	res, err := client.Do(request)
	if err != nil {
		return "", err
	}

	defer res.Body.Close()

	user := UserResponse{}
	err = json.NewDecoder(res.Body).Decode(&user)

	return user.UserId, nil
}


func createUserBridgeToken(userId string) (string, error) {
	log.Println("TRUV: Requesting user bridge token from https://prod.truv.com/v1/users/{user_id}/tokens")
	log.Printf("TRUV: User ID - %s\n", userId)
	productType := os.Getenv("API_PRODUCT_TYPE")
	bridgeTokenRequest := BridgeTokenRequest{
		ProductType:  productType,
		TrackingInfo: "1338-0111-A",
	}
	if productType == "pll" || productType == "deposit_switch" {
		account := AccountRequest{
			AccountNumber: "1600200",
			AccountType:   "checking",
			RoutingNumber: "123456789",
			BankName:      "TD Bank",
		}
		if productType == "pll" {
			account.DepositType = "amount"
			account.DepositValue = "1"
		}
		bridgeTokenRequest.Account = &account
	}
	bridgeJson, _ := json.Marshal(bridgeTokenRequest)
	request, err := getRequest(fmt.Sprintf("users/%s/tokens/", userId), "POST", bridgeJson)
	if err != nil {
		return "", err
	}
	client := &http.Client{}
	response, err := client.Do(request)
	if err != nil {
		return "", err
	}

	defer response.Body.Close()
	data, _ := ioutil.ReadAll(response.Body)
	return (string(data)), nil
}

Initializing Truv Bridge

Users authenticate their payroll accounts through Truv Bridge. After obtaining the bridge_token, this value is used to initialize the Bridge. Truv Bridge is a client-side module that handles the authentication process and is available as a drop-in for web clients, iOS, and Android platforms.

In the Quickstart app, the Bridge module is implemented on the web using a JavaScript integration triggered from client-side code. The front-end application runs TruvBridge.init to pass the bridge_token from the back-end, and assigns callback functions as required. Refer to the code sample below for more details.

const bridge = TruvBridge.init({
  bridgeToken: bridgeToken.bridge_token,
  onLoad: function() { console.log('Bridge loaded'); },
  onSuccess: function(public_token, meta) { console.log('Success:', public_token); },
  onEvent: function(event_type, payload) { console.log('Event:', event_type); },
  onClose: function() { console.log('Bridge closed'); }
});
window.bridge = bridge;

Submitting Credentials for Public Token

Truv Bridge provides a public_token value after a user submits their credentials through the onSuccess callback. The JavaScript code below demonstrates the public_token exchange between the client-side code and the server.

onSuccess: async function (token) {
  console.log('token: ', token);
  successClosing = true;
  const content = document.querySelector('.spinnerContainer');
  content.classList.remove('hidden');
  let verificationInfo;
  try {
    verificationInfo = await apiRequests.getVerificationInfoByToken(token);
  } catch(e) {
    console.error(e);
    content.classList.add('hidden');
    return;
  }
  content.classList.add('hidden');
  if (!verificationInfo.length) {
    return;
  }
},
onClose: function () {
  console.log('closed');
  if (successClosing !== true) {
    renderEmploymentHistory([{ company: { address: {} } }]);
  }
},

📘

Note

The public_token is temporary and expires after 6 hours.

Server Side Exchanges

The Quickstart application accesses the server to Retrieve a link token for the access_token and link_id. These two values are essential for identifying a Link and are required as arguments for Truv API endpoints. Similar to other important values, the access_token and link_id, along with the user_id should be stored in a secure and private location when making API requests.

The code samples below demonstrate the server actions.

def get_access_token(self, public_token: str) -> dict:
    logging.info(
        "TRUV: Exchanging a public_token for an access_token from https://prod.truv.com/v1/link-access-tokens"
    )
    logging.info("TRUV: Public Token - %s", public_token)

    return self.post(
        "link-access-tokens/",
        json={
            "public_token": public_token,
        },
    )
const getAccessToken = async (public_token) => {
  console.log('TRUV: Exchanging a public_token for an access_token from https://prod.truv.com/v1/link-access-tokens');
  console.log(`TRUV: Public Token - ${public_token}`);
  const body = JSON.stringify({
    public_token: public_token,
  });
  const responseBody = await sendRequest('link-access-tokens/', { body });
  return responseBody;
};
func getAccessToken(public_token string) (string, error) {
	log.Println("TRUV: Exchanging a public_token for an access_token from https://prod.truv.com/v1/link-access-tokens")
	log.Printf("TRUV: Public Token - %s\n", public_token)
	publicToken := PublicTokenRequest{PublicToken: public_token}
	jsonPublicToken, _ := json.Marshal(publicToken)
	accessToken := AccessTokenResponse{}
	request, err := getRequest("link-access-tokens/", "POST", jsonPublicToken)
	if err != nil {
		return "", err
	}
	client := &http.Client{}
	res, err := client.Do(request)
	if err != nil {
		return "", err
	}

	defer res.Body.Close()
	err = json.NewDecoder(res.Body).Decode(&accessToken)
	if err != nil {
		return "", err
	}
	return accessToken.AccessToken, nil
}

Making API Requests

Now that the Link flow and token exchange process have been covered, the next step is to use the link_id to retrieve information about the consumer. As an example, consider the [/links/{link_id}/income/report](https://docs.truv.com/reference/income_verification) API call, which retrieves basic information about the consumer’s income and employment. This call is straightforward and uses the link_id as a single argument.

def get_income_info_by_link_id(self, link_id: str) -> dict:
    logging.info(
      	"TRUV: Requesting income report data from https://prod.truv.com/v1/links/:link_id/income/report"
    )
    logging.info("TRUV: Link ID - %s", link_id)

    return self.get(f"links/{link_id}/income/report")
const getIncomeInfoByLinkId = async (link_id) => {
  console.log(
    'TRUV: Requesting income verification data from https://prod.truv.com/v1/links/:link_id/income/report',
  );
  console.log(`TRUV: Link ID - ${link_id}`);
  return await sendRequest('links/${link_id}/income/report', { method: "GET" });
};
func getIncomeInfoByLinkId(link_id string) (string, error) {
  log.Println("TRUV: Requesting income verification data from https://prod.truv.com/v1/links/:link_id/income/report")
	log.Printf("TRUV: Link ID - %s\n", link_id)
  request, err := getRequest(fmt.Sprintf("links/%s/income/report", link_id), "GET", nil)
	if err != nil {
		return "", err
	}
  
	client := &http.Client{}
	res, err := client.Do(request)
	if err != nil {
		return "", err
	}

	defer res.Body.Close()
	data, _ := ioutil.ReadAll(res.Body)
	return string(data), nil
}

Example response data

This JSON object is a sample payload for the income and employment report request.

{
  "id": "24d7e80942ce4ad58a93f70ce4115f5c",
  "status": "new",
  "completed_at": "2021-04-06 11:30:00+00:00",
  "access_token": "48427a36d43c4d5aa6324bc06c692456",
  "tracking_info": "user123456",
  "refresh_status": "new",
  "employments": [
    {
      "id": "24d7e80942ce4ad58a93f70ce4115f5c",
      "job_title": "PR associate",
      "job_type": "F",
      "start_date": "2018-01-01",
      "end_date": "2019-08-24",
      "external_last_updated": "2019-08-24",
      "original_hire_date": "2017-06-21",
      "is_active": false,
      "dates_from_statements": false,
      "derived_fields": [
        "is_active"
      ],
      "missing_data_fields": [
        "w2s"
      ],
      "profile": {
        "first_name": "John",
        "last_name": "Doe",
        "middle_initials": "K",
        "ssn": "123456789",
        "email": "[email protected]",
        "date_of_birth": "1992-03-03",
        "home_address": {
          "street": "1 Morgan Ave",
          "city": "Los Angeles",
          "state": "CA",
          "zip": "90210"
        }
      },
      "company": {
        "name": "Facebook Demo",
        "address": {
          "street": "1 Hacker Way",
          "city": "Menlo Park",
          "state": "CA",
          "zip": "94025"
        },
        "phone": "6503087300"
      },
      "income": "70000.00",
      "income_unit": "YEARLY",
      "pay_frequency": "M",
      "manager_name": "Jenny McDouglas",
      "statements": [
        {
          "id": "24d7e80942ce4ad58a93f70ce4115f5c",
          "pay_date": "2018-05-15",
          "net_pay": "11500.32",
          "net_pay_ytd": "31980.64",
          "gross_pay": "13900.11",
          "gross_pay_ytd": "49200.00",
          "bonus": "100.00",
          "commission": "12000.00",
          "hours": "40.00",
          "basis_of_pay": "S",
          "period_start": "2018-05-01",
          "period_end": "2018-05-15",
          "regular": "1695.11",
          "regular_ytd": "23000.00",
          "bonus_ytd": "1000.00",
          "commission_ytd": "24000.00",
          "overtime": "45.00",
          "overtime_ytd": "500.00",
          "other_pay": "60.00",
          "other_pay_ytd": "700.00",
          "earnings": [
            {}
          ],
          "earnings_ytd": [
            {}
          ],
          "deductions": [
            {}
          ],
          "deductions_ytd": [
            {}
          ],
          "md5sum": "03639d6a6624f69a54a88ea90bd25e9d",
          "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/paystub_sample.pdf"
        }
      ],
      "annual_income_summary": [
        {
          "id": "24d7e80942ce4ad58a93f70ce4115f5c",
          "year": 2018,
          "regular": "23000.00",
          "bonus": "1000.00",
          "commission": "24000.00",
          "overtime": "500.00",
          "other_pay": "700.00",
          "net_pay": "31980.64",
          "gross_pay": "49200.00"
        }
      ],
      "bank_accounts": [
        {
          "account_number": "1234567890",
          "routing_number": "123456789",
          "account_name": "My Bank",
          "account_type": "C",
          "deposit_type": "A",
          "deposit_value": "200.00",
          "bank_name": "TD Bank"
        }
      ],
      "annual_salary": "70000.00",
      "hourly_salary": "36.40",
      "w2s": [
        {
          "file": "https://citadelid-resources.s3-us-west-2.amazonaws.com/W2_sample.pdf",
          "md5sum": "f65e30c39124ad707ac4b3aeaee923a7",
          "year": 2020
        }
      ]
    }
  ],
  "provider": "adp"
}

 

Next Steps

Congratulations! You've successfully integrated Truv's Income and Employment API. For more advanced features and detailed API documentation, visit our API Reference.

Product guides

Learn more about our products through the guides below.

Mobile integrations

For mobile integrations of Truv, learn more about getting started through the Bridge SDK .

Demo applications

Our sample apps page has additional examples, including real-world use cases.

Additional Resources

For any issues or support, contact us at [email protected].


What’s Next

Learn more about other products, such as verifying income and employment and other guides and check out our Postman Collection as well.