Onboarding Lifecycle

The onboarding steps and the Customer's status at each one.

A Customer must be verified before making transfers. The Customer's status shows the current step. With the API, the onboarding also has its own status.

Status Flow

How the Customer's status changes during onboarding.

%%{init: {"flowchart": {"curve": "monotoneY"}, "state": {"curve": "monotoneY", "useMaxWidth": false}}}%%
stateDiagram-v2
    state "Agreement accepted" as agreement
    state "Info submitted (hosted form)" as form
    state "Info submitted (API)" as api
    state "Harbor review" as review
    [*] --> unfinished: Customer created
    unfinished --> agreement
    agreement --> form
    agreement --> api
    form --> finished
    api --> verifying
    finished --> verifying
    verifying --> review
    review --> verified: Approved
    review --> rejected: Needs correction
    review --> declined: Declined
    rejected --> verifying: Resubmitted
    rejected --> declined: Final rejection
    verified --> deactivated: Suspended
    deactivated --> verified: Reinstated

    classDef success fill:#d1fae5,stroke:#059669,color:#065f46
    class verified success
    classDef failure fill:#fee2e2,stroke:#dc2626,color:#991b1b
    class declined failure
    classDef warning fill:#fef3c7,stroke:#d97706,color:#92400e
    class rejected warning
    classDef step fill:#f3f4f6,stroke:#9ca3af,stroke-dasharray:4 4,color:#4b5563
    class agreement, form, api, review step

Create the Customer

Create the Customer with their basic details, such as name and email. For the request, see Customers.

POST /api/v1/customers

The response has what you need for the next steps:

  • uuid: the Customer's ID. You use it in every onboarding request.
  • status: unfinished, until their information is submitted.
  • agreement_link: the link the Customer opens to accept the agreement.

Accept the Agreement

The Customer opens agreement_link in a browser and accepts Harbor's service agreement. There's no API endpoint for it.

Agreement Page

  • has_signed_agreement changes to true.
  • The status stays unfinished.
  • Before the agreement is accepted, the onboarding API returns 403 with error code 2103.

Submit the Information

Send the Customer's information to Harbor in one of two ways: you send it through the API, or the Customer fills in a hosted form.

Choose the onboarding type:

  • Standard Onboarding (V1) or Graded Onboarding (V2).
  • The Customer stays on this type. It can't be switched later.
  • API: collect the Customer's personal details, address and identity document in your own app, then send them to Harbor. See Onboard an Individual (V1).
  • Hosted form: send the Customer their verification_link. They open it, fill in their personal details and upload their documents on Harbor's form. See Onboard via Hosted Form.

Harbor Review

During the review, the Customer's status is verifying, and the onboarding status is submitted. To check the onboarding:

GET /api/v1/customers/{customer_uuid}/onboarding
{
    "data": {
        "id": "onb_FFFBubk55R1Myruer83Mah0i8yTBLft6tyP0u1WT",
        "customer_id": "cus_5N12uO7wjwKewJB82WBNPKs3ZaWxqQFn1jnOSVqm",
        "applicant": "individual",
        "status": "submitted",
        "submitted_at": "2026-10-12T05:51:28.000000Z",
        "created_at": "2026-10-12T05:47:11.000000Z",
        "updated_at": "2026-10-12T05:51:28.000000Z"
    }
}

Harbor also sends a webhook event each time the status changes. For the list, see Customer Events and Onboarding Events.

Review times:

Usually 1 business day. A manual check can take a few more days.

Get the Result

After the review, the Customer's status changes to one of these:

statusMeaningWhat to do
verifiedThe Customer is approved.Nothing. The Customer can make transfers.
rejectedSomething in the information needs fixing.Correct it and send it again. See Fix Onboarding Errors.
declinedThe Customer can't be onboarded.Nothing. This is final.

For example, the Customer in each case:

{
    "data": {
        "uuid": "cus_ZDLfhkqKDNZ9jHDJGkssum5GnS59nxFjx0DHTs5z",
        "object": "customer",
        "status": "verified",
        "state": "verified",
        "type": "individual",
        ...
    }
}

Harbor may also ask for more information. In that case, the status stays the same, and the state changes to update_required. See Onboarding RFI.

Check the Onboarding Type

To check an individual's onboarding type:

GET /api/v2/customers/{customer_uuid}/individual/onboarding

  • 200: Graded Onboarding. The response has the Customer's level.
  • 422 with error code 2309: Standard Onboarding.
  • 404 with error code 2001: not onboarded yet.

For example:

{
    "data": {
        "status": "verified",
        "kyc_level": 2,
        "next_level": 3,
        "transfers_blocked": false,
        "rfi_link": null,
        "pending_requirements": []
    }
}

Test in Sandbox

In Sandbox, Customers are verified automatically, without a review. Because of this, the hosted form shows the Customer as already verified. Customers verified this way don't get a level, so they can't test level limits.

To test Graded Onboarding (V2) level by level, create a Customer, accept the agreement, then start at Level 1 and upgrade.

Level 1

Submit a US residence with a two-letter state code. The Customer reaches Level 1 within a few seconds.

POST /api/v2/customers/{customer_uuid}/individual/onboarding

{
    "kyc_level": 1,
    "nationality": "US",
    "residence": { "country": "US", "state": "WA" }
}

Level 2

Upgrade with the Level 2 information. Any JPG or PNG identity image between 100 KB and 8 MB works. The Customer reaches Level 2 within about a minute.

POST /api/v2/customers/{customer_uuid}/individual/onboarding/upgrade

{
    "residence": { "street": "200 Pine St", "sub_street": "Apt 5", "city": "Seattle", "postal_code": "98101" },
    "occupation": "legislators_and_senior_officials",
    "purpose_of_use": ["payments"],
    "ssn": "123456789",
    "identity_document": { "type": "PASSPORT", "country": "US", "front": "{{BASE64_IMAGE}}" }
}

Notes

  • customer.kyc.verified is sent before the level is set, so its kyc_level can still be null. The level comes in customer.kyc_level.changed.
  • In Sandbox, any identity image over 100 KB is accepted for the Level 2 upgrade. In Production, documents are checked, and an invalid image is rejected.

Did this page help you?