Migrate to new User APIs 
=========================

#### Migration guide for existing Consumer Report integrations on legacy User APIs 

This guide is for customers who integrated with Consumer Report by Plaid Check (CRA) prior to December 2025. It explains how to migrate your integration from the legacy User APIs and CRA API endpoints (which use `user_token` as the primary identifier) to the new User APIs and updated CRA API endpoints. Migration is optional — your existing integration will continue to work, and there is currently no deadline to migrate.

This migration guide applies only to Plaid Check Consumer Report (CRA) customers. If you use the legacy [Plaid Income Verification](https://plaid.com/docs/income/index.html.md) product (non-CRA Bank Income), migration is not yet available. Contact your account manager for timing updates.

#### Are you on the legacy API? 

All clients who began using [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) on or after December 10, 2025 are on the new User API by default and no migration is needed. If you aren't sure, call [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) without `with_upgraded_user: true`. If the response does not include a `user_token`, your client is on the new API by default. If the response includes a `user_token`, then you are eligible to migrate, if you haven't done so already.

#### Overview 

Plaid's new User APIs introduce a unified user identifier, `user_id`, that is consistent across all Plaid user-based products. For existing CRA customers, the migration has two parts depending on whether a user already exists in your system:

**Users you have already created** have a stored `user_token` (format: `user-production-*`). After migration, pass that `user_token` value in the `user_id` field of all CRA API requests and webhook correlation. No changes are needed to the users themselves.

**New users created after migration** use the updated [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) schema, which returns a `user_id` (prefixed with `usr_`) instead of a `user_token`. Use that `user_id` in all subsequent API calls.

The old [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) response returned both a `user_token` and a legacy `user_id` (an unprefixed string, distinct from the `usr_*`\-prefixed `user_id` used in the new APIs). After migration, set the value of the `user_id` field in API requests to your stored `user_token` — not the legacy `user_id`.

#### What's changing 

To migrate, you will need to:

*   **Replace `user_token` with `user_id`** in all CRA API calls, [/link/token/create](https://plaid.com/docs/api/link/index.html.md#linktokencreate) , and webhook correlation. For existing users, set the value of the `user_id` field to your stored `user_token` value.
*   **Update [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate)** to include `with_upgraded_user: true` and replace `consumer_report_user_identity` with the new `identity` schema. [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) is now idempotent: if you call it with a `client_user_id` that already exists, it returns the existing `user_id` rather than an error.
*   **Update webhook handling** to listen for `USER_CHECK_REPORT_READY` and `USER_CHECK_REPORT_FAILED` instead of `CHECK_REPORT_READY` and `CHECK_REPORT_FAILED`. Cash Flow Updates webhooks are consolidated into a single `CASH_FLOW_INSIGHTS_UPDATED` event.
*   **Use the new [/user/get](https://plaid.com/docs/api/users/index.html.md#userget) endpoint** to retrieve identity details about any user, including those created via the legacy User API.

For full details, see [New User API overview](https://plaid.com/docs/api/users/user-apis/index.html.md) and the migration steps below.

#### Migration steps 

##### Update your Plaid client library SDK 

Before making any API changes, upgrade your Plaid client library SDK to the minimum version listed below. Older versions do not support the new request schemas and will return errors.

Minimum required versions:

*   Node.js: 41.0.0
*   Python: 38.0.0
*   Go: 41.0.0
*   Java: 39.0.0
*   Ruby: 45.0.0

##### Handle existing users 

For users you created via the legacy [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) , you have a stored `user_token` (format: `user-production-*`). When making API calls for them:

*   Pass your stored `user_token` value in the `user_id` field for all CRA API requests and webhook correlation.
*   Do not use the legacy `user_id` from the old [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) response — that value is not used for CRA integration after migration.

##### Create new users with the new User API 

For new users, call [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) with the following changes:

*   Include `with_upgraded_user: true` in the request body.
*   Replace `consumer_report_user_identity` with an `identity` object containing `name`, `emails`, `addresses`, `phone_numbers`, `date_of_birth`, and optionally `id_numbers` (last 4 SSN digits).
*   The response returns a single `user_id` — there is no `user_token`. Store this `user_id` as the identifier for all subsequent API calls and webhooks.

/user/create with new User API schema

```bash
curl -X POST https://sandbox.plaid.com/user/create \
  -H 'Content-Type: application/json' \
  -d '{
    "client_id": "${PLAID_CLIENT_ID}",
    "secret": "${PLAID_SECRET}",
    "client_user_id": "c0e2c4ee-b763-4af5-cfe9-46a46bce883d",
    "with_upgraded_user": true,
    "identity": {
      "name": {
        "given_name": "Carmen",
        "family_name": "Berzatto"
      },
      "date_of_birth": "1987-01-31",
      "emails": [
        { "data": "carmen@example.com", "primary": true }
      ],
      "phone_numbers": [
        { "data": "+13125551212", "primary": true }
      ],
      "addresses": [
        {
          "street_1": "3200 W Armitage Ave",
          "city": "Chicago",
          "region": "IL",
          "country": "US",
          "postal_code": "60657",
          "primary": true
        }
      ],
      "id_numbers": [
        { "value": "1234", "type": "us_ssn_last_4" }
      ]
    }
  }'
```

[/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) is now idempotent. If you call it with a `client_user_id` that already exists, it returns the existing `user_id` with a `200` status rather than an error. If you include an `identity` object in that call, it will be attached to the existing user. If you don't, a new `user_id` is created and returned with a `201` status.

Additionally, in the old flow a `client_user_id` could never be reused to create a new user, even after calling [/user/remove](https://plaid.com/docs/api/users/index.html.md#userremove) . In the new flow, once [/user/remove](https://plaid.com/docs/api/users/index.html.md#userremove) has been called on a `user_id`, you can call [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) again with the same `client_user_id` to create a new user.

When calling the new [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) with a `client_user_id` that was previously created via the legacy API, the response `user_id` may be in `user-production-*` format rather than `usr_*` format. This is expected — [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) returns the existing user with a `200`, and since that user was originally created via the legacy API, its `user_token` has simply become the new `user_id`.

For full schema details, see [Updates to user creation and identification](https://plaid.com/docs/api/users/user-apis/index.html.md#updates-to-user-creation-and-identification) and [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) .

##### Update Link token creation 

In [/link/token/create](https://plaid.com/docs/api/link/index.html.md#linktokencreate) , replace the top-level `user_token` field with `user_id`. For existing users, pass your stored `user_token` value in `user_id`. For new users, pass the `user_id` returned by [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) . The `user` object is no longer required.

Before migration

```bash
curl -X POST https://sandbox.plaid.com/link/token/create \
  -H 'Content-Type: application/json' \
  -d '{
    "client_id": "${PLAID_CLIENT_ID}",
    "secret": "${PLAID_SECRET}",
    "user": {
      "client_user_id": "client-user-id-12345"
    },
    "user_token": "user-sandbox-b0e2c4ee-a763-4df5-bfe9-46a46bce993d",
    "products": ["cra_base_report", "cra_income_insights", "cra_network_insights"],
    "webhook": "${WEBHOOK_URL}",
    "client_name": "Name of App",
    "consumer_report_permissible_purpose": "ACCOUNT_REVIEW_CREDIT",
    "country_codes": ["US"],
    "language": "en",
    "cra_options": {
      "days_requested": 365,
      "base_report": {
        "client_report_id": "unique_base_report_id"
      }
    }
  }'
```

After migration

```bash
curl -X POST https://sandbox.plaid.com/link/token/create \
  -H 'Content-Type: application/json' \
  -d '{
    "client_id": "${PLAID_CLIENT_ID}",
    "secret": "${PLAID_SECRET}",
    "user_id": "user-sandbox-b0e2c4ee-a763-4df5-bfe9-46a46bce993d",
    "products": ["cra_base_report", "cra_income_insights", "cra_network_insights"],
    "webhook": "${WEBHOOK_URL}",
    "client_name": "Name of App",
    "consumer_report_permissible_purpose": "ACCOUNT_REVIEW_CREDIT",
    "country_codes": ["US"],
    "language": "en",
    "cra_options": {
      "days_requested": 365,
      "base_report": {
        "client_report_id": "unique_base_report_id"
      }
    }
  }'
```

##### Update webhook handling 

Update your application to handle the renamed webhook events:

| Legacy webhook | New webhook |
| --- | --- |
| `CHECK_REPORT_READY` | `USER_CHECK_REPORT_READY` |
| `CHECK_REPORT_FAILED` | `USER_CHECK_REPORT_FAILED` |
| `INSIGHTS_UPDATED` / `LARGE_DEPOSIT_DETECTED` / `LOW_BALANCE_DETECTED` / `NEW_LOAN_PAYMENT_DETECTED` / `NSF_OVERDRAFT_DETECTED` | `CASH_FLOW_INSIGHTS_UPDATED` |

The `user_id` field in the new webhooks is set to your stored `user_token` value for existing users, and to the `usr_*`\-prefixed `user_id` for users created with the new User API.

As of April 1, 2026, existing customers on the legacy APIs automatically began receiving both the new and legacy versions of revised webhooks in parallel. This means you can update your webhook handling at your own pace — your existing integration continues to work throughout. Once you have switched to the new webhook events, you can safely ignore the legacy ones. Recommended approach:

*   **HTTP layer:** Always return a `2xx` status code for all incoming webhooks. If there is no `200` response or no response within 10 seconds, Plaid retries delivery for up to 24 hours. See [webhook retries](https://plaid.com/docs/api/webhooks/index.html.md#webhook-retries) .
*   **Application layer:** Route events by `webhook_type` and `webhook_code`. Safely ignore any `webhook_type` values your application does not handle.

##### Retrieve reports for existing users 

For existing users, pass your stored `user_token` value in the `user_id` field when calling [/cra/check\_report/base\_report/get](https://plaid.com/docs/api/products/check/index.html.md#cracheck_reportbase_reportget) , [/cra/check\_report/create](https://plaid.com/docs/api/products/check/index.html.md#cracheck_reportcreate) , and other CRA endpoints. The response is identical — only the field name in the request changes.

Before migration

```bash
curl -X POST https://sandbox.plaid.com/cra/check_report/base_report/get \
  -H 'Content-Type: application/json' \
  -d '{
    "client_id": "${PLAID_CLIENT_ID}",
    "secret": "${PLAID_SECRET}",
    "user_token": "user-sandbox-b0e2c4ee-a763-4df5-bfe9-46a46bce993d"
  }'
```

After migration

```bash
curl -X POST https://sandbox.plaid.com/cra/check_report/base_report/get \
  -H 'Content-Type: application/json' \
  -d '{
    "client_id": "${PLAID_CLIENT_ID}",
    "secret": "${PLAID_SECRET}",
    "user_id": "user-sandbox-b0e2c4ee-a763-4df5-bfe9-46a46bce993d"
  }'
```

For new users, pass the `user_id` returned by [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) in the `user_id` field.

#### Testing the migration 

You do not need to create new users to test the migrated API path. Any existing user's `user_token` can be used directly as the `user_id` in the new API fields.

1.  Pick any test user already created via the legacy [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) endpoint. You'll have a stored `user_token` (format: `user-sandbox-*` in Sandbox, `user-production-*` in Production).
2.  Pass that `user_token` value into the `user_id` field in new API calls, for example:
    *   [/link/token/create](https://plaid.com/docs/api/link/index.html.md#linktokencreate) → `user_id` field
    *   [/cra/check\_report/base\_report/get](https://plaid.com/docs/api/products/check/index.html.md#cracheck_reportbase_reportget) → `user_id` field
    *   [/cra/check\_report/create](https://plaid.com/docs/api/products/check/index.html.md#cracheck_reportcreate) → `user_id` field
3.  Listen for `USER_CHECK_REPORT_READY` and `USER_CHECK_REPORT_FAILED` webhook events and confirm the `user_id` field in the payload matches your stored `user_token`.

You can validate the full new flow end-to-end using only existing users in your system, without committing to production migration or creating new users.

#### Compatibility: legacy users vs. new users 

**Users created via the legacy [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) endpoint** (those with a `user-*` format `user_token`) are compatible with both the legacy and new APIs. Their `user_token` value can be passed into the old `user_token` fields or the new `user_id` fields interchangeably during migration. Note that the legacy `user_id` (an unprefixed string also returned by the old [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) ) is not the same as the `user_token` and is not compatible with either the legacy or new API fields — do not use it.

**Users created via the new [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate)** (with `with_upgraded_user: true`) receive a `user_id` with a `usr_*` prefix. These users only work with the new APIs — you cannot pass their `user_id` into legacy fields like `user_token` in [/link/token/create](https://plaid.com/docs/api/link/index.html.md#linktokencreate) or [/cra/check\_report/create](https://plaid.com/docs/api/products/check/index.html.md#cracheck_reportcreate) . If you use the new [/user/create](https://plaid.com/docs/api/users/index.html.md#usercreate) flow in a test environment, make sure your code is already updated to use the new field names.