Migrate to new User APIs
Migration guide for existing Consumer Report and Income integrations on legacy User APIs
This guide is for customers who integrated with Consumer Report by Plaid Check (CRA) before December 10, 2025, or with Plaid Income Verification before August 17, 2026. It explains how to migrate from the legacy User APIs and product endpoints, which use user_token as the primary identifier, to the new User APIs and user_id-based product endpoints. Migration is optional — your existing integration will continue to work, and there is currently no deadline to migrate.
This guide covers both Plaid Check Consumer Report and Plaid Income Verification. Product-specific steps are labeled below.
Are you on the legacy API?
Plaid Check clients who began using /user/create on or after December 10, 2025, and Income clients who began using it on or after August 17, 2026, are on the new User API by default and do not need to migrate. If you aren't sure, call /user/create 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 and Income 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 applicable CRA or Income API requests and webhook correlation. No changes are needed to the users themselves.
New users created after migration use the updated /user/create 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 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_tokenwithuser_idin all applicable CRA or Income API calls,/link/token/create, and webhook correlation. For existing users, set the value of theuser_idfield to your storeduser_tokenvalue. - Update
/user/createto includewith_upgraded_user: true. CRA customers must also replaceconsumer_report_user_identitywith the newidentityschema./user/createis now idempotent: if you call it with aclient_user_idthat already exists, it returns the existinguser_idrather than an error. - Update webhook handling for the products you use:
- For CRA, listen for
USER_CHECK_REPORT_READYandUSER_CHECK_REPORT_FAILEDinstead ofCHECK_REPORT_READYandCHECK_REPORT_FAILED. - For Income, listen for
USER_INCOME_VERIFICATIONinstead ofINCOME_VERIFICATION,USER_INCOME_VERIFICATION_RISK_SIGNALSinstead ofINCOME_VERIFICATION_RISK_SIGNALS, andUSER_INCOME_VERIFICATION_REFRESH_RECONNECT_NEEDEDinstead ofINCOME_VERIFICATION_REFRESH_RECONNECT_NEEDED. - Cash Flow Updates webhooks are consolidated into a single
CASH_FLOW_INSIGHTS_UPDATEDevent.
- For CRA, listen for
- Use the new
/user/getendpoint to retrieve identity details about any user, including those created via the legacy User API.
For full details, see New User API overview 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, you have a stored user_token (format: user-production-*). When making API calls for them:
- Pass your stored
user_tokenvalue in theuser_idfield for all applicable CRA or Income API requests and webhook correlation. - Do not use the legacy
user_idfrom the old/user/createresponse — that value is not used for CRA or Income integrations after migration.
Create new users with the new User API
For new users, call /user/create with the following changes:
- Include
with_upgraded_user: truein the request body. - Replace
consumer_report_user_identitywith anidentityobject containingname,emails,addresses,phone_numbers,date_of_birth, and optionallyid_numbers(last 4 SSN digits). - The response returns a single
user_id— there is nouser_token. Store thisuser_idas the identifier for all subsequent API calls and webhooks.
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" }
]
}
}'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
}'/user/create 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 the client_user_id is new, 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. In the new flow, once /user/remove has been called on a user_id, you can call /user/create again with the same client_user_id to create a new user.
When calling the new /user/create 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 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 and /user/create.
Update Link token creation
In /link/token/create, 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. The user object is no longer required.
The examples below show a CRA Link token. For Income, keep your existing products and income_verification configuration and make the same identifier change.
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"
}
}
}'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 for the products you use:
| Product | Legacy webhook | New webhook |
|---|---|---|
| CRA | CHECK_REPORT_READY | USER_CHECK_REPORT_READY |
| CRA | CHECK_REPORT_FAILED | USER_CHECK_REPORT_FAILED |
| CRA Cash Flow Insights | INSIGHTS_UPDATED / LARGE_DEPOSIT_DETECTED / LOW_BALANCE_DETECTED / NEW_LOAN_PAYMENT_DETECTED / NSF_OVERDRAFT_DETECTED | CASH_FLOW_INSIGHTS_UPDATED |
| Income | INCOME_VERIFICATION | USER_INCOME_VERIFICATION |
| Income | INCOME_VERIFICATION_RISK_SIGNALS | USER_INCOME_VERIFICATION_RISK_SIGNALS |
| Income | INCOME_VERIFICATION_REFRESH_RECONNECT_NEEDED | USER_INCOME_VERIFICATION_REFRESH_RECONNECT_NEEDED |
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 CRA customers on the legacy APIs automatically began receiving both the new and legacy versions of revised CRA webhooks in parallel. Existing Income integrations that have not migrated continue receiving the legacy Income webhooks. Add handlers for the new Income webhook codes before migrating, and retain legacy handlers while your integration still processes legacy webhook events. Recommended approach:
- HTTP layer: Always return a
2xxstatus code for all incoming webhooks. If there is no200response or no response within 10 seconds, Plaid retries delivery for up to 24 hours. See webhook retries. - Application layer: Route events by
webhook_typeandwebhook_code. Safely ignore anywebhook_typevalues your application does not handle.
Call product endpoints for existing users
For existing CRA users, pass your stored user_token value in the user_id field when calling /cra/check_report/base_report/get, /cra/check_report/create, and other CRA endpoints. The response is identical — only the field name in the request changes.
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"
}'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 CRA users, pass the user_id returned by /user/create in the user_id field.
For existing Income users, make the same field-name change when calling applicable Income endpoints, including /credit/sessions/get, /credit/bank_income/get, /credit/bank_income/pdf/get, /credit/bank_statements/uploads/get, /credit/payroll_income/get, /credit/employment/get, /credit/payroll_income/risk_signals/get, /credit/payroll_income/parsing_config/update, and /credit/payroll_income/refresh: pass the stored user_token value in the user_id field. For new Income users, pass the user_id returned by /user/create. See the Income API reference for endpoint details.
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.
- Pick any test user already created via the legacy
/user/createendpoint. You'll have a storeduser_token(format:user-sandbox-*in Sandbox,user-production-*in Production). - Pass that
user_tokenvalue into theuser_idfield in new API calls, for example:/link/token/create→user_idfield- For CRA,
/cra/check_report/base_report/getor/cra/check_report/create→user_idfield - For Income,
/credit/payroll_income/get,/credit/bank_income/get, or another applicable Income endpoint →user_idfield
- Listen for the new webhooks for your product and confirm the
user_idfield in the payload matches your storeduser_token:- For CRA,
USER_CHECK_REPORT_READYandUSER_CHECK_REPORT_FAILED - For Income,
USER_INCOME_VERIFICATION,USER_INCOME_VERIFICATION_RISK_SIGNALS, andUSER_INCOME_VERIFICATION_REFRESH_RECONNECT_NEEDED, as applicable
- For CRA,
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 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) 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 (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 user_token fields in /link/token/create, CRA endpoints, or Income endpoints. If you use the new /user/create flow in a test environment, make sure your code is already updated to use the new field names.
