Income And Employment

Introduction

End-users can connect their payroll accounts to share income and employment data with your application. After a user connects their payroll account, several jobs are asynchronously executed in the background to retrieve data from their account and make it available to your application through Pinwheel's REST API.

JobData
employmentEmployment information about the user such as their employer's name, employment status, and start date. See the full API reference here.
identityPersonally identifiable information about the user such as their full name, date of birth, and the last 4 digits of their SSN. See the full API reference here.
incomeIncome information about the user such as their annual salary or hourly wage. See the full API reference here.
paystubsThe user's most recent and historical paystub data. It includes gross pay, net pay, and more detailed information such as deductions and taxes. See the full API reference here.
shiftsOngoing and completed work performed by a user. It includes details about shift dates, timestamps, the type of work, and associated earnings. See the full API reference here.

Implementation

Step 1: Subscribe to Webhook Events

Pinwheel executes jobs in the background, so the best way to be notified when data is available is to subscribe to webhooks. As a user's account is connected and data is collected, events are posted to your webhook to alert your app that the data is ready for consumption. Webhook events can be customized to your use case, so subscribe to the jobs your application requires. The account.added webhooks signals a user's account has been connected, and the rest of the webhooks are job specific.

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": [
    "account.added",
    "employment.added",
    "income.added",
    "paystubs.added",
    "shifts.added",
  ]
}

The complete guide to webhooks can be found here.

Step 2: Create a Link token

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

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

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",
    "token_id": "97f420ff-5d0a-46ee-9cfc-6f17d5d31256",
    "expires": 1583428981,
    "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: Responding 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",
    "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"
  }
}

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",
    "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",
    "link_token_id": "7c4ac4be-4a0e-4468-ab26-c42b249b233b",
    "name": "paystubs",
    "timestamp": "2021-01-21T20:16:28+00:00",
    "outcome": "success",
    "error_code": null,
    "params": {
      "from_pay_date": "2020-10-01",
      "to_pay_date": "2020-12-31",
      "count": 6
    }
  }
}
{
  "event": "income.added",
  "event_id": "5a141122-4235-4fa1-bd76-0628573880b0",
  "payload":{
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65",
    "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",
    "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",
    "link_token_id": "4787acbc-11cf-4db3-998c-5ea7c4feebcd",
    "name": "shifts",
    "timestamp": "2021-01-12T02:36:01.287148+00:00",
    "outcome": "success",
    "params": {
        "count": 14
    }
  }
}

For accounts with a lot of data, gathering every available shift and paystub can take quite some time. To ensure that you can act without waiting for the full process to complete, you can subscribe to any of the sync status events which fire as soon as the relevant data has been retrieved. The sync status events are:

  • paystubs.seven_days_synced: Triggered when 7 days of paystubs have been collected
  • paystubs.thirty_days_synced: Triggered when 30 days of paystubs have been collected
  • paystubs.ninety_days_synced: Triggered when 90 days of paystubs have been collected
  • paystubs.fully_synced: Triggered when all available paystubs have been collected

You can use these sync events to fit your use case. E.g, if you only require a single paystub, you can subscribe to paystubs.seven_days_synced. This event will trigger once 7 days has elapsed from the first observed paystub, i.e. you will be notified as soon as the first paystub is available. Similarly, you would subscribe to the paystubs.thirty_days_synced event if you need at least 2 paystubs.

For Shifts, follow the same convention but swap out the prefix, e.g., shifts.seven_days_synced.

{
  "event": "paystubs.seven_days_synced",
  "event_id": "5a141122-4235-4fa1-bd76-0628573880b0",
  "payload":{
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65",
    "link_token_id": "4787acbc-11cf-4db3-998c-5ea7c4feebcd",
    "name": "paystubs",
    "timestamp": "2021-01-12T02:36:01.287148+00:00",
    "outcome": "success",
    "params": {
      "from_pay_date": "2020-12-01",
      "to_pay_date": "2020-12-31",
      "count": 1
    }
  }
}
{
  "event": "shifts.seven_days_synced",
  "event_id": "5a141122-4235-4fa1-bd76-0628573880b0",
  "payload":{
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "account_id": "449e7a5c-69d3-4b8a-aaaf-5c9b713ebc65",
    "link_token_id": "4787acbc-11cf-4db3-998c-5ea7c4feebcd",
    "name": "shifts",
    "timestamp": "2021-01-12T02:36:01.287148+00:00",
    "outcome": "success",
    "params": {
      "count": 6
    }
  }
}

Occasionally there might be a delay until the data is available. In these cases, we send a webhook with a pending outcome. This indicates that a job was unable to be completed on its first attempt and will be retried up to 24 hours after the initial attempt. A job with a pending outcome will transition to either an error or a success within the 24 hour window and you will be notified via webhook.

Step 5: Retrieving Income and Employment Data

Upon receiving the relevant webhook events, you will make API requests to retrieve data from the different Income and Employment endpoints.

Request

GET /v1/accounts/03bbc20e-bc39-464a-b4dc-4b63ffb7213d/employment
Host: api.getpinwheel.com
Content-Type: application/json
X-API-SECRET: YOUR-API-SECRET
GET /v1/accounts/03bbc20e-bc39-464a-b4dc-4b63ffb7213d/paystubs?limit=1
Host: api.getpinwheel.com
Content-Type: application/json
X-API-SECRET: YOUR-API-SECRET
GET /v1/accounts/03bbc20e-bc39-464a-b4dc-4b63ffb7213d/income
Host: api.getpinwheel.com
Content-Type: application/json
X-API-SECRET: YOUR-API-SECRET
GET /v1/accounts/03bbc20e-bc39-464a-b4dc-4b63ffb7213d/identity
Host: api.getpinwheel.com
Content-Type: application/json
X-API-SECRET: YOUR-API-SECRET
GET /v1/accounts/03bbc20e-bc39-464a-b4dc-4b63ffb7213d/shifts
Host: api.getpinwheel.com
Content-Type: application/json
X-API-SECRET: YOUR-API-SECRET

Response

See the full Employment API reference here.

{
  "data": {
    "id": "077f2bf2-acfe-44b6-9751-efafc5f97341",
    "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d",
    "status": "employed",
    "title": "Sales Associate",
    "start_date": "2019-03-27",
    "employer_name": "Walmart",
    "created_at": "2021-01-09T21:21:25.256571+00:00"
  }
}

See the full Paystubs API reference here.

{
  "meta": {
    "count": 1
  },
  "data": [
    {
      "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
      "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d",
      "pay_date": "2020-12-31",
      "pay_period_start": "2020-12-10",
      "pay_period_end": "2020-12-24",
      "currency": "usd",
      "gross_pay_amount": 480769,
      "net_pay_amount": 273563,
      "check_amount": 280563,
      "gross_pay_ytd": 6971151,
      "net_pay_ytd": 4357992,
      "total_taxes": 139680,
      "total_deductions": 66704,
      "total_reimbursements": 7000,
      "taxes": [
        {
          "name": "Federal Income Tax",
          "category": "federal_income",
          "amount": 65158
        },
        {
          "name": "Social Security",
          "category": "social_security",
          "amount": 29249
        },
        {
          "name": "Medicare",
          "category": "medicare",
          "amount": 6840
        },
        {
          "name": "New York Income Tax",
          "category": "state",
          "amount": 22860
        },
        {
          "name": "New York City Income Tax (New York County)",
          "category": "local",
          "amount": 15573
        }
      ],
      "deductions": [
        {
          "name": "Aetna PPO",
          "category": "medical_insurance",
          "amount": 7012,
          "type": "pre_tax"
        },
        {
          "name": "401(k) deferral",
          "category": "retirement",
          "amount": 57692,
          "type": "pre_tax"
        },
        {
          "name": "Transit",
          "category": "commuter",
          "amount": 2000,
          "type": "pre_tax"
        }
      ],
      "earnings": [
        {
          "name": "Regular Earnings",
          "category": "salary",
          "amount": 480769,
          "rate": null,
          "hours": null
        }
      ],
      "created_at": "2021-01-10T19:54:45.745660+00:00"
    }
  ]
}

See the full Income API reference here.

{
  "data": {
    "id": "6309e185-9c87-4384-aaae-767c7875775e",
    "account_id": "eb240ec6-a227-47ca-b7c7-fbbdfe7170a0",
    "created_at": "2021-01-06T15:59:13.530178+00:00",
    "compensation_amount": 400000,
    "compensation_unit": "monthly",
    "currency": "USD"
  }
}

See the full Identity API reference here.

{
  "data": {
    "id": "ffec8d06-5d03-42b9-9f75-6c66cb6efed5",
    "account_id": "eb240ec6-a227-47ca-b7c7-fbbdfe7170a0",
    "created_at": "2021-01-06T15:59:13.530178+00:00",
    "full_name": "Alicia Green",
    "date_of_birth": "1987-02-01",
    "last_four_ssn": "0090",
    "emails": [
      "[email protected]"
    ],
    "address": {
      "raw": "1234 Home Road, Washington, DC, 20017, USA",
      "line1": "1234 Home Road",
      "line2": null,
      "city": "Washington",
      "state": "DC",
      "postal_code": "20017",
      "country": "US"
    },
    "phone_numbers": [
      {
        "value": "+14155552671",
        "type": "mobile"
      },
      {
        "value": "+14155551234",
        "type": "home"
      }
    ]
  }
}

See the full Shifts API reference here.

{
  "data": [
    {
      "id": "c134b9bd-29f8-452b-bfaa-0dbcc6f6f026",
      "account_id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
      "created_at": "2021-01-29 22:06:06.920124+00:00",
      "start_date": "2021-01-26",
      "end_date": "2021-01-26",
      "type": "shift",
      "timezone": "America/New_York",
      "timestamps": [
        {
          "from": "2021-01-26T12:00:00.745660+00:00",
          "to": "2021-01-26T20:21:00.745660+00:00"
        }
      ],
      "earnings": [
        {
          "name": "Regular Hours",
          "category": "hourly",
          "hours": 8,
          "rate": 2875,
          "amount": 23000
        },
        {
          "name": "Overtime",
          "category": "overtime",
          "hours": 0.35,
          "rate": 4311,
          "amount": 1509
        }
      ],
      "currency": "USD"
    }
  ],
  "meta": {
    "count": 1
  }
}

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 ID of the Link token 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
      }
    }
  ]
}

Please contact [email protected] for access to our Developer Dashboard.


Did this page help you?