Accepted design · Digital onboarding
Customer update journeys
How authenticated customers discover, resume, and complete profile-enrichment journeys from current NewGen requirements and configured forms.
1. Overview
A customer update is a post-onboarding journey for a digitally onboarded customer. The service returns only journeys that match the customer's current profile requirements. Customer Information and Biometrics are separate journeys.
NewGen decides which profile requirements remain. Form Engine selects existing journey steps and form properties that can satisfy those requirements. Step submissions remain local until the customer submits the confirmation step. That final step sends one journey payload to NewGen and waits for the system of record.
docs/adr/cif-update.2. Ownership and eligibility
| Concern | Owner |
|---|---|
| Authentication and digital-onboarding claim | Profile Management |
| Current profile requirement values, required state, and editability | NewGen |
| Journey state, progress, locking, and reconciliation | Onboarding Service |
| Journey templates, forms, validation, and customer copy | Form Engine |
| CIF data | Finacle |
| Biometric enrollment | Bio Server |
All customer-update endpoints require a bearer token. The service reads `CustomerId` as the CIF, `bankId` as the bank identifier, and `onboardingType` as the onboarding channel enum. Only onboardingType: 5 is eligible. The service never accepts these values from a request body.
Every instance lookup verifies ownership by (bankId, CustomerId). A request for another customer's instance returns 404.
3. NewGen assessment contract
The status endpoint asks NewGen for current requirement groups. NewGen returns values and states, not UI controls.
{
"preferredAddress": {
"streetBuilding": { "editable": true, "value": "", "required": false },
"town": { "editable": true, "value": "", "required": true },
"countryCode": { "editable": false, "value": "KE", "required": true },
"cityCode": { "editable": true, "value": "002", "required": true },
"villageEstate": { "editable": true, "value": "", "required": false },
"stateCode": { "editable": true, "value": "NAI", "required": false }
},
"workitemName": "DP-TEST-00001-Update",
"cifInfo": { "cifNo": "00000000001" },
"sourceOfIncome": {
"occupation": { "editable": true, "value": "", "required": false },
"employmentStatus": { "editable": true, "value": "", "required": false }
},
"contacts": {
"emailAddress": { "editable": true, "value": "", "required": true }
},
"successful": true,
"statusCode": "00"
}
This confirmed response has no missing property. In this contract version, the service requests a parameter when it is required, editable, and its string value is empty. The full fixture is docs/newgen-assessment-v1.example.json; its identifying values are synthetic.
4. Resolve configured forms
- Resolve each NewGen group through an internal Form Engine
sourceReference. - Load the configured journey and form versions for the customer's country and channel.
- Keep a form property only when
required && editable && value is empty. - Require NewGen child names to match schema property names.
- Omit a step when no properties remain. Omit a journey when no steps remain.
- Fail the complete catalog when a required mapping or configuration is invalid.
For the example above, the resolved forms contain town and emailAddress. They omit populated cityCode, read-only countryCode, and optional empty fields. A required, non-editable empty parameter produces actionRequired.
CIF-update forms in this delivery do not contain cross-field rules. Existing onboarding forms keep their current cross-field validation. Supporting values may appear in data, but only properties in the resolved schema may be submitted. The backend rejects additional properties.
5. Status catalog
GET /customer-updates/status queries NewGen on every request and resolves the catalog in memory. An unchanged resolution reuses local journey instances and causes no database write.
{
"overallStatus": "incomplete",
"progress": { "completedSteps": 7, "totalSteps": 10, "percentage": 0.7 },
"journeys": [
{
"journeyId": "customer_information_update",
"displayOrder": 1,
"title": "Customer information",
"subtitle": "Fill in your physical address and occupation information.",
"lifecycle": "not_started",
"availability": "available",
"customerUpdateId": null,
"startToken": "opaque-short-lived-token"
},
{
"journeyId": "biometric_fingerprints_update",
"displayOrder": 2,
"title": "Capture your biometrics",
"subtitle": "Share your biometric information.",
"lifecycle": "not_started",
"availability": "available",
"customerUpdateId": null,
"startToken": "opaque-short-lived-token"
}
]
}
Lifecycle values are not_started, active, and processing. Availability values are available, locked, and temporarily_unavailable. The API supplies displayOrder; the client does not sort by state.
Customer Information and Biometrics are independent. Both can be available before the customer starts either one. After a journey starts, that item becomes active and every other item becomes locked until core-confirmed completion.
An active catalog item contains its customerUpdateId. The client calls current-step after selection. The catalog does not contain forms or current-step data. If NewGen reports only non-editable gaps, the response uses actionRequired and configured support guidance without exposing internal parameter names.
An ineligible customer receives notEligible, no progress, and an empty journey list. A customer with no remaining gaps receives complete, progress 1.0, and an empty journey list.
6. Authenticated client API
| Method | Route | Purpose |
|---|---|---|
| GET | /customer-updates/status | Resolve overall progress and the journey catalog. |
| POST | /customer-updates | Start or return the active instance from a protected start token. |
| GET | /customer-updates/{customerUpdateId}/journey-config | Return resolved steps and journey progress. |
| GET | /customer-updates/{customerUpdateId}/current-step | Return the current step using the onboarding response shape. |
| GET | /customer-updates/{customerUpdateId}/steps/{stepId} | Read a resolved step without changing journey state. |
| POST | /customer-updates/{customerUpdateId}/steps/{stepId}/submit | Save a draft or validate and submit a step. |
| POST | /customer-updates/{customerUpdateId}/otp/verify | Verify the OTP for the instance. |
| POST | /customer-updates/{customerUpdateId}/otp/regenerate | Regenerate the OTP for the instance. |
The executable examples are in the Customer updates Postman collection.
Start request
{ "startToken": "opaque-short-lived-token" }
The protected token contains the customer key, journey and form versions, resolved field set, assessment fingerprint, and expiry. It creates no database record until used. An expired token requires another status request. A replay returns the active instance.
Start response
{
"customerUpdateId": "019c...",
"journeyId": "customer_information_update",
"lifecycle": "active",
"currentStepId": "email_address"
}
Journey configuration response
{
"customerUpdateId": "019c...",
"journeyId": "customer_information_update",
"journeyVersion": 2,
"progress": { "completedSteps": 1, "totalSteps": 4, "percentage": 0.25 },
"steps": [
{
"stepId": "email_address",
"sequence": 1,
"formKey": "profile_email_validation",
"formVersion": 2,
"title": "Email address",
"type": "Form",
"status": "Validated"
},
{
"stepId": "preferred_address",
"sequence": 2,
"formKey": "profile_preferred_address",
"formVersion": 1,
"title": "Preferred address",
"type": "Form",
"status": "NotStarted"
},
{
"stepId": "review_information",
"sequence": 3,
"formKey": "review_information",
"formVersion": 1,
"title": "Review information",
"type": "ReviewForm",
"status": "NotStarted"
},
{
"stepId": "confirm",
"sequence": 4,
"formKey": "confirm",
"formVersion": 1,
"title": "Confirm",
"type": "ConfirmForm",
"status": "NotStarted"
}
],
"branches": []
}
The response keeps the configured-journey structure. It adds instance progress and step status, but does not include form schemas.
7. Steps, drafts, OTP, and review
current-step and get-step use the same response structure as onboarding. The response contains data and a nested form with formKey, formVersion, schema, uiSchema, hash, and context.
{
"customerUpdateId": "019c...",
"currentStepId": "preferred_address",
"stepType": "Form",
"status": "NotStarted",
"onboardingStatus": "InProgress",
"countryCode": "KE",
"data": { "country": "KE", "city": "NBO" },
"form": {
"formKey": "profile_preferred_address",
"formVersion": 1,
"schema": {
"type": "object",
"properties": {
"town": { "type": "string" },
"building": { "type": "string" }
},
"required": ["town", "building"]
},
"uiSchema": {},
"statusPage": null,
"hash": "sha256:...",
"context": {}
},
"documentType": null,
"documentNumber": null,
"resolvedBranches": {}
}
The customer-update response renames onboardingId to customerUpdateId. All other fields retain the onboarding endpoint structure.
{
"requestId": "client-generated-id",
"isDraft": false,
"payload": { "town": "Westlands" }
}
The step submission contract remains compatible with onboarding. While review is current, the client may submit a corrected payload for an earlier reviewable step. The backend validates and replaces that step without moving away from review.
Email is a step in Customer Information. A missing email follows the existing onboarding OTP behavior. The customer may correct the email before OTP validation, but cannot reopen it afterward. OTP routes use the customerUpdateId.
8. Submit one journey
Normal form submissions do not call NewGen. They validate and persist customer data locally. Submitting the configured confirmation step performs these actions:
- Validate the confirmation and all selected fields.
- Atomically move the journey to
processing. - Build one flat object that contains the values collected by the journey.
- Call NewGen once for the journey. NewGen decides whether to reuse or create a WorkItem.
- Wait for Finacle or Bio Server confirmation through NewGen.
- Complete the journey and resolve status again.
{
"town": "Westlands",
"emailAddress": "customer@example.test"
}
The flat object contains only selected properties. The service rejects duplicate selected parameter names across groups because the object cannot represent them safely. Onboarding does not echo unchanged profile values.
9. Resumability and reconciliation
Every successful status request compares current NewGen requirements with local customer-update state.
| Condition | Result |
|---|---|
| Requirements are unchanged | Reuse the existing instance and avoid a write. |
| A requirement is satisfied elsewhere | Remove its field. If no fields remain, mark the journey superseded. |
| A requirement is added to an active journey | Add its configured field and invalidate review. |
| A draft conflicts with NewGen | NewGen wins. Preserve drafts only for required and editable parameters whose current value remains empty. |
| A completed requirement appears again | Create a new instance and retain the earlier completion history. |
| The journey is processing | Freeze its submitted payload. New requirements belong to a later instance. |
Journey and form versions freeze when the customer starts. The selected fields can change within those versions after re-resolution. Another channel receives the same catalog and resumes the active instance through current-step.
10. Progress
The API returns a fractional percentage such as 0.5. Baseline steps are the steps the customer traversed in the persisted onboarding instance. Every journey step counts except a status page. Review, OTP, and confirmation count.
Re-resolution can add required steps and reduce the percentage. A requirement satisfied through another channel counts as externally completed. When no gaps remain, status returns complete, progress 1.0, and an empty journey list.
11. Failures and HTTP behavior
| Status | Use |
|---|---|
200 | Successful read, local step operation, or core-confirmed submission. |
202 | Submission still processing, including a duplicate request. |
400 | Malformed request. |
401 | Missing or invalid token. |
404 | Unknown or unowned journey or step. |
409 | Invalid lifecycle transition, including an attempt to edit verified email. |
422 | Form validation failure. |
500 | Unexpected server or configuration failure. |
503 | NewGen or a core system is unavailable. |
If NewGen is unavailable, the API may return the previous catalog with overall status temporarilyUnavailable. Its items use availability temporarily_unavailable. The customer cannot start, resume, edit, save, or submit until the authoritative systems recover. If no previous catalog exists, status fails.
A normal final submission waits for core confirmation. If the request window closes first, the API returns 202 with a stable submission reference and status URL. Polling and NewGen callbacks update the same durable state. Core-confirmed success is final.
12. Storage and scale
Status calls do not create records when no local state changes. The service does not retain full NewGen responses. It stores only data needed for ownership, resumability, validation, progress, and submission reconciliation:
- Customer key, journey identity, versions, current step, and lifecycle.
- Resolved field allowlists and customer drafts.
- Baseline and enrichment progress counters.
- Submission, NewGen, and trace references.
- Journey outcomes and timestamps.
Completed cycles may be compacted to counters and audit references. The retention period for a previous catalog during outages is configurable.
13. Current implementation gaps
The ADRs remain the source of truth for the target design. Milestone 1 is complete with a versioned NewGen fixture, contract checks, form mappings, journey configuration, and clean-database version retention. Implementation work must still address these differences:
- The existing profile endpoint returns one preferred-address form and a generated ID that is not persisted.
- The configured customer-update journey is not yet selected from a live NewGen assessment or exposed through the authenticated customer-update routes.
- The generic onboarding routes are anonymous and do not enforce customer ownership.
- The current service marks a journey submitted only after the NewGen call, so it does not prevent concurrent external submissions.
- The current review screen can read prior submissions but cannot replace a validated earlier step.
- The current progress calculation counts validated main-flow submissions and does not implement the customer-update formula.