Welcome

Welcome to Pinwheel, the fastest way to securely update direct deposits.

Pinwheel provides a set of SDKs and APIs that allow end-users to authorize 3rd party applications to update their direct deposits on payroll platforms such as ADP, Paychex, Gusto, and many more.

Platform Overview

An end to end integration with Pinwheel involves both server-side and client-side configurations. On the server-side, the application can interface with Pinwheel through a REST API service.

On the client-side, end-users interact with Link. Link provides a modal view that allows the end-user to select their payroll provider, authenticate with their login credentials, and consent to updating their direct deposit.

Pinwheel Flow
  1. Your server requests Pinwheel API to generate a short-lived token using your API secret and details about the direct deposit update.
  2. Using the token generated in step 1, your client initializes the Pinwheel SDK to launch the Link modal.
  3. The end user interfaces with the Link modal to choose their payroll provider, input their credentials, and consent to the direct deposit update. Simultaneously, Link sends user input via a web socket to the Pinwheel service which makes the direct deposit update in the user’s payroll provider.
API Secrets

Developers should first create a workspace at https://developer.getpinwheel.com to obtain their API secrets.

api_secret
This private identifier is used to authenticate with the Pinwheel API server. The API secret must be kept private and never exposed publicly on the client-side.
Jobs

There are two types of jobs supported on the platform:

direct_deposit_switch
Sets 100% of an end-user's direct deposit to the specified account. Most commonly used for switching a customer's direct deposits to a new bank account.
direct_deposit_payment
Sets a fixed amount of an end-user's direct deposit to the specified account. Most commonly used for scheduling loan payments directly from a borrower's paycheck.
Modes

There are three Pinwheel modes:

sandbox

Use sandbox mode to build and test your integration. In this mode, you use test credentials to authenticate with payroll platforms, but no actual updates are made to any payroll platform. The sandbox API server is available at https://sandbox.getpinwheel.com.

development

Development mode can be used to test your integration before going live to production. In this mode, you use real credentials to authenticate and direct deposits are actually updated on the payroll platform. The development API server is available at https://development.getpinwheel.com.

production

Use production mode to go live with your integration. Your end users will use their login credentials to authenticate and update direct deposits on payroll platforms. Note, in this environment, all successful direct deposit updates are billed. The production API server is available at https://api.getpinwheel.com.

Integration
Generate Link Token

To initialize Pinwheel, a short-lived link token will need to be generated first. Your server can generate the link token by sending a POST request to the /v1/link/tokens endpoint. DO NOT ever send this request from the client side and publicly expose your api_secret.

Request
POST /v1/link/tokens
Host: api.getpinwheel.com
Content-Type: application/json
X-API-SECRET: INSERT_API_SECRET
{
"org_name": "INSERT_ORGANIZATION_NAME",
"job": "<direct_deposit_switch|direct_deposit_payment>",
"routing_number": "INSERT_ROUTING_NUMBER",
"account_number": "INSERT_ACCOUNT_NUMBER",
"account_type": "<checking|savings>"
}

The link token returned is valid for 15 minutes, after which it expires and can no longer be used to initialize Link. The expiration time is returned as a unix timestamp.

Response
{
"mode": "production",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiJjZTkzNDQ3OS0wYTE2LTRiNzMtYWM2NC0yNDIxMTk2MTA2MGIiLCJkYXRhIjp7ImJhbmtfbmFtZSI6IkJhbmsgb2YgQW1lcmljYSIsInJvdXRpbmdfbnVtYmVyIjoiMDAwMDAwMDAwMSIsImFjY291bnRfbnVtYmVyIjoiMDAwMDAwMDAwMSIsImFjY291bnRfdHlwZSI6ImNoZWNraW5nIiwiZW52aXJvbm1lbnQiOiJzdGFnaW5nIn0sImlhdCI6MTU4MzQyODA4MSwiZXhwIjoxNTgzNDI4OTgxfQ.6HHcrMxkakXaaDNpZ-qZZPO-mA06Dzu-nfUdurwaTUA"
"expires": 1583428981
"token_id": "b4fcb94c-99d6-4f8f-9abf-b87ae9ca4917"
}
Client-Side Link Configuration

Pinwheel Link opens up as a modal in your application. Through the modal, end-users can select their payroll platform, authenticate with their login credentials, and authorize the direct deposit change.

oauthselect providersign in

Pinwheel Link is initialized with a recently generated link token. Additionally, you can pass in completion handler functions that are called after the Link flow is complete.

Sample
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<script src="https://cdn.getpinwheel.com/link-v1.js"></script>
<script>
window.onload = () => {
Pinwheel.init({
linkToken: "INSERT_LINK_TOKEN",
onSuccess: (result) => {
console.log("User successfully updated their direct deposit!");
console.log("Token ID: " + result.tokenId);
},
onFailure: (err) => {
console.log("Direct deposit not switched.");
console.log("Error code: " + err.code);
console.log("Error message: " + err.message);
}
});
</script>
</head>
<body>
</body>
</html>
Sandbox Credentials

In sandbox mode, you can use the following credentials to test Link:

Field
Value
Company ID
company_good
Username
user_good
Email
user_good@example.com
Password
pass_good
SSN
123456789
Last 4 of SSN
1234
MFA code
mfa_code
What street did you grow up on?
pinwheel drive


For providers that support optional multifactor authentication, use the following credentials to test the mfa flow:

Field
Value
Username
user_mfa
Email
user_mfa@example.com


To test error handling, use the following credentials to trigger an exception:

Field
Value
Username
user_crash
Email
user_crash@example.com
Link Methods

Link comes with the ability to programmatically close itself through the developer console

Method Name
Action
Pinwheel.close()
Programmatically closes Link Modal
Link Events

The Pinwheel instance is an event emitter. You can listen to events that are triggered from user interactions. The event emitter used is EventEmitter2 which allows wildcards.

Sample event emitter
Pinwheel.init({
linkToken: "INSERT_LINK_TOKEN",
onSuccess: (result) => {},
onFailure: (err) => {}
});
Pinwheel.on('*', function (payload) {
// NOTE: Since we are using EventEmitter2 with a wildcard event listener here
// we use the classic "function" declaration so we have access to "this.event"
console.log('The name of this event is ' + this.event);
console.log('The payload of this event is:', payload);
}));
Event name
Payload
Intro Close
tokenId: uuid
mode: string
...tokenParams
Intro Continue
tokenId: uuid
mode: string
...tokenParams
Intro PrivacyPolicy
tokenId: uuid
mode: string
...tokenParams
SelectProvider Close
tokenId: uuid
mode: string
...tokenParams
SelectProvider Continue
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
SelectProvider ProviderMissing
tokenId: uuid
mode: string
...tokenParams
SelectProvider ProviderUnknown
tokenId: uuid
mode: string
...tokenParams
ProviderLogin Back
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ProviderLogin Close
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ProviderLogin NoAccount
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ProviderLogin ForgotLogin
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ProviderLogin Continue
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
BankingInput Close
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
BankingInput Continue
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
BankingConfirmation Back
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
BankingConfirmation Close
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
BankingConfirmation Continue
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ChooseMfa Close
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ChooseMfa Continue
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ValidateMfa Close
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ValidateMfa Continue
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ProviderSuccess Exit
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
IncorrectLogin Close
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
IncorrectLogin Retry
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
InvalidMfa Close
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
InvalidMfa Retry
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ProviderError Close
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ProviderError Retry
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ProviderCrash Continue
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ProviderCrash Close
tokenId: uuid
mode: string
...tokenParams
selectedPlatformKey: string
ProviderUnknown Exit
tokenId: uuid
mode: string
...tokenParams
InputMissingProvider Back
tokenId: uuid
mode: string
...tokenParams
InputMissingProvider Close
tokenId: uuid
mode: string
...tokenParams
InputMissingProvider Submit
tokenId: uuid
mode: string
...tokenParams
foundFromSearch: boolean
inputtedProvider: string
MissingProviderSubmitted Exit
tokenId: uuid
mode: string
...tokenParams
ExitSurvey Submit
tokenId: uuid
mode: string
...tokenParams
answer: string
otherReason?: string
Link Error Codes

Link's onFailure callback handler can return the following error codes and messages.

Code
Message
exit
User exited the modal.
unexpected_error
Link crashed, user exited modal.
max-accounts
Maximum number of accounts for payroll provider already exists, new Direct Deposit Account could not be added. User exited the modal
invalid-existing-split
New Direct Deposit Account could not be added due to invalid configurations, user exited the modal
API Reference

All requests to Pinwheel’s API are required to include a custom HTTP header with the correct API secret for your application and environment.

HTTP Header
X-API-SECRET: <INSERT_API_SECRET>
Fetch Platforms
Response Attributes
key
string
Unique identifier of the payroll platform.
name
string
Name of platform, e.g., Paychex, Gusto, ADP.
fractionalAmountSupported
boolean
Whether or not the platform accepts decimal values for direct_deposit_switch or direct_deposit_payment
minAmount
boolean (optional)
Minimum amount, if any, platform accepts for direct_deposit_switch or direct_deposit_payment
maxAmount
boolean (optional)
Maximum amount, if any, platform accepts for direct_deposit_switch or direct_deposit_payment
supported_jobs
string[]
Array of supported jobs.
ENDPOINT
GET /v1/platforms
Response
[
{
"key": "zenefits",
"name": "Zenefits",
"fractional_amount_supported": false,
"supported_jobs": [
"direct_deposit_switch",
"direct_deposit_payment"
]
},
{
"key": "gusto",
"name": "Gusto",
"fractionalAmountSupported": true,
"minAmount": 1,
"supportedJobs": [
"direct_deposit_switch",
"direct_deposit_payment"
]
},
{
"key": "paylocity",
"name": "Paylocity",
"fractional_amount_supported": true,
"supported_jobs": [
"direct_deposit_switch",
"direct_deposit_payment"
]
}
]
Create Sites
Request Params
job
string
direct_deposit_switch or direct_deposit_payment
org_name
string
Organization name
account_number
string
Unique account number
account_type
string
Account type, checking or savings
amount
number (optional)
Amount for each direct deposit payment. Must be at exactly two decimal places and required if job is direct_deposit_payment.
platform_key
string (optional)
If set, user will not be able to select their payroll platform
routing_number
string
Routing number of bank
skip_exit_survey
boolean (optional)
If set to true, exit survey is not shown to user. Defaults to false.
skip_intro_screen
boolean (optional)
If set to true, intro screen with privacy policy is not shown to user. Defaults to false.
unique_user_id
string (optional)
A unique user identifier that is passed through for tracking purposes and does not affect functionality.
redirect_url
string
A URL to redirect after the job has been executed.
Response Attributes
url
string
URL of the generated site you can send your users.
expires
timestamp
Timestamp in seconds when the URL expires.
ENDPOINT
POST /v1/link/sites
Request
{
"org_name": "XYZ Bank",
"job": "direct_deposit_switch",
"routing_number": "401355953",
"account_number": "491190534152",
"account_type": "checking",
"platform_key": "zenefits",
"skip_intro_screen": true,
"skip_exit_survey": false
}
Response
{
"url": "https://use.getpinwheel.com/?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiI3NmNiMDRjOS1lMzA4LTRjMjMtYmI4ZS0zZWM3ZjM1NTFlNDUiLCJkYXRhIjp7Im9yZ19uYW1lIjoiQmFuayBuYW1lIiwicm91dGluZ19udW1iZXIiOiIxMjM0NTY3ODkiLCJhY2NvdW50X251bWJlciI6IjEyMzQ1Njc4OTEiLCJhY2NvdW50X3R5cGUiOiJjaGVja2luZyIsImpvYiI6ImRpcmVjdF9kZXBvc2l0X3N3aXRjaCIsInJlZGlyZWN0X3VybCI6Imh0dHBzOi8vZ29vZ2xlLmNvbSIsIm1vZGUiOiJwcm9kdWN0aW9uIiwiYXBpX2tleSI6ImQzNTIzM2Q4M2FiZGQ4ZjQ0MTk3ZDhhZjUwZDEwZGUxOWI5ODFiOWRjNDc5MWVkZTk2NTQ4YWE5ODEzM2JhNTQifSwiaWF0IjoxNTkxMzYwOTY1LCJleHAiOjMxODUzMTM5MzB9.aX-ypCRlXlc6VguYNQLKFD3DsxjXoSm0LCS5OnzmxQI&redirectUrl=https%3A%2F%2Fgoogle.com",
"expires": 1593952965
}
Create Link token
Request Params
job
string
direct_deposit_switch or direct_deposit_payment
org_name
string
Organization name
account_number
string
Unique account number
account_type
string
Account type, checking or savings
amount
number (optional)
Amount for each direct deposit payment. Must be at exactly two decimal places and required if job is direct_deposit_payment.
platform_key
string (optional)
If set, user will not be able to select their payroll platform
routing_number
string
Routing number of bank
skip_exit_survey
boolean (optional)
If set to true, exit survey is not shown to user. Defaults to false.
skip_intro_screen
boolean (optional)
If set to true, intro screen with privacy policy is not shown to user. Defaults to false.
unique_user_id
string (optional)
A unique user identifier that is passed through for tracking purposes and does not affect functionality.
Response Attributes
mode
string
sandbox, development, or production
token
string
Short-lived token that is used to initialize Pinwheel Link.
expires
integer
Unix timestamp of when token expires.
token_id
string
GUID of the link token.
ENDPOINT
POST /v1/link/tokens
Request
{
"org_name": "XYZ Bank",
"job": "direct_deposit_switch",
"routing_number": "401355953",
"account_number": "491190534152",
"account_type": "checking",
"platform_key": "zenefits",
"skip_intro_screen": true,
"skip_exit_survey": false
}
Response
{
"mode": "production",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWQiOiJjZTkzNDQ3OS0wYTE2LTRiNzMtYWM2NC0yNDIxMTk2MTA2MGIiLCJkYXRhIjp7ImJhbmtfbmFtZSI6IkJhbmsgb2YgQW1lcmljYSIsInJvdXRpbmdfbnVtYmVyIjoiMDAwMDAwMDAwMSIsImFjY291bnRfbnVtYmVyIjoiMDAwMDAwMDAwMSIsImFjY291bnRfdHlwZSI6ImNoZWNraW5nIiwiZW52aXJvbm1lbnQiOiJzdGFnaW5nIn0sImlhdCI6MTU4MzQyODA4MSwiZXhwIjoxNTgzNDI4OTgxfQ.6HHcrMxkakXaaDNpZ-qZZPO-mA06Dzu-nfUdurwaTUA",
"expires": 1583428981,
"token_id": "bd2c09e7-1303-46d5-87c0-0ffa572d7ae4"
}