Technical 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 webhook 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",
"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",
}
}
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",
"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.
If you would instead prefer to be notified after specific days of data have been synced, 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 collectedpaystubs.thirty_days_synced
: Triggered when 30 days of paystubs have been collectedpaystubs.ninety_days_synced
: Triggered when 90 days of paystubs have been collectedpaystubs.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",
"end_user_id": "my_user_12345",
"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",
"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",
"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.
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": {
"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"
}
}
Paystubs
Request
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
Response
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"
}
]
}
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": "eb240ec6-a227-47ca-b7c7-fbbdfe7170a0",
"created_at": "2021-01-06T15:59:13.530178+00:00",
"compensation_amount": 400000,
"compensation_unit": "monthly",
"currency": "USD"
}
}
Identity
Request
GET /v1/accounts/03bbc20e-bc39-464a-b4dc-4b63ffb7213d/identity
Host: api.getpinwheel.com
Content-Type: application/json
x-api-secret: YOUR-API-SECRET
Response
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"
}
]
}
}
Shifts
Request
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 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 Dashboard.
Updated 9 months ago