Handling Identity Verification with the API

    Learn how Connect platforms can use webhooks and the API to handle identity verification of their Custom Accounts.

    Platforms with Custom accounts are required to provide Stripe with the necessary information about their users to meet “Know Your Customer” (KYC) regulations. As a Connect platform with Custom accounts, it’s your responsibility to collect the required information from your users and provide it to Stripe. We’ll then perform verification, asking for more information when needed.

    The rest of this page goes through how platforms:

    • Know when identity verification is needed
    • Provide the necessary information to Stripe

    You should also read our Identity Verification for Custom Accounts guide to learn about the verification flow options, how our API fields translate to both companies and individuals, and how to localize information requests.

    Verification process

    Before charges and payouts can be enabled for a connected account, Stripe needs a certain set of information. The specific needs vary depending on the:

    • The country the connected account is in.
    • The Capabilities the connected account needs (only applicable to connected accounts in the U.S.).
    • Whether the business entity is a company or an individual.

    Platforms need to choose the proper onboarding flow for their business and users to meet the KYC requirements. Broadly speaking, this means providing all the requisite information upfront or incrementally. Either way, you’ll need to be set up to watch for and respond to requests from Stripe.

    1. Establish a platform webhook URL in your webhook settings to watch for activity, especially events of the account.updated type. When using the Persons API, you should also watch for person.updated events.
    2. Immediately after creating an account, check the Account object’s requirements[currently_due] attribute for any additional requirements. If additional information is required, obtain it from the user and update the connected account.
    3. Continue watching for account.updated event notifications to see if the requirements hash changes, reaching out to your user for additional information as needed.

    Note that when you provide additional information, you do not need to resubmit any previously verified details (e.g., if the dob has already been verified, it does not need to be provided again in subsequent updates).

    Once an individual is verified, you cannot change that individual’s information. For verified companies, you’ll need to contact us to make changes (e.g., the name or EIN on file).

    Determining if identity verification is needed

    When you receive an account.updated webhook notification or fetch an account via the API, you receive an Account object. The Account object’s charges_enabled and payouts_enabled indicate whether the account can create charges and accept payouts.

    The Account object has a requirements hash, representing the requirements needed to verify the account. The requirements hash has three arrays:

    • eventually_due: Information in this array isn’t needed immediately, but it will be when certain thresholds are hit. All required information starts in this array.
    • currently_due: Information in this array needs to be collected by the current_deadline and is a subset of eventually_due.
    • past_due: Information in this array means the account is disabled because the required information wasn’t collected. past_due is a subset of currently_due.

    The example below shows what the requirements hash might look like for an account that has some information that’s currently_due, and some information that’s eventually_due.

    {
      "requirements": {
          "disabled_reason": null,
          "current_deadline": 1529085600,
          "past_due": [],
          "currently_due": [
              "external_account",
              "individual.dob.day",
              "individual.dob.month",
              "individual.dob.year",
              "individual.first_name",
              "individual.last_name",
              "tos_acceptance.date",
              "tos_acceptance.ip"
          ],
          "eventually_due": [
              "external_account",
              "individual.address.city",
              "individual.address.line1",
              "individual.address.postal_code",
              "individual.address.state",
              "individual.dob.day",
              "individual.dob.month",
              "individual.dob.year",
              "individual.first_name",
              "individual.last_name",
              "individual.ssn_last_4",
              "tos_acceptance.date",
              "tos_acceptance.ip"
          ],
      },
      ...
    }

    If requirements[currently_due] is not an empty array, requirements[current_deadline] might be set. This is a Unix timestamp identifying when the information is needed by. Usually, if we don’t receive the information by the current_deadline, payouts on the account are disabled. 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 might also disable the ability to process charges. Unless fraud or other rare circumstances occur, Stripe always provides at least 3 days for the information to be provided before limiting account functionality.

    Separately, the requirements[disabled_reason] property might 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.

    Reason Meaning
    fields_needed Additional verification information is required to enable payout or charge capabilities on this account
    listed Account might be a match on a prohibited persons or companies list (Stripe will investigate and either reject or reinstate the account appropriately)
    rejected.fraud Account is rejected due to suspected fraud or illegal activity
    rejected.listed Account is rejected due to a match on a third-party prohibited persons or companies list (such as financial services provider or government)
    rejected.terms_of_service Account is rejected due to suspected terms of service violations
    rejected.other Account is rejected for another reason
    under_review Account is under review by Stripe
    other Account is not rejected but is disabled for another reason while being reviewed

    Person information

    During the verification process, information about the persons associated with an account needs to be collected. If you onboard:

    • Only companies, use the Persons API to collect this information.
    • Only individuals, you can use the Persons API or the individual hash on the Account object.
    • A combination of individuals and companies, use the Persons API to collect this information (this way you collect information in the same way regardless of business type).

    On both the Person object and the individual hash, there is a verification subhash that you can use to help manage identity verification (you should also watch for person.updated events so you know when there are changes):

    {
      "verification": {
          "details": null,
          "details_code": null,
          "document": null,
          "document_back": null,
          "status": "unverified"
      },
      ...
    }

    You can look up the definition for each verification attribute on the Person object or the Account object, but there are two attributes worth noting now: status and details.

    status indicates the current verification state for the person and has three possible values.

    • pending: Stripe is currently trying to verify this entity.
    • 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.
    • verified: Stripe has successfully verified this entity.

    Note that an unverified status is not necessarily an urgent issue, but it does mean that Stripe might request more information soon.

    The details attribute provides an explanation for the current status. This explanation can be shared with the user so they can understand why their account is in the current state.

    Acceptable ID types by country

    The table below lists the documents that Stripe can accept as proof of identity, for each country where Stripe supports the creation of Connect accounts.

    The ID documents that are acceptable vary by country, although a passport scan is always acceptable and preferred. For IDs marked with an *, you must provide scans of both the front and back of the ID, using the document_back field to provide the back of the document.

    CountryAcceptable IDs
    AustraliaPassport, government-issued ID*, or driver's license*
    AustriaPassport, government-issued ID*, or driver's license*
    BelgiumPassport, government-issued ID, or driver's license
    BrazilPassport, government-issued ID*, or driver's license*
    CanadaPassport, government-issued ID*, or driver's license*
    DenmarkPassport, government-issued ID*, or driver's license
    FinlandPassport, government-issued ID*, or driver's license
    FrancePassport, government-issued ID*, or driver's license*
    GermanyPassport, government-issued ID*, or driver's license
    Hong KongPassport or government-issued ID
    IrelandPassport, government-issued ID*, or driver's license
    ItalyPassport, government-issued ID*, or driver's license
    JapanPassport, Juki card* (with photo), driver's license*, foreign nationals residence card*, or health insurance card with certificate of residence or national pension notebook
    LuxembourgPassport, government-issued ID*, or driver's license
    MexicoPassport, government-issued ID*, or driver's license*
    NetherlandsPassport, government-issued ID*, or driver's license*
    New ZealandPassport or driver's license*
    NorwayPassport or driver's license
    PortugalPassport, government-issued ID*, or driver's license
    SingaporePassport, government-issued ID*, or driver's license*
    SpainPassport, government-issued ID*, or driver's license
    SwedenPassport or driver's license
    SwitzerlandPassport, government-issued ID*, or driver's license
    United KingdomPassport, government-issued ID*, or driver's license
    United StatesPassport, government-issued ID, or driver's license*

    Handling ID verification problems

    Many complications with the identity verification process involve the ID itself. To help you recognize and handle the most common problems, this table revisits the details_codes (on the verification subhash) and the likely resolutions for each error.

    Error Resolution
    scan_corrupt
    scan_failed_greyscale
    scan_not_readable
    scan_not_uploaded
    The upload failed due to a problem with the file itself. Ask your user to provide a new file that meets these requirements: a color image (smaller than 8,000px by 8,000px), in JPG or PNG format, and less than 5MB in size.
    scan_id_country_not_supported
    scan_id_type_not_supported
    The provided file was not a government-issued ID from a supported country. Ask your user to provide a new file that meets that requirement.
    scan_name_mismatch The name on the ID does not match the name on the account. Ask your user to verify and correct the provided name information on the account.
    failed_keyed_identity The name on the account could not be verified. Ask your users to verify they provided their full legal name and provide a photo ID matching that name.
    failed_other
    scan_failed_other
    Your support team should reach out to Stripe to learn more about why the identity verification failed.
    document_name_mismatch The name on the ID document doesn't match the name provided by the user. Ask your user to verify and correct the provided name information on the account.

    Handling identity verification

    There are two ways to respond to an identity verification change. The first is to perform an update account call, correcting or adding information.

    Secondarily, we may ask you to upload a color scan or photo of government-issued identification, 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

    For security reasons, we do not accept copies of IDs over email.

    Uploading a file

    To upload a file, POST it as part of a multipart/form-data request to https://files.stripe.com/v1/files. The uploaded file needs to be a color image (smaller than 8,000px by 8,000px), in JPG or PNG format, and less than 5MB in size.

    Pass the file in the file parameter and provide a purpose parameter with the value identity_document:

    curl https://files.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", "rb") 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(
      [
        'purpose' => 'identity_document',
        'file' => fopen('/path/to/a/file.jpg', 'r')
      ],
      ['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}
    );
    
    fp, err := os.Open("/path/to/a/success.png")
    params := &stripe.FileUploadParams{
      FileReader: fp,
      Filename: stripe.String("success.png"),
      Purpose: stripe.String(string(stripe.FileUploadPurposeIdentityDocument)),
    }
    params.SetStripeAccount("{CONNECTED_STRIPE_ACCOUNT_ID}")
    file_upload, err := fileupload.New(params)
    

    This request uploads the file and returns 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.

    Attaching the file

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

    curl https://api.stripe.com/v1/accounts/{{CONNECTED_STRIPE_ACCOUNT_ID}} \
      -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
      -d verification[document][front]=file_5dtoJkOhAxrMWb
    

    This step changes the verification[status] to pending. If additional persons needs to be verified, use the Persons API to update them.

    Confirming ID verification

    Assuming the photo of the ID passes Stripe’s checks, the status field changes to verified. You 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 couple business days, depending on how readable the provided image is.

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

    Further reading

    Now that you know how to perform identity verification through the API, you may want to read:

    Questions?

    We're always happy to help with code or other questions you might have! Search our documentation, contact support, or connect with our sales team. You can also chat live with other developers in #stripe on freenode.

    Was this page helpful? Yes No

    Send

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    On this page