GuidesAPI ReferenceChangelog
Log In

Technical Implementation

This guide explains how to access Verify and handle updates. For an overview of concepts in the Pinwheel system that are referenced in this doc, review the Platform Overview.

Pinwheel Verify includes reports and core data to meet your needs. The biggest difference between the types of data is that reports are associated with an end_user_id and individual data is associated with a payroll_account_id. Reports aggregate data across payroll accounts.

[Beta] Verification Reports

Step 1: Subscribe to webhook events

Pinwheel sends webhook events to alert you when up-to-date Verify data is available or Monitoring has detected a change to a user's information.

Register a webhook endpoint using the POST /v1/webhooks with the data you're interested in receiving updates for (e.g. verification_reports.refreshed). See Webhooks for additional configuration details and instructions on authenticating webhook events. See Pinwheel Verify Webhook Events for details on webhook event payloads.

POST /v1/webhooks
Host: api.getpinwheel.com
Content-Type: application/json
x-api-secret: YOUR-API-SECRET
{
  "url": "https://your-domain.com/webhook_endpoint",
  "status": "ACTIVE",
  "enabled_events": [
    "verification_reports.refreshed"
  ]
}

Step 2: Create a Link token

Create a Link token specifying the jobs that your application requires using required_jobs.

POST /v1/link_tokens
Host: api.getpinwheel.com
Content-Type: application/json
x-api-secret: YOUR-API-SECRET
{
  "org_name": "YOUR APP NAME",
  "end_user_id": "my_user_12345",
  "required_jobs": ["employment", "income"]
}

Note: The end_user_id is your internal reference to the end user. See User Model for more information.

The response includes a short-lived token that expires after 15 minutes and a unique id. Persist the id in your database.

{
  "data": {
    "id": "97f420ff-5d0a-46ee-9cfc-6f17d5d31256",
    "expires": "2021-01-09T02:52:26+00:00",
    "mode": "production",
    "token": "eyJ0eXAiOiJKV1QiLCJhbGci...cyldX8fILelb6A0XKmdWsXZHMH4W1o",
  }
}

Step 3: Initialize Link

Using the Link token that was created, open the Link modal in your client application. In addition to passing in the token, you can optionally pass in several callback handlers.

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.getpinwheel.com/pinwheel-v2.3.js"></script>
    <script>
      window.addEventListener("load", () => {
        Pinwheel.open({
          linkToken: "INSERT LINK TOKEN",
        });
      });
    </script>
  </head>
  <body></body>
</html>

Calling Pinwheel.open() renders the Link modal in full screen mode.

Step 4: Respond to webhook events

The account.added webhook event is published after a user logs in to their payroll account. Using the link_token_id you persisted earlier, you can associate the account with the user who logged in with Link.

{
  "event": "account.added",
  "event_id": "5a141122-4235-4fa1-bd76-0628573880b0",
  "payload": {
    "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d",
    "end_user_id": "my_user_12345",
    "link_token_id": "97f420ff-5d0a-46ee-9cfc-6f17d5d31256",
    "platform_id": "fce3eee0-285b-496f-9b36-30e976194736",
    "created_at": "2021-01-12T02:36:01.287148+00:00",
    "connected": true
  }
}

Subsequent webhook events are published when data for a report has been updated.

{
  "event": "verification_reports.refreshed",
  "event_id": "5a141122-4235-4fa1-bd76-0628573880b0",
  "payload": {
    "end_user_id": "my_user_12345",
    "availability": {
      "voe": {"status": "available", "updated_at": "2021-01-12T02:36:01.287148+00:00", "refreshed_at": "2021-01-12T02:36:01.287148+00:00"},
      "voie": {"status": "available", "updated_at": "2021-01-12T02:36:01.287148+00:00", "refreshed_at": "2021-01-12T02:36:01.287148+00:00"}
    }
  }
}

Step 5: Retrieving data

To retrieve verification report data, you must use the end_user_id from the webhook event that you received and the endpoint associated with the data you're retrieving.

An example for a verification of employment report is below.

Request

GET /v1/end_users/my_user_12345/verification_reports/voe
Host: api.getpinwheel.com
Content-Type: application/json
x-api-secret: YOUR-API-SECRET

Response

See the full Verification Reports API reference here.

{
  "data": {
    "document": {
      "download_url": "download_url",
      "download_url_expiration": "2024-01-06T17:47:52.782334+00:00",
      "id": "15bbb566-fb4e-4835-9967-b308c164269e"
    },
    "employee": {
      "address": {
        "city": "Washington",
        "country": "US",
        "line1": "1234 Home Road",
        "line2": null,
        "postal_code": "20017",
        "raw": "1234 Home Road, Washington, DC, 20017, USA",
        "state": "DC"
      },
      "date_of_birth": "1987-02-01",
      "last_four_ssn": "0090",
      "name": {
        "first_name": "Alicia",
        "full_name": "Alicia Green",
        "last_name": "Green",
        "middle_name": ""
      },
      "phone_numbers": [
        {
          "type": "mobile",
          "value": "+14155552671"
        }
      ]
    },
    "employments": [
      {
        "employer_address": {
          "city": "Washington",
          "country": "US",
          "line1": "1234 Main St",
          "line2": "Suite 3",
          "postal_code": "20036",
          "raw": "1234 Main St, Suite 3, Washington, DC, 20036, USA",
          "state": "DC"
        },
        "employer_name": "Acme Corporation",
        "employment_duration_months": 23,
        "employment_status": "employed",
        "employment_type": "full time",
        "most_recent_pay_date": "2021-01-05",
        "start_date": "2020-06-01",
        "termination_date": null,
        "title": "Engineer"
      }
    ],
    "id": "aea8cc55-93fa-452d-8eac-b83c80b19a41",
    "refreshed_at": "2021-01-06T15:59:13.530178+00:00",
    "report_type": "voe",
    "updated_at": "2021-01-06T15:59:13.530178+00:00"
  }
}

Step 6 (Optional): Performing an On Demand Update

There are times when a user's account loses connection or you need the latest information for a user. We recognize that asking the user to log in multiple times introduces friction, so we've added the ability to re-access the payroll account without needing the user to re-enter credentials.

Enabling this functionality is similar to the way standard Link tokens are created. When creating the Link token, pass in the account_id parameter from a prior account.added webhook event or from the login client side event in Link. The account_id can be preserved and passed in any time you have it for a user.

POST /v1/link_tokens
Host: api.getpinwheel.com
Content-Type: application/json
X-API-SECRET: YOUR-API-SECRET
{
  "org_name": "YOUR APP NAME",
  "end_user_id": "my_user_12345",
  "required_jobs": ["employment", "income"],
  "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d"
}

It is not necessary to pass end_user_id when creating Link tokens for On Demand Updates, as the information is persisted from the initial connection.

Once you've created the Link token, follow Step 3 to initialize Link. When the user is presented with the Pinwheel Link flow, they will be directed to the payroll platform associated with their account, bypassing login if possible.

Core Data

Step 1: Subscribe to webhook events

Pinwheel sends webhook events to alert you when up-to-date Verify data is available or Monitoring has detected a change to a user's information.

Register a webhook endpoint using the POST /v1/webhooks with the data you're interested in receiving updates for (e.g. employment.added or income.added). See Webhooks for additional configuration details and instructions on authenticating webhook events.

POST /v1/webhooks
Host: api.getpinwheel.com
Content-Type: application/json
x-api-secret: YOUR-API-SECRET
{
  "url": "https://your-domain.com/webhook_endpoint",
  "status": "ACTIVE",
  "enabled_events": [
    "employment.added",
    "income.added",
    "paystubs.added"
  ]
}

Step 2: Create a Link token

Create a Link token specifying the jobs that your application requires using required_jobs.

POST /v1/link_tokens
Host: api.getpinwheel.com
Content-Type: application/json
x-api-secret: YOUR-API-SECRET
{
  "org_name": "YOUR APP NAME",
  "end_user_id": "my_user_12345",
  "required_jobs": ["employment", "income", "paystubs"]
}

Note: The end_user_id is your internal reference to the end user. See User Model for more information.

The response includes a short-lived token that expires after 15 minutes and a unique id. Persist the id in your database.

{
  "data": {
    "id": "97f420ff-5d0a-46ee-9cfc-6f17d5d31256",
    "expires": "2021-01-09T02:52:26+00:00",
    "mode": "production",
    "token": "eyJ0eXAiOiJKV1QiLCJhbGci...cyldX8fILelb6A0XKmdWsXZHMH4W1o",
  }
}

It is not necessary to pass end_user_id when creating Link tokens for On Demand Updates, as the information is persisted from the initial connection.

Once you've created the Link token, follow Step 3 to initialize Link. When the user is presented with the Pinwheel Link flow, they will be directed to the payroll platform associated with their account, bypassing login if possible.

Step 3: Initialize Link

Using the Link token that was created, open the Link modal in your client application. In addition to passing in the token, you can optionally pass in several callback handlers.

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.getpinwheel.com/pinwheel-v2.3.js"></script>
    <script>
      window.addEventListener("load", () => {
        Pinwheel.open({
          linkToken: "INSERT LINK TOKEN",
        });
      });
    </script>
  </head>
  <body></body>
</html>

Calling Pinwheel.open() renders the Link modal in full screen mode.

Step 4: Respond to webhook events

The account.added webhook event is published after a user logs in to their payroll account. Using the link_token_id you persisted earlier, you can associate the account with the user who logged in with Link.

{
  "event": "account.added",
  "event_id": "5a141122-4235-4fa1-bd76-0628573880b0",
  "payload": {
    "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d",
    "end_user_id": "my_user_12345",
    "link_token_id": "97f420ff-5d0a-46ee-9cfc-6f17d5d31256",
    "platform_id": "fce3eee0-285b-496f-9b36-30e976194736",
    "created_at": "2021-01-12T02:36:01.287148+00:00",
    "connected": true
  }
}

Subsequent webhook events are published when data for a job is available. For example, the employment.added event is sent once employment data is ready.

{
  "event": "employment.added",
  "event_id": "5a141122-4235-4fa1-bd76-0628573880b0",
  "payload": {
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d",
    "end_user_id": "my_user_12345",
    "link_token_id": "7c4ac4be-4a0e-4468-ab26-c42b249b233b",
    "name": "employment",
    "timestamp": "2021-01-21T20:16:28+00:00",
    "outcome": "success",
    "error_code": null
  }
}
{
  "event": "paystubs.added",
  "event_id": "5a141122-4235-4fa1-bd76-0628573880b0",
  "payload": {
    "id": "24fe697f-a893-42a5-adca-a727f11ad792",
    "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d",
    "end_user_id": "my_user_12345",
    "link_token_id": "7c4ac4be-4a0e-4468-ab26-c42b249b233b",
    "name": "paystubs",
    "timestamp": "2021-01-21T20:16:28+00:00",
    "outcome": "success",
    "error_code": null,
    "added": [{"id": "dbff9830-55ce-4aa5-82f1-e04f196da041"}],
    "deleted": [{"id": "57043868-6fb3-47e8-a2b7-c993bd023f25"}],
    "params": {
      "from_pay_date": "2020-10-01",
      "to_pay_date": "2020-12-31",
      "count": 6,
      "sync_status": "in_progress",
    }
  }
}
{
  "event": "income.added",
  "event_id": "5a141122-4235-4fa1-bd76-0628573880b0",
  "payload":{
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65",
    "end_user_id": "my_user_12345",
    "link_token_id": "4787acbc-11cf-4db3-998c-5ea7c4feebcd",
    "name": "income",
    "timestamp": "2021-01-12T02:36:01.287148+00:00",
    "outcome": "success"
  }
}
{
  "event": "identity.added",
  "event_id": "5a141122-4235-4fa1-bd76-0628573880b0",
  "payload":{
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65",
    "end_user_id": "my_user_12345",
    "link_token_id": "4787acbc-11cf-4db3-998c-5ea7c4feebcd",
    "name": "identity",
    "timestamp": "2021-01-12T02:36:01.287148+00:00",
    "outcome": "success"
  }
}
{
  "event": "shifts.added",
  "event_id": "5a141122-4235-4fa1-bd76-0628573880b0",
  "payload":{
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65",
    "end_user_id": "my_user_12345",
    "link_token_id": "4787acbc-11cf-4db3-998c-5ea7c4feebcd",
    "name": "shifts",
    "timestamp": "2021-01-12T02:36:01.287148+00:00",
    "outcome": "success",
    "added": [{"id": "dbff9830-55ce-4aa5-82f1-e04f196da041"}],
    "deleted": [{"id": "57043868-6fb3-47e8-a2b7-c993bd023f25"}],
    "params": {
      "count": 14,
      "sync_status": "complete",
    }
  }
}

For accounts with a lot of data, gathering every available shift and paystub can take quite some time. To communicate this progress, both the paystubs.added and shifts.added webhook events have a payload.parms.sync_status field letting you know when Pinwheel is done syncing data from your user's payroll platform. A status of in_progress means we are still collecting data, while a status of complete mean we are done. See Verify Webhooks Events for more details and additional webhook events for paystubs and shifts.

Step 5: Retrieving data

To retrieve core data, you must use the account_id from the webhook event that you received and the endpoint associated with the data you're retrieving.

Examples for employment and income are below.

Employment

Request

GET /v1/accounts/03bbc20e-bc39-464a-b4dc-4b63ffb7213d/employment
Host: api.getpinwheel.com
Content-Type: application/json
x-api-secret: YOUR-API-SECRET

Response

See the full Employment API reference here.

{
  "data": {
    "account_id": "eb240ec6-a227-47ca-b7c7-fbbdfe7170a0",
    "created_at": "2021-01-06T15:59:13.530178+00:00",
    "employer_address": {
      "city": "Washington",
      "country": "US",
      "line1": "1234 Main St",
      "line2": "Suite 3",
      "postal_code": "20036",
      "raw": "1234 Main St, Suite 3, Washington, DC, 20036, USA",
      "state": "DC"
    },
    "employer_name": "Acme Corporation",
    "employer_phone_number": {
      "type": "mobile",
      "value": "+14155552671"
    },
    "id": "9d3309c3-b1d5-47dd-a175-20acf291f419",
    "start_date": "2020-06-01",
    "status": "employed",
    "termination_date": null,
    "title": "Engineer",
    "updated_at": "2021-01-12T00:00:00.000000+00:00"
  }
}

Income

Request

GET /v1/accounts/03bbc20e-bc39-464a-b4dc-4b63ffb7213d/income
Host: api.getpinwheel.com
Content-Type: application/json
x-api-secret: YOUR-API-SECRET

Response

See the full Income API reference here.

{
  "data": {
    "id": "6309e185-9c87-4384-aaae-767c7875775e",
    "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d",
    "created_at": "2021-01-06T15:59:13.530178+00:00",
    "compensation_amount": 400000,
    "compensation_unit": "monthly",
    "currency": "USD",
    "pay_frequency": "monthly",
  }
}

Step 6 (Optional): Query for Job Results

If your application never subscribed to webhook events, or your application server failed to handle the webhook events, you can query the Jobs endpoint with the link_token_id used to initialize Link to fetch the results.

Request

GET /v1/jobs?link_token_id=97f420ff-5d0a-46ee-9cfc-6f17d5d31256&job_types=employment
Host: api.getpinwheel.com
Content-Type: application/json
x-api-secret: YOUR-API-SECRET

Response

{
  "meta": {
    "count": 1
  },
  "data": [
    {
      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
      "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d",
      "link_token_id": "7c4ac4be-4a0e-4468-ab26-c42b249b233b",
      "name": "employment",
      "timestamp": "2021-01-12T02:36:01.287148+00:00",
      "outcome": "success",
      "params": {
        "amount": null
      }
    }
  ]
}

Step 7 (Optional): Performing an On Demand Update

There are times when a user's account loses connection or you need the latest information for a user. We recognize that asking the user to log in multiple times introduces friction, so we've added the ability to re-access the payroll account without needing the user to re-enter credentials.

Enabling this functionality is similar to the way standard Link tokens are created. When creating the Link token, pass in the account_id parameter from a prior account.added webhook event or from the login client side event in Link. The account_id can be preserved and passed in any time you have it for a user.

POST /v1/link_tokens
Host: api.getpinwheel.com
Content-Type: application/json
X-API-SECRET: YOUR-API-SECRET
{
  "org_name": "YOUR APP NAME",
  "end_user_id": "my_user_12345",
  "required_jobs": ["employment", "income"],
  "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d"
}

It is not necessary to pass end_user_id when creating Link tokens for On Demand Updates, as the information is persisted from the initial connection.

Once you've created the Link token, follow Step 3 to initialize Link. When the user is presented with the Pinwheel Link flow, they will be directed to the payroll platform associated with their account, bypassing login if possible.