Implement Bill Manager

Overview

This guide explains how to enable Bill Manager for an end user through Pinwheel's SDK.

Bill Manager allows users to connect their existing bank accounts and credit cards and automatically identifies recurring bills & subscriptions and offers a rich set of features to help manage bills including one-click bill switch to existing accounts/cards and cancelling bills.

Prerequisites

Link SDK versions

In order to use Bill Manager, the Link SDKs used in your application must be installed/upgraded to use the following minimum versions:

  • React (web): 3.3.x
  • iOS: 3.4.x
  • Android: 3.4.x
  • React Native: 3.5.x

We recommend upgrading to the latest version for maximum conversion. Pinwheel requires the use of Native Mobile SDKs (iOS, Android, React Native, or Flutter) in iOS and Android apps.

API and Webhook Version v2025-07-08

The use of Bill Manager requires version v2025-07-08 or higher. Breaking changes for each API version upgrade are listed in our Change Management page. Functionality of older API versions can be found by using the API version dropdown in the top left corner of this page.

Step 1: Set up access to external account transactions

In order to identify an end user's recurring bills and subscriptions, Pinwheel needs access to transaction data from external user accounts that those bills are paid out of. Pinwheel supports the Plaid banking aggregator for retrieving transaction data from external bank and credit card accounts.

If you have already incorporated Plaid into your existing solution, Pinwheel can get access to transaction data through your account as a Plaid partner. Plaid partners get access via Plaid Processor tokens, which are tightly scoped to allow a specific partner access to a specific data stream - in this case, transaction data. The benefits of this approach are that you will continue to own and manage the Plaid account, and in most cases if a user has already connected an external account elsewhere in your app, they don't need to reconnect it for Pinwheel. However, there is some extra development work to support this approach. To learn more about this integration path, see Integrating Pinwheel with your Plaid account.

If you do not already use Plaid, then you can simply use Pinwheel's own Plaid account to identify bills and subscriptions.

Step 2: Create a Pinwheel Link token

Once access to transactions is squared away, you're ready to focus on the real-time Bill Manager user experience.

Start by creating a Link Token for Bill Manager. To do so, you'll need to provide the following:

  • The solution field must be set to "Bill Manager". Note: If you have an existing implementation that specifies the prior name of this solution, "Bill Navigator", it will continue to work.
  • The features field must specify "bill_switch" if you want to enable bill switching and bill cancellation functionality.
    • If you do not include the "bill_switch" feature, Bill Manager will still identify recurring bills and subscriptions and offer all the subscription-management features and benefits outlined in the Overview. In this scenario, it is not necessary to add cards or allocation data in the link token request (see below).
  • A unique identifier for the user that you can resolve to your own internal user model
  • Debit/credit card details for the bank account(s) that the user can choose to switch bills to (if enabled)
  • Details for the bank account(s) that the user can choose for ACH payment methods, e.g. type, routing number, account number, and an account nickname when applicable (if enabled)

See Link Token Creation docs for full details on required fields.

POST /v1/link_tokens
Host: api.getpinwheel.com
Content-Type: application/json
Pinwheel-Version: 2025-07-08
x-api-secret: YOUR-API-SECRET
{
  "org_name": "YOUR APP NAME",
  "end_user_id": "my_user_12345",
  "allocation": {
    "targets": [
      {
        "name": "My Checking Account",
        "type": "checking",
        "routing_number": "07464755",
        "account_number": "193464372203"
      }
    ]
  },
 "cards": [
   {
      "card_name": "Card Name",
      "card_number": "12345678912345",
      "cvc": "111",
      "expiration_date": "05/29",
      "name_on_card": "Jane Doe",
      "card_zip_code": "12345",
      "billing_address": "123 Lane St",
      "billing_address2": "Apt 987",
      "city": "New York",
      "state": "NY"
    }
  ],
  "solution": "Bill Manager",
  "features": [
  	"bill_switch",
  ]
}
POST /v1/link_tokens
Host: api.getpinwheel.com
Content-Type: application/json
Pinwheel-Version: 2025-07-08
x-api-secret: YOUR-API-SECRET
{
  "org_name": "YOUR APP NAME",
  "end_user_id": "my_user_12345",
  "end_user": {
    "platform_matching": {
      "first_name": "John",
      "last_name": "Smith",
      "email": "[email protected]"
    }
  },
  "solution": "Bill Navigator",
  "features": []
}

Step 3: Initialize Link

Use the Link token to open the Link modal in your client application. In addition to passing in the token, you can optionally pass several 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>

The onSuccess callback handler is executed on job success and contains metadata about the bill switch or bill cancellation job. It is important to note that multiple bill switch/cancellation jobs can be executed within a single Pinwheel link flow.

{  
  "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d",  // user's account on the platform
  "platform_id": "97f420ff-5d0a-46ee-9cfc-6f17d5d31256", // merchant (e.g. Netflix)
  "job": "bill_switch",  
  "params": {  
    "payment_method": {
      "type": "card",
      "card": {  
        "card_name": "Card Name",
        "card_num_last4": "2345",
      }  
    }  
  }  
}
{  
  "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d",  // user's account on the platform
  "platform_id": "97f420ff-5d0a-46ee-9cfc-6f17d5d31256", // merchant (e.g. Netflix)
  "job": "bill_cancellation"
}

Step 4: Respond to Webhook Events

account.added

For both bill switches and bill cancellations, after a user successfully logs into the merchant, an account.added webhook event is published. An account_id is provided that represents the end user's account with the merchant they are switching/cancelling.

Using either the end_user_id or the link_token_id, you can associate the account with the user who logged in with Link. For example:

{
  "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", // refers to the merchant (e.g. Netflix)
    "platform_name": "Netflix",
    "created_at": "2021-01-12T02:36:01.287148+00:00",
    "connected": true
  }
}

See webhook specification for account.added for more details here.

bill_switch.added

Once the user has completed a bill switch, a bill_switch.added webhook event is published. For example:

{
  "event": "bill_switch.added",
  "event_id": "5a141122-4235-4fa1-bd76-0628573880b0",
  "payload": {
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d", // users's account with the merchant
    "end_user_id": "my_user_12345",
    "link_token_id": "97f420ff-5d0a-46ee-9cfc-6f17d5d31256",
    "name": "bill_switch",
    "platform_id": "fce3eee0-285b-496f-9b36-30e976194736", // refers to the merchant (e.g. Netflix)
    "platform_name": "Netflix",
    "timestamp": "2021-01-12T02:36:01.287148+00:00",
    "outcome": "success",
    "params": {  
      "is_integrated_switch": true,
      "type": "card",
      "payment": {
        "card_name": "CARD NAME",
        "last_four_card_number": "4242"
      },
      "frequency": "monthly",
      "next_payment_date": "2025-09-26T12:00:00+00:00",
      "next_payment_amount_cents": 1234
    }  
  }
}

See webhook specification for bill_switch.added for more details here.

bill_switch.cancelled

Once the user has completed a bill cancellation, a bill_switch.cancelled webhook event is published. For example:

{
  "event": "bill_switch.cancelled",
  "event_id": "5a141122-4235-4fa1-bd76-0628573880b0",
  "payload": {
    "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
    "account_id": "03bbc20e-bc39-464a-b4dc-4b63ffb7213d", // users's account with the merchant
    "end_user_id": "my_user_12345",
    "link_token_id": "97f420ff-5d0a-46ee-9cfc-6f17d5d31256",
    "name": "bill_switch",
    "platform_id": "fce3eee0-285b-496f-9b36-30e976194736", // refers to the merchant (e.g. Netflix)
    "platform_name": "Netflix",
    "timestamp": "2021-01-12T02:36:01.287148+00:00",
    "outcome": "success",
    "params": {  
      "is_integrated_cancellation": true,
    }  
  }
}

See webhook specification for bill_switch.cancelled for more details here.

Step 5: Returning users

Pinwheel will use end_user_id passed in the Link token to keep track of returning users for recurring transaction monitoring and continued updating. Users who have previously connected an account will maintain their list of recurring transactions. For this use case a link token can be created exactly as above.


Step 6: Bill Detection Webhooks (Recurring Transaction Monitoring)

In addition to action-based webhooks (bill switches and cancellations), Pinwheel provides detection-based webhooks that notify you of changes to a user's recurring bills and subscriptions over time. These webhooks are fired automatically when Pinwheel detects changes in the user's transaction data.

bill.added

Fired when a new subscription or bill is detected for a user.

{
    "event": "bill.added",
    "event_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "payload": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "end_user_id": "user_12345",
        "platform_id": "28b38a67-240e-44be-8df0-0582a4b64a62",
        "platform_name": "Netflix",
        "frequency": "monthly",
        "status": "active",
        "next_payment_amount_cents": 1599,
        "next_payment_date": "2026-02-15T00:00:00Z",
        "last_payment_date": "2026-01-15T00:00:00Z",
        "first_seen_at": "2026-01-29T18:30:00Z",
        "payment": {
            "type": "card",
            "card_name": "Chase Sapphire",
            "last_four_card_number": "4242"
        }
    }
}
FieldTypeDescription
idUUIDUnique identifier for this bill record
end_user_idstringThe end user's unique identifier
platform_idUUIDPlatform/merchant identifier
platform_namestringDisplay name of the platform/merchant (e.g. "Netflix")
frequencystringBilling frequency: weekly, bi-weekly, monthly, bi-monthly, quarterly, semi-yearly, yearly, or other
statusstringBill status: active, inactive, or canceled
next_payment_amount_centsintegerNext payment amount in cents
next_payment_datedatetimeNext scheduled payment date (if known)
last_payment_datedatetimeMost recent payment date (if known)
first_seen_atdatetimeWhen this bill was first detected
paymentobjectPayment method information (see below)

Payment object fields:

FieldTypeDescription
card_namestringName of the card (if type is card)
typestringPayment method type: card or bank_account
last_four_card_numberstringLast 4 digits of card number (if type is card)
account_namestringName of the bank account (if type is bank_account)
last_four_account_numberstringLast 4 digits of account number (if type is bank_account)

bill.updated

Fired when the payment amount has changed on an existing bill.

{
    "event": "bill.updated",
    "event_id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
    "payload": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "end_user_id": "user_12345",
        "platform_id": "28b38a67-240e-44be-8df0-0582a4b64a62",
        "platform_name": "Netflix",
        "frequency": "monthly",
        "status": "active",
        "next_payment_amount_cents": 2299,
        "next_payment_date": "2026-02-15T00:00:00Z",
        "last_payment_date": "2026-01-15T00:00:00Z",
        "first_seen_at": "2025-06-01T12:00:00Z",
        "previous_next_payment_amount_cents": 1599,
        "payment": {
            "type": "card",
            "card_name": "Chase Sapphire",
            "last_four_card_number": "4242"
        }
    }
}

This webhook includes all the fields from bill.added, plus:

FieldTypeDescription
previous_next_payment_amount_centsintegerThe previous next payment amount before the change

bill.reminder

Fired when a payment is upcoming (within 5 days of the next payment date).

{
    "event": "bill.reminder",
    "event_id": "d4e5f6a7-b8c9-0123-def4-567890123456",
    "payload": {
        "id": "550e8400-e29b-41d4-a716-446655440000",
        "end_user_id": "user_12345",
        "platform_id": "28b38a67-240e-44be-8df0-0582a4b64a62",
        "platform_name": "Netflix",
        "frequency": "monthly",
        "status": "active",
        "next_payment_amount_cents": 1599,
        "next_payment_date": "2026-02-03T00:00:00Z",
        "last_payment_date": "2026-01-03T00:00:00Z",
        "first_seen_at": "2025-06-01T12:00:00Z",
        "payment": {
            "type": "card",
            "card_name": "Chase Sapphire",
            "last_four_card_number": "4242"
        }
    }
}

This webhook has the same fields as bill.added. The next_payment_date field indicates when the upcoming payment is due.