Identity Verification for Custom Accounts

    Identity verification of Stripe accounts you manage is an important—and required—step toward reducing risk on your platform when using Connect. If you need help after reading this, search our documentation or check out answers to common questions. You can even 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 Custom accounts when the required information is provided by the platform.

    Custom 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 Custom account begins to process payments so it can start receiving payouts. Once the Custom account has begun to receive payouts, the platform needs to provide any remaining information that is required. The verification process is then completed and the Custom account can operate on an ongoing basis.

    Verification flow and requirements

    Connect platforms need to collect the required information from Custom 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 payouts 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 U.S. 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. Custom 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 Custom account has received payouts totaling more than couple of thousand U.S. dollars (or equivalent), Stripe requests any additional verification information that is still required. If Stripe cannot verify the account with the information provided after paying out another few thousand U.S. dollars (or equivalent), payouts for that Custom 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 Custom account, the verification process can be completed automatically. You might choose this option if you want to verify from the outset that a Custom account is fully set up to process and receive payments and payouts 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 Custom 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, a read-only hash representing the requirements needed to verify the account. This is a very important section to keep track of for Custom accounts, as ignoring it will result in payouts being disabled for accounts you control.

    {
      "verification": {
        "fields_needed": [
          "legal_entity.type"
        ],
        "due_by": null,
        "disabled_reason": null
      },
      ...
    }

    The verification hash has a fields_needed property: an array of strings representing the account fields required for verification. These strings usually correlate directly to Account object properties. For example, the string legal_entity.type corresponds to the type field in the legal_entity hash. If fields_needed is not empty—if additional information is required, that information is necessary to either enable additional capabilities (if, for example, payouts_enabled is false), or to prevent capabilities from eventually being disabled (if due_by is set).

    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).

    If fields_needed is not an empty array, due_by may be set. This is a Unix timestamp identifying when the information is needed by. Usually, if we don’t receive the information we need by the due date, we will disable payouts on the account. However, there can be other consequences for rarer situations. For example, if payouts are already disabled and our inquiries are not being responded to within a reasonable period of time, Stripe may also disable the ability to process charges. Unless fraud or other rare circumstances occur, Stripe will always provide at least 3 days for the information to be provided before limiting account functionality.

    Separately, the disabled_reason property may also be set. This is a string describing the reason why this account is unable to make payouts or charges. The reason can fall into several categories:

    1. rejected.fraud this account is rejected due to suspected fraud or illegal activity.
    2. rejected.terms_of_service - this account is rejected due to suspected terms of service violations.
    3. rejected.listed - this account is rejected due to a match on a third party prohibited persons or companies list (such as financial services provider or government).
    4. rejected.other - this account is rejected for some other reason.
    5. fields_needed - additional verification information is required in order to enable payout or charge capabilities on this account.
    6. listed - this account might be a match on a prohibited persons or companies list. Stripe will investigate and either reject or reinstate the account appropriately.
    7. under_review - this account is under review by Stripe.
    8. other - this account is not rejected but disabled for other reasons.

    You can examine the charges_enabled and payouts_enabled properties to understand the account’s current capabilities. Feel free to reach out to us if you would like more details.

    The first time you will interact with fields_needed is when creating an account: before payouts can be enabled, we need a certain set of information. The specific needs vary depending on the country and legal entity type. Rather than having to keep track of the requirements for every combination of these options (and changes to those requirements), use fields_needed to discover and address verification needs as they arise.

    That being said, knowing the information that will eventually be requested is useful for planning. We provide some advice and guidelines to help you decide what information to collect.

    We expect developers to use this flow when creating new accounts:

    1. Decide what information you’ll request up-front, usually a subset of fields_needed in the countries you wish to support.
    2. Request this information from the user and use it when creating an account for them.
    3. After creating the account, check verification[fields_needed] for any additional requirements. If additional information is required, obtain it from the user and update the connected account.
    4. Watch for account.updated webhooks to see if verification[fields_needed] changes, reaching out to your user for additional information as needed.

    Finally, there are a few special fields_needed values that warrant noting:

    • legal_entity.additional_owners: see the additional_owners section below for information on the EU owner requirements
    • legal_entity.verification.document: see the next section. Note that this also applies to legal_entity.additional_owners.#.verification.document (where # can be 0, 1, 2, or 3).
    • external_account: noting the singular—this means Stripe needs a bank account for the Stripe account to complete activation. See the payouts guide for details on providing this.

    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).

    Additional owners

    The legal_entity[additional_owners] parameter is an array of hashes of individual information. In Single Euro Payments Area member countries, Stripe is required to collect and verify information about anybody that owns at least 25% of the company, in addition to the representative. For each owner, the possible fields are first_name, last_name, dob, and address. The address and dob fields are identical in formatting to the top-level legal_entity[address] and legal_entity[dob] fields, and the address does not need to be in the same country as the account. Stripe will attempt to verify each owner, and if the verification fails, we may request more information. As such, each additional owner has a verification property that works exactly the same as the legal_entity[verification] hash (see the identity verification section), but for that individual.

    When legal_entity.additional_owners is listed in the verification[fields_needed] array, you must ask the user to provide any additional owners. You may pass an array or integer-indexed hash with information on additional owners. Otherwise, if there are no additional owners, you need to update legal_entity[additional_owners] as shown in the examples below.

    # Create or update additional owners
    curl https://api.stripe.com/v1/accounts/{CONNECTED_STRIPE_ACCOUNT_ID} \
       -u {PLATFORM_SECRET_KEY}: \
       -d legal_entity[additional_owners][0][first_name]=Bob \
       -d legal_entity[additional_owners][0][last_name]=Smith \
       -d legal_entity[additional_owners][1][first_name]=Jane \
       -d legal_entity[additional_owners][1][last_name]=Doe
    
    # Indicate that there are no additional owners
    curl https://api.stripe.com/v1/accounts/{CONNECTED_STRIPE_ACCOUNT_ID} \
       -u {PLATFORM_SECRET_KEY}: \
       -d legal_entity[additional_owners]=
    
    Stripe.api_key = PLATFORM_SECRET_KEY
    account = Stripe::Account.retrieve({CONNECTED_STRIPE_ACCOUNT_ID})
    
    # Create additional owners
    account.legal_entity.additional_owners = [
      {:first_name => 'Bob', :last_name => 'Smith'},
      {:first_name => 'Jane', :last_name => 'Doe'}
    ]
    
    # Add an additional owner
    length = account.legal_entity.additional_owners.length
    account.legal_entity.additional_owners[length] = {
      :first_name => 'Andrew',
      :last_name => 'Jackson'
    }
    
    # Update additional owners
    account.legal_entity.additional_owners[0].first_name = 'Robert'
    
    # Indicate that there are no additional owners
    account.legal_entity.additional_owners = nil
    
    account.save
    
    stripe.api_key = PLATFORM_SECRET_KEY
    account = stripe.Account.retrieve({CONNECTED_STRIPE_ACCOUNT_ID})
    
    # Create additional owners
    account.legal_entity.additional_owners = [
      {"first_name": "Bob", "last_name": "Smith"},
      {"first_name": "Jane", "last_name": "Doe"}
    ]
    
    # Add an additional owner
    account.legal_entity.additional_owners.append(
      {"first_name": "Andrew", "last_name": "Jackson"}
    )
    
    # Update additional owners
    account.legal_entity.additional_owners[0].first_name = "Robert"
    
    # Indicate that there are no additional owners
    account.legal_entity.additional_owners = None
    
    account.save()
    
    \Stripe\Stripe::setApiKey(PLATFORM_SECRET_KEY);
    $account = \Stripe\Account::retrieve({CONNECTED_STRIPE_ACCOUNT_ID});
    
    // Create additional owners
    $account->legal_entity->additional_owners = array(
      array('first_name' => 'Bob', 'last_name' => 'Smith'),
      array('first_name' => 'Jane', 'last_name' => 'Doe')
    );
    
    // Add an additional owner
    $count = count($account->legal_entity->additional_owners);
    $account->legal_entity->additional_owners[$count] = array(
      'first_name' => 'Andrew',
      'last_name' => 'Jackson'
    );
    
    // Update additional owners
    $account->legal_entity->additional_owners[0]->first_name = 'Robert';
    
    // Indicate that there are no additional owners
    $account->legal_entity->additional_owners = null;
    
    $account->save();
    
    Stripe.apiKey = PLATFORM_SECRET_KEY;
    Account account = Account.retrieve({CONNECTED_STRIPE_ACCOUNT_ID}, (RequestOptions) null);
    
    Map<String, Object> accountParams = new HashMap<String, Object>();
    Map<String, Object> legalEntityParams = new HashMap<String, Object>();
    List<Map<String, Object>> additionalOwnersParams = new LinkedList<Map<String, Object>>();
    
    // Create additional owners
    Map<String, Object> newOwner1 = new HashMap<String, Object>();
    newOwner1.put("first_name", "Bob");
    newOwner1.put("last_name", "Smith");
    
    Map<String, Object> newOwner2 = new HashMap<String, Object>();
    newOwner2.put("first_name", "Jane");
    newOwner2.put("last_name", "Doe");
    
    additionalOwnersParams.add(newOwner1);
    additionalOwnersParams.add(newOwner2);
    
    // Add an additional owner
    int size = additionalOwnersParams.size();
    Map<String, Object> newOwner3 = new HashMap<String, Object>();
    newOwner3.put("first_name", "Andrew");
    newOwner3.put("last_name", "Jackson");
    additionalOwnersParams.add(size, newOwner3);
    
    // Update additional owners
    additionalOwnersParams.get(0).put("first_name", "Robert");
    
    // Indicate that there are no additional owners
    legalEntityParams.put("additional_owners", new LinkedList<Object>());
    
    accountParams.put("legal_entity", legalEntityParams);
    
    account.update(accountParams);
    
    var stripe = require('stripe')(PLATFORM_SECRET_KEY);
    
    // Create and update additional owners
    stripe.accounts.update(
      {CONNECTED_STRIPE_ACCOUNT_ID},
      {
        legal_entity: {
          additional_owners: {
            // Note the use of an object instead of an array
            0: {first_name: 'Bob', last_name: 'Smith'},
            1: {first_name: 'Jane', last_name: 'Doe'}
          }
        }
      }
    );
    
    // Indicate that there are no additional owners
    stripe.accounts.update(
      {CONNECTED_STRIPE_ACCOUNT_ID},
      {legal_entity: {additional_owners: ''}}
    );
    

    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 mis-entered 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 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.

    Webhooks

    All changes, including the important verification hash changes for Custom accounts, will be delivered via webhooks as an account.updated event. Be certain to establish a platform webhook URL in your webhook settings to watch for these.

    Further reading

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