GuidesAPI ReferenceChangelog
Log In

Document Uploads

Introduction

Document uploads allows users to upload supported documents within the Link experience. With document uploads, you can send all users through the Pinwheel experience for Verify data, no matter their employment details or ability to connect to a payroll account. The response will be parsed and returned as a normalized JSON object for easy processing and usage in your system. In addition, uploaded paystubs will be evaluated for potential fraud, and the Pinwheel API response indicates if any fraud signals were detected. Pinwheel supports uploading of PDF files as well as common image formats (JPG, PNG, HEIC).

The document uploads experience is available as 2 options:

  1. Fallback (recommended): the user first attempts to connect to their payroll account and document uploads is used as a backup method.
  2. Direct: the user bypasses the payroll account connection and is only presented with the option to upload documents.

You can optionally disable the document uploads functionality if you don’t want to offer this path for users.

Implementation

Step 1: Subscribe to the webhook event for uploaded documents

Subscribe to the documents.added webhook event:

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": [
    "documents.added",
  ]
}

This webhook event will be published when documents have been uploaded by the user and are ready for retrieval. You will receive a webhook event for each document processed.

Documents that don't pass our anti-virus scan will be rejected and will result in a webhook event with outcome: error.

{
  "event_id": "641da69f-3c2d-4ebd-bbaa-42b2147c89ba",
  "event": "documents.added",
  "payload": {
    "document_id": "b90e9fc9-c1ce-42d0-9bde-a08c728e2683",
    "id": "d9617def-0221-4edf-aa0e-7a7f63748cbb",
    "name": "document_uploads",
    "timestamp": "2023-06-09T21:23:03.437394+00:00",
    "outcome": "success",
    "link_token_id": "c10ab8f6-4866-4f6d-b642-ffad6899249e",
    "end_user_id": "12345678",
    "params":{
      "document_type": "paystub",
    },
    "error_code": null,
    "error_type": null
  }
}

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

Step 2: Initialize Link

To initialize Link, your server side code will need to generate a short-lived Link token by sending a POST request to the /link_tokens endpoint. Link tokens are intended to be single-use and expire after one hour. Your server should generate a new Link token each time you wish to launch Pinwheel Link.

Document uploads configuration

Document uploads requires a few key Link token parameters to be set correctly in order to function.

  • end_user_id: Document uploads is built on your User Model since the user may not connect to a payroll account, meaning the documents must be associated with a user directly.
  • required_jobs: A document uploads supported job must be specified. Currently paystubs and tax_forms are supported.
    Note: If direct_deposit_switch is passed in, the document uploads experience will not be available. This is due to prioritizing the deposit switch which requires an account connection.
  • document_uploads: Must be either fallback or direct. Default value is fallback.
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": ["paystubs"],
  "docuemnt_uploads": "fallback"
}

Initialize Link

Using the Link token that was created, open the Link modal in your client application using one of our SDKs. In addition to passing in the token, you can pass in several optional callback handlers.

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.getpinwheel.com/pinwheel-v2.3.js"></script>
    <script>
      Pinwheel.open({
        linkToken: "INSERT LINK TOKEN",
          onSuccess: (result) => {
            console.log("Job succeeded!");
        },
      });
    </script>
  </head>
  <body></body>
</html>

[Optional] Step 3: Listen for Link events

Link events are returned to your client and give you visibility into what is happening in the UX as the user moves through Link Modal. The events for document uploads will allow you to understand when and how many documents have been uploaded.

  • document_upload_begin: One time event when the user first enters the document uploads experience within the Link session.
  • document_upload_submit: One time event when the user submits the uploaded documents. The payload includes a count of files that were successfully uploaded to Pinwheel.

Step 4: Wait for documents.added webhook to fire

After the end user has submitted the documents they wish to upload, Pinwheel does additional back-end processing, including virus-scanning and parsing. This step might take several minutes. After the document is available for query, Pinwheel will fire a documents.added webhook, which contains the specific document_id.

Step 5: Retrieve the document

After receiving a document webhook event, you can retrieve the document using the document_id or list all available documents for the end user.

The documents can be retrieved in 2 ways:

  1. All at once using the List Documents endpoint - /v1/end_users/{end_user_id}/documents/
  2. Individually using the Get Documents endpoint - /v1/end_users/{end_user_id}/documents/{document_id}

In either case, we will return both the PDF and JSON data for the documents. The PDF document is available for download via the provided download_url.

For security purposes, the download_url is only active for 15 minutes. As such, we recommend that you download the document as soon as you retrieve the download_url. If the download_url has expired, calling the endpoint again will generate a new, active download_url.

Request

GET /v1/end_users/01234/documents/35187f48-4879-4956-7713-9a23825e388d
Host: api.getpinwheel.com
Content-Type: application/json
x-api-secret: YOUR-API-SECRET

Response

See the full End User Documents API reference here.

{
    "data":
    {
        "created_at": "2022-07-12T17:32:23.162839-04:00",
        "end_user_id": "01234",
        "document_type": "paystub",
        "document":
        {
            "id": "35187f48-4879-4956-7713-9a23825e388d",
            "download_url": <pre-signed AWS URL>,
            "download_url_expiration": "2022-07-12T17:47:52.782334+00:00"
        },
        "fraud":
        {
            "status":"not_suspected"
        },
        "parsed_data":
        {
            "box_a": "XXX-XX-1234",
            "box_b": "123456789",
            "box_c":
            {
                "name": "SAMPLE COMPANY INC",
                "address":
                {
                    "raw": "123 MAIN ST, ANYWHERE, CA 123456 1234",
                    "line1": "123 MAIN ST",
                    "line2": null,
                    "city": "ANYWHERE",
                    "state": "CA",
                    "postal_code": "123456",
                    "country": "US"
                }
            },
            "box_d": "000011 R#/123",
            "box_e": "JOHN SMITH",
            "box_f":
            {
                "raw": "1234 S MAPLE ST, ANYWHERE, CA 123456",
                "line1": "1234 S MAPLE ST",
                "line2": null,
                "city": "ANYWHERE",
                "state": "CA",
                "postal_code": "123456",
                "country": "US"
            },
            "box_1": 2350000,
            "box_2": 150000,
            "box_3": 2350000,
            "box_4": 145700,
            "box_5": 2350000,
            "box_6": 34057,
            "box_7": null,
            "box_8": null,
            "box_9": null,
            "box_10": null,
            "box_11": null,
            "box_12":
            [
                {
                    "code": "W",
                    "amount": 50000
                },
                {
                    "code": "DD",
                    "amount": 2100
                }
            ],
            "box_13":
            {
                "statutory_employee": false,
                "retirement_plan": false,
                "third_party_sick_pay": false
            },
            "box_14": null,
            "box_15_to_20":
            [
                {
                    "box_15_state": "CA",
                    "box_15_employer_state_id": "12345678901ABC",
                    "box_16": 2350000,
                    "box_17": 80000,
                    "box_18": null,
                    "box_19": null,
                    "box_20": null
                }
            ]
        },
        "warnings":
        []
    }
}

Interested in integrating Pinwheel into your workflow? Reach out to [email protected] for access today!