Identity Verification for Managed Accounts

Identity verification of Stripe accounts you manage is an important (and required) step toward reducing risk on your platform. If you need help after reading this, check out our answers to common questions or chat live with other developers in #stripe on freenode.

Every country has its own set of rules about paying out funds to individuals and businesses. These are typically known as “Know Your Customer” (KYC) regulations. These regulations broadly require two things: collecting information, and verifying the information is accurate. Stripe is able to perform the verification of Managed Accounts when the required information is provided by the platform.

Managed Accounts can be created and begin accepting payments with as little as the country of the account. Stripe will ask for some additional information as the managed account begins to process payments so it can start receiving bank transfers. Once the managed account has begun to receive transfers, the platform needs to provide any remaining information that is required. The verification process is then completed and the managed account can operate on an ongoing basis.

Verification flow and requirements

Connect platforms need to collect the required information from managed account owners and provide it to Stripe for verification. In terms of exactly when and how to ask for information, this is decided by the platform.

The minimum information needed to send transfers must be provided to Stripe by the platform within seven days of the first charge created for that account or after processing a couple of thousand US dollars (or equivalent) in charges—whichever comes first. This information includes:

  • The business type (individual or company)
  • Full name of the individual or company representative
  • Date of birth of the individual or company representative
  • Address of the individual or business (not required for Australian and U.S. Managed Accounts at this stage)

If this isn’t completed, Stripe temporarily pauses charge creation until the information has been provided. Stripe refunds any charges on the account 21 days after the first charge.

Once the managed account has received transfers totaling more than couple of thousand US dollars (or equivalent), Stripe requests any additional verification information that is still required. If Stripe cannot verify the account with the information provided after transferring another few thousand US dollars (or equivalent), transfers for that managed account are temporarily paused until the issue is resolved.

Collect all required information up-front

Some platforms would rather not have to ask their users for information more than once and, instead, gather as much information as possible from the start. You can pass to us any of the required information as you collect it.

Should all the required information be provided during the creation of a managed account, the verification process can be completed automatically. You might choose this option if you want to verify from the outset that a managed account is fully set up to process and receive payments and transfers on an ongoing basis.

Individual vs. business information

For individuals, this involves collecting information about that person. For businesses, you’ll need to also collect information about the business, in addition to that of one or more persons (company representatives) who are authorized to set up a Stripe account.

The company representative can be anyone who has authorization from their business to set up a managed account on its behalf. These individuals are not responsible or liable for any activity that happens on the businesses’ account.

Determining if identity verification is needed

The Account object has a verification property representing the requirements needed to verify the account. The verification hash contains a list of fields in the fields_needed array. You can test if verification of a connected account is required by seeing if the fields_needed array includes legal_entity.verification.document or legal_entity.additional_owners.#.verification.document (where # can be 0, 1, 2, or 3).

The easiest way to catch a needed identity verification is to watch for the account.updated webhook, which will be sent whenever the verification[fields_needed] list changes (as well as for other account changes).

Whereas the top-level verification property contains information about verifying the Stripe account, the legal_entity property also has its own verification subhash. This hash contains information about verifying the identity of the associated legal entity. The legal_entity hash looks like:

{
  "verification": {
    "status": "pending",
    "document": {
      "id": "file_1234",
      "created": 1414480885,
      "size": 10240
    },
    "details": null,
    "details_code": null
  },
  ...
}

This hash contains everything you need to handle identity verification:

  • status: has one of 3 values–
    • unverified: Stripe is not able to verify this entity right now, either because verification has failed or we do not have enough information to attempt verification
    • pending: Stripe is currently trying to verify this entity
    • verified: Stripe has successfully verified this entity
  • document: reflects the most recently uploaded identity document
  • details: a read-only string containing any information about the identification
  • details_code : a read-only string containing a code for why verification failed. It will have one of 10 values–
    • scan_corrupt: The ID image uploaded is corrupt.
    • scan_not_readable: The image supplied was not readable. It may have been too blurry or not well lit.
    • scan_failed_greyscale: The image supplied was not readable because it was grayscale. Please upload a color copy instead.
    • scan_not_uploaded: No ID scan was uploaded.
    • scan_id_type_not_supported: The ID type submitted is not supported.
    • scan_id_country_not_supported: The ID country is not supported.
    • scan_name_mismatch: The name on the ID does not match the name on the account.
    • scan_failed_other: Scan failed for other reason.
    • failed_keyed_identity: The provided identity information could not be verified.
    • failed_other: Verification failed for other reason.

Note that an unverified status is not an urgent issue, but likely means Stripe will request additional information about the entity in the future.

Information presented in details for any verification status will be suitable for display to the account holder (for example, why an identity verification may have failed).

Handling identity verification

There are two ways to respond to an identity verification request. The first is to correct one of the legal entity’s first_name, last_name, dob hash, personal_id_number, or ssn_last_4 attributes. It’s possible that the account holder misentered this information, so Stripe allows you to change it.

The second option is to upload a scan of an identifying document, such as a passport or driver’s license. This is a two-step process:

  1. Upload the file to Stripe
  2. Attach the file to the account

Uploading a file

To upload a file (either a JPEG or PNG), POST it as part of a multipart/form-data request to https://uploads.stripe.com/v1/files. The file is passed in the file parameter, and there must be another field named purpose, which always contains the string identity_document:

curl https://uploads.stripe.com/v1/files \
   -u {PLATFORM_SECRET_KEY}: \
   -H "Stripe-Account: {CONNECTED_STRIPE_ACCOUNT_ID}" \
   -F purpose=identity_document \
   -F file="@/path/to/a/file"
Stripe.api_key = PLATFORM_SECRET_KEY
Stripe::FileUpload.create(
  {
    :purpose => 'identity_document',
    :file => File.new('/path/to/a/file.jpg')
  },
  {:stripe_account => CONNECTED_STRIPE_ACCOUNT_ID}
)
stripe.api_key = PLATFORM_SECRET_KEY
with open("/path/to/a/file.jpg", "r") as fp:
  stripe.FileUpload.create(
    purpose="identity_document",
    file=fp,
    stripe_account=CONNECTED_STRIPE_ACCOUNT_ID
  )
\Stripe\Stripe::setApiKey(PLATFORM_SECRET_KEY);
\Stripe\FileUpload::create(
  array(
    "purpose" => "identity_document",
    "file" => fopen('/path/to/a/file.jpg', 'r')
  ),
  array("stripe_account" => CONNECTED_STRIPE_ACCOUNT_ID)
);
Stripe.apiKey = PLATFORM_SECRET_KEY;
RequestOptions requestOptions = RequestOptions.builder().setStripeAccount(CONNECTED_STRIPE_ACCOUNT_ID).build();

Map<String, Object> fileUploadParams = new HashMap<String, Object>();
fileUploadParams.put("purpose", "identity_document");
fileUploadParams.put("file", new File("/path/to/a/file.jpg"));

FileUpload.create(fileUploadParams, requestOptions);
var fs = require('fs');
var stripe = require('stripe')(PLATFORM_SECRET_KEY);
stripe.fileUploads.create(
  {
    purpose: 'identity_document',
    file: {
      data: fs.readFileSync('/path/to/a/file.jpg'),
      name: 'file_name.jpg',
      type: 'application/octet-stream'
    }
  },
  {stripe_account: CONNECTED_STRIPE_ACCOUNT_ID}
);

This request will upload the file and return a token:

{
  "id": "file_5dtoJkOhAxrMWb",
  "created": 1403047735,
  "size": 4908
}

You may then use the token’s id value to attach the file to a connected account for identity verification. (See also the file upload guide.)

Attaching the file

Once the file has been uploaded and a representative token returned, simply provide the file ID as the value of the document field in an account update call:

curl https://api.stripe.com/v1/accounts/{CONNECTED_STRIPE_ACCOUNT_ID} \
   -u {PLATFORM_SECRET_KEY}: \
   -d legal_entity[verification][document]=file_5dtoJkOhAxrMWb
Stripe.api_key = PLATFORM_SECRET_KEY
account = Stripe::Account.retrieve({CONNECTED_STRIPE_ACCOUNT_ID})
account.legal_entity.verification.document = 'file_5dtoJkOhAxrMWb'
account.save
stripe.api_key = PLATFORM_SECRET_KEY
account = stripe.Account.retrieve({
  key: })
account.legal_entity.verification.document = "file_5dtoJkOhAxrMWb"
account.save()
\Stripe\Stripe::setApiKey(PLATFORM_SECRET_KEY);
$account = \Stripe\Account::retrieve({CONNECTED_STRIPE_ACCOUNT_ID});
$account->legal_entity->verification->document = 'file_5dtoJkOhAxrMWb';
$account->save();
Stripe.apiKey = PLATFORM_SECRET_KEY;
Account account = Account.retrieve({CONNECTED_STRIPE_ACCOUNT_ID}, (RequestOptions) null);

Map<String, String> verificationParams = new HashMap<String, String>();
verificationParams.put("document", "file_5dtoJkOhAxrMWb");

Map<String, Object> legalEntityParams = new HashMap<String, Object>();
legalEntityParams.put("verification", verificationParams);

Map<String, Object> accountParams = new HashMap<String, Object>();
accountParams.put("legal_entity", legalEntityParams);

account.update(accountParams);
var stripe = require('stripe')(PLATFORM_SECRET_KEY);
stripe.accounts.update(
  {CONNECTED_STRIPE_ACCOUNT_ID},
  {legal_entity: {verification: {document: 'file_5dtoJkOhAxrMWb'}}}
);

This step will change the legal_entity[verification][status] to pending. If an additional owner needs to be verified, instead use legal_entity[additional_owners][#][verification][document], where # is the index of the owner within the legal_entity[additional_owners] array.

Confirming ID verification

Assuming the photo of the ID passes Stripe’s checks, the status field will change to verified. You will also receive an account.updated webhook notification upon completion of the verification process. Note that verification could take anywhere from a few minutes to a day, depending upon how readable the provided item is.

Stripe treats verified identities as immutable entities. You cannot change the information on verified individuals. For verified businesses, you’ll need to contact us to change, for example, the name or EIN on file.

If the verification attempt fails, the status will be unverified and the details field will contain a message stating the cause. The message will be safe to present to your user, such as “The image supplied was not readable”. In addition, the response will contain a details_code value, such as scan_not_readable. Upon failure, verification[fields_needed] will indicate that a new ID upload is required. If the deadline for verification is near, verification[due_by] may also be populated with a date. Again, an account.updated webhook notification will be sent as well.

You can also know that an uploaded ID failed verification on an account if legal_entity[verification][document] has a value, but verification[fields_needed] indicates an ID is still required.

Further reading

Check out the rest of the guide for help with your integration.