Skip to content

Onboarding

Two ways to get a client from prospect to signed. They produce identical outcomes and identical webhooks — the only difference is who renders the screens.

You build the screens, push each section to us, and submit. We validate, generate the financing contract, and hand back a signing link for you to redirect to.

Best when you already hold most of this data and want the flow entirely inside your own product.

Use the onboarding.url returned when you create the client. The flow carries your logo, colours, and copy; users stay on a page that looks like yours end to end.

Best when you want zero screens to build and no KYC field maintenance as rules change.

Either way, signing works the same

Every signatory must sign. When the person filling the form is one of them, they sign in-session and we return a redirect URL. Otherwise everyone signs via emailed links and we return {"method": "email"}.

Both paths finish with the same client.onboarding_completed webhook.

Signing also records the client's consent to share financial data, so a client onboarded this way needs no separate consent object.


Six sections. Push them in any order; the application is created on the first push. PUT is a full overwrite of that section, so it is safe to resend as the user edits.

Push a section

PUT /clients/{countryCode}/{registrationNumber}/onboarding/steps/{step} Partner writes
Step Carries
applicant firstName, lastName, email, phone
company officialName, tradeName, registeredAddress, mainIndustryCode, vatNumber (BTW), iban
offer invoiceAmount, paymentTermDays (1–365), financingPercentage (1–100)
signatories userIsSignatory, plus 1–20 signatories with dateOfBirth, role, canSignAlone
ownership UBOs (max 20), senior managing officials, activitiesAbroad, taxLiabilities
funding hasExternalFunding and, when true, each funding's financierType, sourceType, amount, terms

applicant

{
  "firstName": "Sanne",
  "lastName": "van Dijk",
  "email": "sanne@vandijk-installatie.nl",
  "phone": "+31612345678"
}

company

{
  "officialName": "Van Dijk Installatietechniek B.V.",
  "tradeName": "Van Dijk Installatie",
  "registeredAddress": {
    "street": "Keizersgracht",
    "houseNumber": "241-B",
    "postalCode": "1016 EA",
    "city": "Amsterdam",
    "country": "NL"
  },
  "mainIndustryCode": "4322",
  "vatNumber": "NL861234567B01",
  "iban": "NL91ABNA0417164300"
}

offer

{
  "invoiceAmount": { "amount": 424250, "currency": "EUR" },
  "paymentTermDays": 30,
  "financingPercentage": 80
}

signatories

{
  "userIsSignatory": true,
  "signatories": [{
    "firstName": "Sanne",
    "lastName": "van Dijk",
    "email": "sanne@vandijk-installatie.nl",
    "phone": "+31612345678",
    "dateOfBirth": "1987-04-19",
    "role": "director",
    "canSignAlone": true
  }]
}

role is one of director, authorized_representative, other. Every listed signatory becomes a required signer on the contract.

ownership

{
  "ubos": [{
    "firstName": "Sanne",
    "lastName": "van Dijk",
    "dateOfBirth": "1987-04-19",
    "nationality": "NL",
    "countryOfResidence": "NL",
    "ownershipPercentage": 100,
    "natureOfControl": "ownership",
    "isPEP": false
  }],
  "seniorManagingOfficials": [],
  "activitiesAbroad": { "countries": ["BE", "DE"] },
  "taxLiabilities": [
    { "country": "NL", "taxNumber": "NL861234567B01" }
  ]
}

natureOfControl is one of ownership, voting, other. When no UBO reaches the ownership threshold, send an empty ubos array and list seniorManagingOfficials instead — that is the documented fallback and it is expected, not an error.

funding

{
  "hasExternalFunding": true,
  "fundings": [{
    "financierType": "company",
    "sourceType": "loan",
    "name": "Rabobank",
    "registrationNumber": "30046259",
    "amount": { "amount": 5000000, "currency": "EUR" },
    "terms": "Business loan, 5 year term"
  }]
}

Send {"hasExternalFunding": false} when there is none. The section is still required.

Response · 200

{
  "countryCode": "NL",
  "registrationNumber": "68123456",
  "step": "ownership",
  "updatedAt": "2026-09-18T13:41:22Z"
}

Submit

POST /clients/{countryCode}/{registrationNumber}/onboarding/submit Partner writes

Runs cross-section validation, creates the financing contract, and sends it for signature. Idempotent — replaying returns the same signing instruction rather than a second contract.

Applicant signs in-session
{
  "submittedAt": "2026-09-18T13:58:11Z",
  "signing": {
    "method": "redirect",
    "url": "https://scrive.com/s/9f21a.../sign",
    "expiresAt": "2026-09-25T13:58:11Z",
    "signerCount": 1
  }
}
Signatories sign by email
{
  "submittedAt": "2026-09-18T13:58:11Z",
  "signing": {
    "method": "email",
    "signerCount": 2
  }
}

When method is redirect, send the user to url. When it is email, tell them to check their inbox — there is nothing to redirect to.

Errors

Status Code When
409 already_submitted Submitted under a different idempotency key.
422 validation_failed A required section is missing or internally inconsistent. fields names each one.
503 signing_unavailable The signing provider is temporarily unreachable. Retry — nothing was consumed.

Mode B — hand off to a white-labelled flow

Redirect the user to the onboarding.url from client creation. It carries your theme, logo, and copy, plus the locale you set. Nothing else is required of you.

  1. Create the client. Keep onboarding.url from the response.
  2. Open it — new tab, or embedded in your own page.
  3. The user completes KYC and signs. You render nothing.
  4. You receive client.onboarding_completed, then client.status_changed when underwriting finishes.

Mint a fresh session

POST /clients/{countryCode}/{registrationNumber}/onboarding/session Partner writes

Use this after a URL expires, or to resume a half-finished application.

Request
{
  "returnUrl": "https://app.partner.example.com/financiering/klaar",
  "language": "nl"
}

language accepts nl, en or fi.

Response
{
  "url": "https://aanvraag.noja.nl/?partnerAccess=p_7f2a91&ref=0193f2a1",
  "expiresAt": "2026-10-14T09:31:04Z",
  "resumesFrom": "signatories"
}

returnUrl sends the user back into your product after signing. resumesFrom tells you which section they stopped at, if you want to set expectations before the redirect.

What you control

Element Configured by
Logo, colours, typography Your brand assets, supplied once at partner setup
Page copy and value propositions You, per-section overrides
Domain aanvraag.noja.nl, or your own subdomain via CNAME
Language Per-session, nl, en or fi
Return destination Per-session returnUrl

Brand assets are configured once during partner setup, not per request.

Check onboarding state

GET /clients/{countryCode}/{registrationNumber}/onboarding Noja writes

Everything we hold for the application, in either mode. Useful for resyncing after a failed push, or for showing progress in your own product while a user is inside the hosted flow.

{
  "state": "submitted",
  "mode": "hosted",
  "steps": {
    "applicant": { "complete": true, "updatedAt": "2026-09-18T13:20:11Z" },
    "company":   { "complete": true, "updatedAt": "2026-09-18T13:28:44Z" },
    "offer":     { "complete": true, "updatedAt": "2026-09-18T13:31:02Z" },
    "signatories": { "complete": true, "updatedAt": "2026-09-18T13:44:19Z" },
    "ownership": { "complete": true, "updatedAt": "2026-09-18T13:51:37Z" },
    "funding":   { "complete": true, "updatedAt": "2026-09-18T13:55:08Z" }
  },
  "submittedAt": "2026-09-18T13:58:11Z",
  "signing": {
    "state": "partially_signed",
    "provider": "scrive",
    "signedCount": 1,
    "signerCount": 2
  }
}
state Meaning
draft Application open — sections may be incomplete
submitted Cross-section validation passed, signing started
signed Every signatory signed; the financing contract is sealed
signing_failed Signing cancelled, declined, or expired

There is no state for "client exists but has no application" — the endpoint returns 404 until the first section is pushed or the hosted flow is opened.

From signing_failed you can mint a fresh session and the client resumes from where they stopped; the application returns to draft.