Skip to content

Complete Customer Onboarding Guide: From Setup to Customer Creation

This guide walks through the entire customer onboarding process using the checklist-based system, from initial configuration to customer creation.


1. Setting Up a Country Checklist

Before users can onboard, staff must create a country-specific checklist.

Example: Estonia Onboarding Setup

Step 1.1: Create the Checklist

Navigate to Administration → Checklist management → Add Checklist

1
2
3
4
Name: Estonia SME Onboarding
Description: Onboarding form for Estonian companies with automatic validation via Äriregister
Checklist Type: Customer onboarding
Country: EE

Save the checklist.

Step 1.2: Create Country Configuration

Navigate to Administration → Organizations → Country Checklist Configurations → Add

1
2
3
Country: EE
Checklist: Estonia SME Onboarding (select from dropdown)
Is Active: ✓ Yes

Save the configuration.

This configuration maps the country code "EE" to your checklist. When a user starts onboarding with country="EE", the system will automatically use this checklist.


Step 1.3: Add Questions

Now add questions to the checklist. Questions must be added in order.

Question 1: Primary Contact Email
1
2
3
4
5
6
7
Description: Primary Contact Email
Question Type: Text input
Required: ✓ Yes
Order: 1

User Guidance:
This email will be used for important notifications and communications.

Add OnboardingQuestionMetadata:

1
2
3
Question: Primary Contact Email
Maps to Customer Field: email
Intent Field: (leave empty)

Explanation:

  • maps_to_customer_field: "email" - Saved to Customer.email
  • Used for customer communication

Question 2: VAT Code (Estonia-specific, optional)
1
2
3
4
5
6
7
8
Description: VAT Registration Number (KMKR)
Question Type: Text input
Required: ✗ No
Order: 2

User Guidance:
Your Estonian VAT registration number in format EEXXXXXXXXX.
Leave empty if your company is not VAT registered.

Add OnboardingQuestionMetadata:

1
2
3
Question: VAT Registration Number (KMKR)
Maps to Customer Field: vat_code
Intent Field: (leave empty)

Explanation:

  • Only maps_to_customer_field - Goes directly to Customer.vat_code
  • Not used for validation, just stored

Question 3: Intended Use (Multi-select)
1
2
3
4
5
6
7
Description: What is your intended use of the platform?
Question Type: Multi select
Required: ✓ Yes
Order: 3

User Guidance:
Select all that apply. This helps us understand your needs and provide appropriate support.

Add Question Options:

  1. Option 1:
  2. Label: Research & Development
  3. Order: 1

  4. Option 2:

  5. Label: Commercial Operations
  6. Order: 2

  7. Option 3:

  8. Label: Educational Purposes
  9. Order: 3

  10. Option 4:

  11. Label: Government Services
  12. Order: 4

  13. Option 5:

  14. Label: Other
  15. Order: 5

Add OnboardingQuestionMetadata:

1
2
3
Question: What is your intended use of the platform?
Maps to Customer Field: (leave empty)
Intent Field: intent

Explanation:

  • intent_field: "intent" - Stored with verification, NOT in Customer model
  • Multi-select allows multiple choices
  • Accessible via verification.onboarding_metadata['intent']

Question 4: Contact Phone Number
1
2
3
4
5
6
7
Description: Contact Phone Number
Question Type: Text input
Required: ✗ No
Order: 4

User Guidance:
Include country code (e.g., +372 1234567)

Add OnboardingQuestionMetadata:

1
2
3
Question: Contact Phone Number
Maps to Customer Field: phone_number
Intent Field: (leave empty)

Question 5: Company Address
1
2
3
4
5
6
7
Description: Legal Business Address
Question Type: Text area
Required: ✗ No
Order: 5

User Guidance:
Full business address as registered in official documents.

Add OnboardingQuestionMetadata:

1
2
3
Question: Legal Business Address
Maps to Customer Field: address
Intent Field: (leave empty)

Summary: Estonia Checklist Structure

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
Estonia SME Onboarding (country=EE)
├── Q1: Primary Contact Email
│   └── Metadata: Customer.email
├── Q2: VAT Code (optional)
│   └── Metadata: Customer.vat_code
├── Q3: Intended Use (multi-select, required)
│   └── Metadata: intent_field="intent" (stays with verification)
├── Q4: Phone Number (optional)
│   └── Metadata: Customer.phone_number
└── Q5: Company Address (optional)
    └── Metadata: Customer.address

Important Changes:

  • Checklist is now optional: Countries can enable automatic validation without configuring a checklist
  • Checklist for supplemental data only: Email, VAT code, intent, contact info, etc.

Note: Metadata is configured via the OnboardingQuestionMetadata model in Django Admin. The verification_field concept has been removed - verification fields are provided directly in API requests, not extracted from checklists.


2. User Onboarding Flow

Now let's walk through what happens when a user onboards.

Step 2.0: Preview Available Checklists (Optional)

Before starting the verification process, users can preview the available onboarding checklists to see what information will be required. This is useful for preparing necessary documentation and understanding the onboarding requirements.

Frontend Action: User wants to see what information is needed for onboarding

API Call:

1
2
GET /api/onboarding-verifications/available_checklists/
Authorization: Token <user_token>

Optional Query Parameters:

  • checklist_type: Filter by type (customer, intent, or all). Default: all

Response (200 OK):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
{
  "customer_checklist": {
    "uuid": "c1c2c3c4-e5e6-47e8-a9a0-b1b2b3b4b5b6",
    "name": "Customer Data Checklist",
    "description": "Questions mapping to Customer model fields",
    "checklist_type": "onboarding_customer",
    "questions": [
      {
        "uuid": "q1111111-1111-1111-1111-111111111111",
        "description": "Company registration number",
        "question_type": "text_input",
        "required": true,
        "order": 1,
        "onboarding_metadata": {
          "maps_to_customer_field": "registration_code",
          "intent_field": ""
        }
      },
      {
        "uuid": "q2222222-2222-2222-2222-222222222222",
        "description": "Company email address",
        "question_type": "email",
        "required": true,
        "order": 2,
        "onboarding_metadata": {
          "maps_to_customer_field": "email",
          "intent_field": ""
        }
      }
    ]
  },
  "intent_checklist": {
    "uuid": "i1i2i3i4-e5e6-47e8-a9a0-b1b2b3b4b5b6",
    "name": "Intent Checklist",
    "description": "Questions about business intent/purpose",
    "checklist_type": "onboarding_intent",
    "questions": [
      {
        "uuid": "q3333333-3333-3333-3333-333333333333",
        "description": "What is your primary business purpose?",
        "question_type": "text_input",
        "required": false,
        "order": 1,
        "onboarding_metadata": {
          "maps_to_customer_field": "",
          "intent_field": "business_purpose"
        }
      },
      {
        "uuid": "q4444444-4444-4444-4444-444444444444",
        "description": "Select your business sector",
        "question_type": "single_select",
        "required": false,
        "order": 2,
        "question_options": [
          {
            "uuid": "o1111111-1111-1111-1111-111111111111",
            "label": "Technology",
            "order": 1
          },
          {
            "uuid": "o2222222-2222-2222-2222-222222222222",
            "label": "Healthcare",
            "order": 2
          }
        ],
        "onboarding_metadata": {
          "maps_to_customer_field": "",
          "intent_field": "business_sector"
        }
      }
    ]
  }
}

Example: Get Only Intent Checklist

1
2
GET /api/onboarding-verifications/available_checklists/?checklist_type=intent
Authorization: Token <user_token>

Response:

1
2
3
4
5
6
7
8
9
{
  "intent_checklist": {
    "uuid": "i1i2i3i4-e5e6-47e8-a9a0-b1b2b3b4b5b6",
    "name": "Intent Checklist",
    "description": "Questions about business intent/purpose",
    "checklist_type": "onboarding_intent",
    "questions": [...]
  }
}

What the response contains:

  • Both checklists (customer and intent) with their questions and metadata
  • Onboarding metadata for each question showing:
  • maps_to_customer_field: Which Customer model field this maps to (e.g., email, registration_code)
  • intent_field: Intent/purpose field name that stays with verification (e.g., business_purpose, intent)
  • Question options for select-type questions
  • null values if a checklist is not configured

Use Cases:

  • Display onboarding form preview before user starts
  • Inform users what documents/information to prepare
  • Show field mappings for transparency
  • Build dynamic forms based on checklist configuration

Note: This endpoint is available before creating a verification object, allowing users to understand requirements upfront.


Step 2.1: User Initiates Onboarding

Frontend Action: User clicks "Register Company" and selects country "Estonia", then provides company registration code and name

API Call:

1
2
3
4
5
6
7
8
9
POST /api/onboarding-verifications/start_verification/
Authorization: Token <user_token>
Content-Type: application/json

{
  "country": "EE",
  "legal_person_identifier": "12345678",
  "legal_name": "Example Technologies OÜ"
}

Response (201 Created):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "user": "http://localhost:8000/api/users/a1b2c3d4.../",
  "country": "EE",
  "legal_person_identifier": "12345678",
  "legal_name": "Example Technologies OÜ",
  "status": "pending",
  "validation_method": "",
  "verified_user_roles": [],
  "verified_company_data": {},
  "onboarding_metadata": {},
  "customer": null,
  "created": "2025-10-24T10:30:00Z",
  "modified": "2025-10-24T10:30:00Z"
}

What happened:

  • OnboardingVerification created with status="pending"
  • legal_person_identifier and legal_name stored immediately on the verification object
  • ChecklistCompletion created if checklist is configured for country (optional)
  • User receives verification UUID to continue

Note: If no checklist is configured for the country, user can proceed directly to validation (Step 2.4) without submitting additional answers.


Step 2.2: User Fetches Onboarding Form (Optional)

Note: This step is optional. If no checklist is configured for the country, users can skip directly to validation.

API Call:

1
2
GET /api/onboarding-verifications/550e8400-e29b-41d4-a716-446655440000/checklist/
Authorization: Token <user_token>

Response (200 OK):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
{
  "checklist": {
    "uuid": "c1c2c3c4-e5e6-47e8-a9a0-b1b2b3b4b5b6",
    "name": "Estonia SME Onboarding",
    "description": "Onboarding form for Estonian companies with automatic validation via Äriregister",
    "checklist_type": "customer_onboarding",
    "country": "EE"
  },
  "questions": [
    {
      "uuid": "q1111111-1111-1111-1111-111111111111",
      "description": "Primary Contact Email",
      "user_guidance": "This email will be used for important notifications and communications.",
      "question_type": "text_input",
      "required": true,
      "order": 1,
      "existing_answer": null,
      "question_options": [],
      "min_value": null,
      "max_value": null
    },
    {
      "uuid": "q2222222-2222-2222-2222-222222222222",
      "description": "VAT Registration Number (KMKR)",
      "user_guidance": "Your Estonian VAT registration number in format EEXXXXXXXXX...",
      "question_type": "text_input",
      "required": false,
      "order": 2,
      "existing_answer": null,
      "question_options": [],
      "min_value": null,
      "max_value": null
    },
    {
      "uuid": "q3333333-3333-3333-3333-333333333333",
      "description": "What is your intended use of the platform?",
      "user_guidance": "Select all that apply. This helps us understand your needs...",
      "question_type": "multi_select",
      "required": true,
      "order": 3,
      "existing_answer": null,
      "question_options": [
        {
          "uuid": "o1111111-1111-1111-1111-111111111111",
          "label": "Research & Development",
          "order": 1
        },
        {
          "uuid": "o2222222-2222-2222-2222-222222222222",
          "label": "Commercial Operations",
          "order": 2
        },
        {
          "uuid": "o3333333-3333-3333-3333-333333333333",
          "label": "Educational Purposes",
          "order": 3
        },
        {
          "uuid": "o4444444-4444-4444-4444-444444444444",
          "label": "Government Services",
          "order": 4
        },
        {
          "uuid": "o5555555-5555-5555-5555-555555555555",
          "label": "Other",
          "order": 5
        }
      ],
      "min_value": null,
      "max_value": null
    },
    {
      "uuid": "q4444444-4444-4444-4444-444444444444",
      "description": "Contact Phone Number",
      "user_guidance": "Include country code (e.g., +372 1234567)",
      "question_type": "text_input",
      "required": false,
      "order": 4,
      "existing_answer": null,
      "question_options": [],
      "min_value": null,
      "max_value": null
    },
    {
      "uuid": "q5555555-5555-5555-5555-555555555555",
      "description": "Legal Business Address",
      "user_guidance": "Full business address as registered in official documents.",
      "question_type": "text_area",
      "required": false,
      "order": 5,
      "existing_answer": null,
      "question_options": [],
      "min_value": null,
      "max_value": null
    }
  ],
  "completion_percentage": 0,
  "is_completed": false
}

What the frontend does:

  • Dynamically renders form based on questions
  • Shows multi-select for intents
  • Marks required fields
  • User fills out the supplemental information

Step 2.3: User Submits Answers (Optional)

Note: This step is only needed if a checklist exists. Users can skip this if no checklist is configured.

API Call:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
POST /api/onboarding-verifications/550e8400-e29b-41d4-a716-446655440000/submit_answers/
Authorization: Token <user_token>
Content-Type: application/json

[
  {
    "question_uuid": "q1111111-1111-1111-1111-111111111111",
    "answer_data": "contact@example.ee"
  },
  {
    "question_uuid": "q2222222-2222-2222-2222-222222222222",
    "answer_data": "EE123456789"
  },
  {
    "question_uuid": "q3333333-3333-3333-3333-333333333333",
    "answer_data": ["Research & Development", "Commercial Operations"]
  },
  {
    "question_uuid": "q4444444-4444-4444-4444-444444444444",
    "answer_data": "+372 1234567"
  },
  {
    "question_uuid": "q5555555-5555-5555-5555-555555555555",
    "answer_data": "Tallinn, Harju County, 10111, Estonia"
  }
]

Response (200 OK):

1
2
3
4
5
{
  "message": "Answers submitted successfully",
  "completion_percentage": 100,
  "is_completed": true
}

What happened:

  • All answers stored in Answer table linked to ChecklistCompletion
  • Completion status updated to 100%
  • Ready for validation

3. Automatic Validation

Step 3.1: Trigger Validation

After submitting answers (or immediately if no checklist), user triggers automatic validation.

API Call:

1
2
POST /api/onboarding-verifications/550e8400-e29b-41d4-a716-446655440000/run_validation/
Authorization: Token <user_token>

What happens internally:

  1. Use verification data:
1
2
3
4
# legal_person_identifier and legal_name are already on the verification object
# from the start_verification request
legal_person_identifier = verification.legal_person_identifier  # "12345678"
legal_name = verification.legal_name  # "Example Technologies OÜ"
  1. Find validation backend:
1
2
3
4
5
6
7
8
9
backend = backend_registry.find_backend_for_request(
    ValidationRequest(
        country="EE",
        person_identifier=user.civil_number,
        legal_person_identifier="12345678",
        legal_name="Example Technologies OÜ"
    )
)
# Uses EstonianAriregisterBackend
  1. Validate user identity:
1
2
identity_valid, error = backend.validate_user_identity(user)
# Checks if user has Estonian civil_number
  1. Query Äriregister API:
1
2
3
result = backend_registry.validate_company(request)
# Calls Estonian Business Register API
# Checks if user is authorized to represent company
  1. Update verification:
1
2
3
4
5
6
7
8
verification.status = "verified"  # or "escalated"
verification.verified_user_roles = ["board_member", "authorized_representative"]
verification.verified_company_data = {
    "name": "Example Technologies OÜ",
    "registration_code": "12345678",
    "status": "active"
}
verification.save()

Step 3.2: Successful Validation Response

Response (200 OK):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "user": "http://localhost:8000/api/users/a1b2c3d4.../",
  "country": "EE",
  "legal_person_identifier": "12345678",
  "legal_name": "Example Technologies OÜ",
  "status": "verified",
  "validation_method": "ariregister",
  "verified_user_roles": ["board_member", "authorized_representative"],
  "verified_company_data": {
    "name": "Example Technologies OÜ",
    "registration_code": "12345678",
    "status": "active",
    "vat_number": "EE123456789"
  },
  "onboarding_metadata": {
    "intent": ["Research & Development", "Commercial Operations"]
  },
  "raw_response": { /* Full API response */ },
  "error_message": "",
  "validated_at": "2025-10-24T10:35:00Z",
  "expires_at": "2025-10-25T10:35:00Z",
  "customer": null,
  "created": "2025-10-24T10:30:00Z",
  "modified": "2025-10-24T10:35:00Z"
}

Key fields:

  • status: "verified" - Automatic validation succeeded
  • verified_user_roles: User is authorized representative
  • onboarding_metadata: Intent visible here, not on Customer

Step 3.3: Failed Validation Response (Escalated)

If validation fails (user not authorized, API error, etc.):

Response (200 OK):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "status": "escalated",
  "validation_method": "ariregister",
  "verified_user_roles": [],
  "verified_company_data": {},
  "error_message": "USER_NOT_AUTHORIZED",
  "error_traceback": "User is not listed as authorized representative in Äriregister",
  "validated_at": "2025-10-24T10:35:00Z",
  ...
}

Next step: User must provide justification (see Manual Review section)


4. Manual Review Process

When automatic validation fails, users can submit a justification for manual review.

Step 4.1: User Submits Justification

API Call:

1
2
3
4
5
6
7
8
POST /api/onboarding-justifications/create_justification/
Authorization: Token <user_token>
Content-Type: application/json

{
  "verification_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "user_justification": "I am a member of the research group affiliated with this company. Our group is conducting a research project funded by the Estonian Research Council. While I am not listed as a board member, I have authorization from the company director to register on behalf of our research team."
}

Response (201 Created):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "uuid": "j1j2j3j4-j5j6-j7j8-j9j0-jajbjcjdjejf",
  "verification": "http://localhost:8000/api/onboarding-verifications/550e8400.../",
  "user": "http://localhost:8000/api/users/a1b2c3d4.../",
  "legal_person_identifier": "12345678",
  "legal_name": "Example Technologies OÜ",
  "user_justification": "I am a member of the research group...",
  "validation_decision": "pending",
  "validated_by": null,
  "validated_at": null,
  "staff_notes": "",
  "supporting_documentation": [],
  "created": "2025-10-24T10:40:00Z"
}

Step 4.2: Upload Supporting Documents (Optional)

API Call:

1
2
3
4
5
POST /api/onboarding-justifications/j1j2j3j4-j5j6-j7j8-j9j0-jajbjcjdjejf/attach_document/
Authorization: Token <user_token>
Content-Type: multipart/form-data

file: <authorization_letter.pdf>

Response (201 Created):

1
2
3
4
5
6
7
{
  "uuid": "d1d2d3d4-d5d6-d7d8-d9d0-dadbdcdddedf",
  "file": "/media/onboarding_justification_documentation/authorization_letter.pdf",
  "file_name": "authorization_letter.pdf",
  "file_size": 245678,
  "created": "2025-10-24T10:42:00Z"
}

Step 4.3: Staff Reviews Justification

Staff member reviews in Django Admin or via API:

View Justification Details:

  • Verification info: company, user, validation results
  • User's justification text
  • Checklist answers (including intents)
  • Supporting documents

Staff Decision:

1
2
3
4
5
6
7
POST /api/onboarding-justifications/j1j2j3j4-j5j6-j7j8-j9j0-jajbjcjdjejf/approve/
Authorization: Token <staff_token>
Content-Type: application/json

{
  "staff_notes": "Verified authorization."
}

Response (200 OK):

1
2
3
4
5
6
7
{
  "uuid": "j1j2j3j4-j5j6-j7j8-j9j0-jajbjcjdjejf",
  "validation_decision": "approved",
  "validated_by": "http://localhost:8000/api/users/staff123.../",
  "validated_at": "2025-10-24T14:00:00Z",
  "staff_notes": "Verified authorization.",
}

What happened:

  • Justification status → "approved"
  • Verification status → "verified" ← Now eligible for customer creation!

5. Customer Creation

Once verification status is "verified" (either automatic or manual approval), user can create customer.

Step 5.1: Create Customer from Verification

API Call:

1
2
POST /api/onboarding-verifications/550e8400-e29b-41d4-a716-446655440000/create_customer/
Authorization: Token <user_token>

What happens internally:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 1. Check eligibility
# - Status must be "verified" ✓
# - Customer doesn't exist yet ✓
# - No duplicate registration_code ✓
# - If checklist exists, it must be completed ✓

# 2. Extract supplemental data from checklist (if exists)
extracted = verification.extract_data_from_checklist()
# Returns:
# {
#   'customer_data': {
#     'email': 'contact@example.ee',
#     'vat_code': 'EE123456789',
#     'phone_number': '+372 1234567',
#     'address': 'Tallinn, Harju County, 10111, Estonia'
#   },
#   'onboarding_metadata': {
#     'intent': ['Research & Development', 'Commercial Operations']
#   }
# }

# 3. Merge with verified_company_data
customer_data = {
    # Prioritize API data from business registry
    "name": verified_company_data.get("name") or verification.legal_name,
    "country": "EE",
    "registration_code": verification.legal_person_identifier,
    # Add checklist data
    "vat_code": "EE123456789",
    "email": "contact@example.ee",
    "phone_number": "+372 1234567",
    "address": "Tallinn, Harju County, 10111, Estonia"
}

# 4. Create Customer
customer = Customer.objects.create(**customer_data)

# 5. Link to verification
verification.customer = customer
verification.save()

Response (201 Created):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "uuid": "c1c2c3c4-c5c6-c7c8-c9c0-cacbcccdcecf",
  "url": "http://localhost:8000/api/customers/c1c2c3c4.../",
  "name": "Example Technologies OÜ",
  "abbreviation": "",
  "native_name": "",
  "registration_code": "12345678",
  "vat_code": "EE123456789",
  "country": "EE",
  "email": "contact@example.ee",
  "phone_number": "+372 1234567",
  "address": "Tallinn, Harju County, 10111, Estonia",
  "created": "2025-10-24T14:05:00Z",
  "owners": [
    {
      "uuid": "a1b2c3d4-a5a6-a7a8-a9a0-aaabacadaeaf",
      "username": "user@example.com",
      "full_name": "John Doe"
    }
  ],
  "projects": []
}

Key points:

  • ✅ Customer created with data from verification + checklist
  • ✅ API-verified data prioritized over user input
  • ✅ User who verified becomes owner
  • ✅ If checklist has required fields, they must be completed first
  • ❌ Intent NOT in Customer model (stays with verification)

Important: If a checklist exists with required fields and the user hasn't completed them, customer creation will fail with:

1
2
3
{
  "detail": "Cannot create customer: checklist has required fields that are not completed. Please complete all required checklist questions before creating a customer."
}

API Call:

1
2
GET /api/onboarding-verifications/550e8400-e29b-41d4-a716-446655440000/
Authorization: Token <user_token>

Response:

1
2
3
4
5
6
7
8
9
{
  "uuid": "550e8400-e29b-41d4-a716-446655440000",
  "status": "verified",
  "customer": "http://localhost:8000/api/customers/c1c2c3c4.../",
  "onboarding_metadata": {
    "intent": ["Research & Development", "Commercial Operations"]
  },
  ...
}
  • Customer object with business data
  • Verification object with onboarding context (intents)
  • Link between them via verification.customer

6. Complete API Examples

Scenario A: Successful Automatic Validation (Estonia)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# Step 1: Start verification with required fields
curl -X POST http://localhost:8000/api/onboarding-verifications/start_verification/ \
  -H "Authorization: Token abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "country": "EE",
    "legal_person_identifier": "12345678",
    "legal_name": "Example Technologies OÜ"
  }'

# Response: {"uuid": "550e8400...", "status": "pending", "legal_person_identifier": "12345678"}

# Step 2: Get form (optional - only if checklist configured)
curl http://localhost:8000/api/onboarding-verifications/550e8400.../checklist/ \
  -H "Authorization: Token abc123"

# Response: {"checklist": {...}, "questions": [...]}

# Step 3: Submit answers (optional - only if checklist exists)
curl -X POST http://localhost:8000/api/onboarding-verifications/550e8400.../submit_answers/ \
  -H "Authorization: Token abc123" \
  -H "Content-Type: application/json" \
  -d '[
    {"question_uuid": "q111...", "answer_data": "info@example.ee"},
    {"question_uuid": "q222...", "answer_data": "EE123456789"},
    {"question_uuid": "q333...", "answer_data": ["Research & Development"]}
  ]'

# Step 4: Run validation (can be done immediately after Step 1 if no checklist)
curl -X POST http://localhost:8000/api/onboarding-verifications/550e8400.../run_validation/ \

# Response: {"status": "verified", "verified_user_roles": ["board_member"]}

# Step 5: Create customer
curl -X POST http://localhost:8000/api/onboarding-verifications/550e8400.../create_customer/ \
  -H "Authorization: Token abc123"

# Response: {"uuid": "c1c2c3c4...", "name": "Example OÜ", ...}

Scenario B: Manual Review Required

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# Steps 1-4: Same as above, but validation returns:
# {"status": "escalated", "error_message": "USER_NOT_AUTHORIZED"}

# Step 5: Submit justification
curl -X POST http://localhost:8000/api/onboarding-justifications/create_justification/ \
  -H "Authorization: Token abc123" \
  -H "Content-Type: application/json" \
  -d '{
    "verification_uuid": "550e8400...",
    "user_justification": "I am authorized by company director..."
  }'

# Response: {"uuid": "j1j2j3j4...", "validation_decision": "pending"}

# Step 6: Upload document
curl -X POST http://localhost:8000/api/onboarding-justifications/j1j2j3j4.../attach_document/ \
  -H "Authorization: Token abc123" \
  -F "file=@authorization.pdf"

# Step 7: Staff approves (staff token required)
curl -X POST http://localhost:8000/api/onboarding-justifications/j1j2j3j4.../approve/ \
  -H "Authorization: Token staff_token" \
  -H "Content-Type: application/json" \
  -d '{"staff_notes": "Authorization verified. Approved."}'

# Response: {"validation_decision": "approved"}

# Step 8: Check verification status
curl http://localhost:8000/api/onboarding-verifications/550e8400.../ \
  -H "Authorization: Token abc123"

# Response: {"status": "verified"}  ← Now can create customer!

# Step 9: Create customer
curl -X POST http://localhost:8000/api/onboarding-verifications/550e8400.../create_customer/ \
  -H "Authorization: Token abc123"

Summary

Data Flow Diagram

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
1. START VERIFICATION
   └─> OnboardingVerification created (status=pending)
       └─> CountryChecklistConfiguration queried by country
           └─> Checklist found
               └─> ChecklistCompletion created

2. GET CHECKLIST
   └─> Returns country-specific questions

3. SUBMIT ANSWERS
   └─> Answer objects created
       └─> OnboardingQuestionMetadata defines how to map answers:
           ├─> verification_field → Used for validation
           ├─> maps_to_customer_field → Extracted for Customer model
           └─> intent_field → Stored in onboarding_metadata

4. RUN VALIDATION
   ├─> Extract legal_person_identifier from checklist via OnboardingQuestionMetadata
   ├─> Call validation backend (e.g., Äriregister)
   └─> Update verification.status
       ├─> "verified" → Ready for customer creation
       └─> "escalated" → Requires manual review
           └─> CREATE JUSTIFICATION
               └─> Staff reviews → Approve/Reject
                   └─> Approved → status="verified"

5. CREATE CUSTOMER
   ├─> Extract data from checklist answers using OnboardingQuestionMetadata mappings
   ├─> Merge with verified_company_data from API
   ├─> Create Customer with essential fields
   └─> Link: verification.customer → Customer
       ├─> Customer has: name, registration_code, email, etc.
       └─> Verification has: onboarding_metadata (intents)

Key Architecture Components

1. CountryChecklistConfiguration

  • Maps ISO country codes to checklists
  • Lives in onboarding app (not in generic checklist system)
  • Allows multiple checklists per country (toggle with is_active)
  • Example: Country "EE" → "Estonia SME Onboarding" checklist

2. OnboardingQuestionMetadata

  • Defines how question answers should be used
  • Three mapping types:
  • verification_field - For validation (legal_person_identifier, legal_name)
  • maps_to_customer_field - For Customer model (registration_code, email, etc.)
  • intent_field - For verification metadata (intent, purpose)
  • Lives in onboarding app (not in generic checklist system)
  • Configured per question in Django Admin

3. Generic Checklist System

  • Remains completely generic and reusable
  • No onboarding-specific logic
  • Used for project compliance, proposals, offerings, and onboarding

Key Takeaways

Flexible per country - Each checklist completely independent

Checklist is truly optional - Countries can enable automatic validation without configuring a checklist

Required fields in API request - legal_person_identifier and legal_name provided directly in start_verification, not through checklist

Essential data → Customer - email, vat_code, phone_number, address (from checklist)

Context → Verification - intents, purposes, onboarding metadata (from checklist)

No pollution - Generic checklist stays generic, onboarding logic in onboarding app

Clean separation - CountryChecklistConfiguration and OnboardingQuestionMetadata isolate onboarding concerns

Staff configurable - Add countries and configure mappings without code changes

Two validation paths - Automatic + manual fallback

Complete audit trail - All answers preserved in checklist

Checklist completion enforced - If checklist exists with required fields, they must be completed before customer creation

User-friendly - No errors if checklist not configured; works seamlessly with or without checklists

This architecture provides maximum flexibility while keeping models focused and properly separated!