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 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 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 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 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 CRA 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: trueand 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 to listen for
USER_CHECK_REPORT_READYandUSER_CHECK_REPORT_FAILEDinstead ofCHECK_REPORT_READYandCHECK_REPORT_FAILED. Cash Flow Updates webhooks are consolidated into a singleCASH_FLOW_INSIGHTS_UPDATEDevent. - 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 CRA API requests and webhook correlation. - Do not use the legacy
user_idfrom the old/user/createresponse — that value is not used for CRA integration 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" }
]
}
}'/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 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. 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.
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", # your stored user_token value
"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 |
CASH_FLOW_UPDATES / 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
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.
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, /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" # your stored user_token value
}'For new users, pass the user_id returned by /user/create 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.
- 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/cra/check_report/base_report/get→user_idfield/cra/check_report/create→user_idfield
- Listen for
USER_CHECK_REPORT_READYandUSER_CHECK_REPORT_FAILEDwebhook events and confirm theuser_idfield in the payload matches your storeduser_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 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 fields like user_token in /link/token/create or /cra/check_report/create. 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.