Source: https://docs.debitura.com/referral-partners/integration-guides/client-onboarding

# Client Onboarding

Create and link clients (creditors) to your referral partner account. **Full Integration.** With the [Starter Integration](https://docs.debitura.com/referral-partners/integration-guides/starter-integration.md), `POST /collection-requests` creates or finds the client for you.

<a id="client-lifecycle"></a>

## Client lifecycle

| Code | Meaning | Next step |
| --- | --- | --- |
| **201** | Client ready (SDCA signed) | Mint a token, submit cases |
| **202** | Onboarding required (SDCA not signed, or needs re-signing) | Send the user to `onboardingLinks.url` |
| **409** | Client already exists, or an identical keyed request is still running | Handle by `type`, or `businessErrors[0].type` for `IdempotencyKeyInProgress` |

-   **Idempotent on `externalTenantId`.** A repeat call returns the existing client state and creates no cases: every case in it is reported as `IdempotencyViolation`. Submit later cases through the Customer API with a bearer token.
-   **Existing clients are matched by exact email address** (any supplied user email or the support email). Company-domain matching is not used.
-   **Several brands under one parent** use Divisions: one client account, several divisions. See [Client Divisions](https://docs.debitura.com/clients/concepts/client-divisions.md).
-   **Withdrawal** archives the link and closes all pending cases; see [Client Withdrawal](https://docs.debitura.com/referral-partners/integration-guides/client-onboarding.md#client-withdrawal).

<a id="overview"></a>

## Overview

Before submitting cases on behalf of a client, you must create or link them in Debitura. This establishes the relationship between your platform, the client, and Debitura's collection network.

**The flow:**

1.  Call `POST /clients` with your `externalTenantId`
2.  Handle the response (201, 202, or 409)
3.  If onboarding is required, present the onboarding URL to your client
4.  Once onboarding is complete, mint a bearer token and submit cases

<a id="create-or-link-a-client"></a>

## Create or Link a Client

```http
POST https://referral-api.debitura.com/clients
XApiKey: YOUR_API_KEY
Content-Type: application/json

{
  "externalTenantId": "your-unique-client-id",
  "client": {
    "name": "Acme Corp",
    "registrationNumber": "12345678",
    "country": "GB",
    "address": "123 Business Street",
    "city": "London",
    "zipCode": "EC1A 1BB",
    "supportEmail": "support@acme.com"
  },
  "users": [
    {
      "email": "admin@acme.com",
      "name": "Jane Smith",
      "isOnboardingUser": true
    }
  ]
}
```

<a id="required-fields"></a>

### Required Fields

| Field | Description |
| --- | --- |
| `externalTenantId` | Your unique identifier for this client. Used for all future operations. |
| `client.name` | Legal company name |
| `client.country` | ISO 3166-1 alpha-2 code (e.g. `"GB"`) or full English country name |
| `client.supportEmail` | Support email address for the client. Required. |
| `users` | At least one user. For multi-user requests, exactly one must have `isOnboardingUser: true`. Single-user requests auto-infer the onboarding user. |
| `users[].name` | Full name of the user (single field, not split into first/last) |
| `users[].email` | User's email address (validated format) |

<a id="how-client-detection-works"></a>

## How Client Detection Works

When you call `POST /clients`, Debitura checks if the client already exists by matching email addresses from your request against existing users and creditors.

**All emails are matched by exact email address only.** Company-domain matching is intentionally disabled for this endpoint to avoid false-positive matches. `alice@acme.com` matches only `alice@acme.com` — it will **not** match `bob@acme.com`. This applies to both company and generic-provider emails (the supplied user emails and the client's support email).

<a id="decision-flow"></a>

### Decision Flow

```text
POST /clients
│
├─ Same externalTenantId already linked?
│  YES → Return existing client (201 or 202); no cases created
│
├─ Email exactly matches existing Debitura client?
│  │
│  ├─ That client linked to DIFFERENT partner?
│  │  YES → 409 ClientAlreadyLinkedToAnotherPartner
│  │
│  └─ Not linked yet?
│     YES → 409 ClientExistsNeedsLinking (approval required)
│
└─ No match found → Create new client (201 or 202)
```

<a id="multi-brand-organizations"></a>

## Multi-Brand Organizations

One User Can Manage Multiple Clients

A Debitura user can have relations to multiple client (creditor) accounts. Reusing an existing user does not require merging otherwise separate businesses.

If your customer has multiple brands or legal entities under one parent, use **Divisions** — one client account with multiple divisions for case attribution. See [Client Divisions](https://docs.debitura.com/clients/concepts/client-divisions.md).

If they are completely separate businesses, each needs its own client account with a unique `externalTenantId`. The same user can manage multiple businesses: for the same referral partner, a new external tenant can create a separate creditor while reusing that user's identity. Do not assume this always produces `409 ClientExistsNeedsLinking`. See [multi-entity flow](https://docs.debitura.com/referral-partners/integration-guides/client-onboarding-conflicts.md#multi-entity-same-user-multiple-companies) for details.

<a id="response-handling"></a>

## Response Handling

<a id="201-created--sdca-onboarding-complete"></a>

### 201 Created — SDCA Onboarding Complete

The client's current SDCA-based onboarding check is complete. You can [mint a token](https://docs.debitura.com/referral-partners/getting-started/authentication.md#bearer-tokens-full-integration), but a future case can still require partner- or jurisdiction-specific PoA, JPA, or KYC before it proceeds.

<a id="202-accepted--onboarding-required"></a>

### 202 Accepted — Onboarding Required

The client was created, but its SDCA-based onboarding check is incomplete. An active link can already mint a bearer token, and a case sent with `allowPendingContracts: true` can enter `PendingContractSigning` before onboarding finishes. Other case-specific requirements can still block submission.

```json
{
  "externalTenantId": "your-unique-client-id",
  "onboardingDone": false,
  "onboardingLinks": {
    "url": "https://referral.debitura.com/onboarding/companydetails/..."
  }
}
```

**Important:** `POST /clients` suppresses the immediate standard creditor welcome email, so you must present the onboarding URL to your client (in-app, email, redirect). After the client successfully signs through the referral onboarding UI, Debitura automatically sends welcome or activation emails to the users. While production cases wait for the client's signature, Debitura also sends contract-signing reminders; see [White-Label UI](https://docs.debitura.com/referral-partners/concepts/white-label-ui.md).

<a id="409-conflict--client-already-exists"></a>

### 409 Conflict — Client Already Exists

A `409` indicates an onboarding conflict: an existing account/link or user identity conflict, a referral partner that is not accepting new referrals, or an in-progress keyed request. Inspect `type` or `businessErrors[]` as appropriate:

| Type | Meaning | Action |
| --- | --- | --- |
| `ClientExistsNeedsLinking` | User has a Debitura account, not linked to you | Present the approval URL — user chooses to link existing or create new |
| `ClientAlreadyLinkedToAnotherPartner` | Linked to a different partner | Contact Debitura support |
| `UserCreationFailed` | Unexpected onboarding-user identity conflict while creating a new client | Retry only after correcting the user identity; contact Debitura if it persists |
| `PartnerNotAcceptingReferrals` | The referral partner is not accepting new referrals | Contact Debitura support |

These are the four conflict types currently returned in `type` by `POST /clients`. With an `Idempotency-Key`, a retry while the first request is still running returns `409` with `businessErrors[0].type: IdempotencyKeyInProgress` and `Retry-After: 2` instead. An account that is not a creditor (e.g. a collection-partner user) is never matched. If its email is submitted as a user email, the request fails with `409 UserCreationFailed`; if it is only the `supportEmail`, a new client is created. See [InvalidClientType](https://docs.debitura.com/referral-partners/integration-guides/client-onboarding-conflicts.md#invalidclienttype).

For `ClientExistsNeedsLinking`, the response includes an `onboardingLinks.url`. Present this URL to the user — they'll see a choice page where they can connect an existing Debitura account or set up a new one. After either choice, you receive a `client.linked` webhook and the client is ready.

**Full guide:** See [Handling 409 Conflicts](https://docs.debitura.com/referral-partners/integration-guides/client-onboarding-conflicts.md) for the complete flow — what the user sees, the two resolution paths, how to create cases after the conflict is resolved, multi-entity scenarios, and revenue attribution impact.

<a id="adding-a-return-url"></a>

## Adding a Return URL

Append a return URL to redirect clients back to your platform after onboarding:

```text
https://referral.debitura.com/onboarding/companydetails/abc123?returnUrl=https://yourapp.com/onboarding-complete
```

URL Encoding

If your return URL contains query parameters, URL-encode it first:

```javascript
const returnUrl = encodeURIComponent('https://yourapp.com/done?clientId=123');
const onboardingUrl = `${baseUrl}?returnUrl=${returnUrl}`;
```

After successful completion of the signing or approval flow, Debitura appends `completed=true` to your returnUrl before redirecting (e.g. `https://yourapp.com/onboarding-complete?completed=true`). If the user abandons the flow, the redirect happens without this parameter — allowing you to distinguish a completed flow from an abandoned one.

See [White-Label UI](https://docs.debitura.com/referral-partners/concepts/white-label-ui.md) for customization options.

<a id="including-cases"></a>

## Including Cases

You can include cases in the `POST /clients` request. Cases are validated and created alongside the client.

-   **Before the client is created**, every case is checked. Invalid case fields (for example a `creditorReference` repeated within the request, claim-line or age-bucket errors, an unsupported debtor country, an invalid debtor `type`, or a missing debtor address or city) return `400`, and a case below the minimum claim amount returns `422` with `AmountBelowMinimum`. In both cases nothing is created.
-   **After the client is created**, each case either appears in `caseResults.successfulCases` or in `caseResults.failedCases` with an `errorType` such as `NoPartnerAvailable`, `CreditorBlocked`, `DuplicateReference`, `ValidationError` or `UnexpectedError`. The response is still `201` or `202`.

Embedded cases accept all three modes: a single due date, cumulative age buckets, or `claimLines[]` (max 1,000 lines per case and 1,000 across all cases in the request). Use [Claim Amount and Age](https://docs.debitura.com/developer-concepts/claim-amount-and-age.md) to choose between them and understand where invoice-level claim lines are available.

Client vs Debtor

The **client** object (your customer) has minimal required fields. The **debtor** object in each case has strict validation—see [debtor requirements](https://docs.debitura.com/clients/integration-guides/create-a-case.md#debtor-object-required-fields).

If the response is `409 ClientExistsNeedsLinking`, the cases you submitted are **persisted server-side** and replayed against the creditor on the resolved client link (the selected existing account or newly created account) automatically once the user approves the link. `AllowPendingContracts=true` is forced on replay, so missing SDCA or PoA can place a case in `PendingContractSigning`. KYC is evaluated separately and does not itself force that lifecycle on the replay path. Failed replayed cases are reported via the `cases.replay_failed` webhook (one per failed case, with `creditorReference` and `failureReason`); a failed case is never created, so `lifecycle` cannot show it. See [Handling 409 Conflicts](https://docs.debitura.com/referral-partners/integration-guides/client-onboarding-conflicts.md) for the full flow.

<a id="after-onboarding"></a>

## After Onboarding

Once onboarding is complete:

1.  **Mint a bearer token** — See [Authentication](https://docs.debitura.com/referral-partners/getting-started/authentication.md#bearer-tokens-full-integration)
2.  **Submit cases** — See [Create a Case](https://docs.debitura.com/clients/integration-guides/create-a-case.md) (uses the Customer API)
3.  **Track progress** — See [Webhooks](https://docs.debitura.com/referral-partners/integration-guides/webhooks.md) for real-time updates

<a id="handling-abandoned-onboarding"></a>

## Handling Abandoned Onboarding

If a user starts onboarding but doesn't complete it (closes browser, etc.):

1.  Call `POST /clients` again with the same `externalTenantId`
2.  You'll receive **202 Accepted** with the existing link's deterministic onboarding URL
3.  Redirect the user to continue where they left off

The API is idempotent—repeated calls return the existing client, not an error.

202 on retry, not 409

If you created the client (got 202), retrying the active link returns **202 again**. A 409 usually reflects an existing-account/link conflict, but `UserCreationFailed` can also occur while a new client is being created.

See [Client lifecycle](https://docs.debitura.com/referral-partners/integration-guides/client-onboarding.md#client-lifecycle) for the summary.

<a id="error-handling"></a>

## Error Handling

| Error | Cause | Solution |
| --- | --- | --- |
| `400 Bad Request` | Invalid or missing fields, or invalid embedded cases | Check required fields and data formats |
| `401 Unauthorized` | Invalid API key | Verify your `XApiKey` header |
| `409 ClientExistsNeedsLinking` | Client exists in Debitura | Present approval URL to client (configurable per-partner TTL, default 7 days, max 30) |
| `409 ClientAlreadyLinkedToAnotherPartner` | Client linked to different partner | Contact Debitura support |
| `409 UserCreationFailed` | Onboarding-user identity conflict during new-client creation | Correct the user identity or contact Debitura support |
| `409 PartnerNotAcceptingReferrals` | Referral partner is paused or closed to new clients | Contact Debitura; if a paused account is reactivated, retry the same request and `Idempotency-Key` |
| `409` with `IdempotencyKeyInProgress` | Same `Idempotency-Key` request still running | Wait for `Retry-After` (2 seconds) and retry |
| `422 AmountBelowMinimum` | An embedded case is below the minimum claim amount | Remove or correct the case |
| `422 IdempotencyConflict` | `Idempotency-Key` reused with a different request | Use a new key for a different request |

See [Error Handling](https://docs.debitura.com/developer-concepts/error-handling.md) for the full error response format.

<a id="client-withdrawal"></a>

## Client Withdrawal

To offboard a client and remove their link to your referral partner account:

```http
POST https://referral-api.debitura.com/clients/{externalTenantId}/withdraw
XApiKey: YOUR_API_KEY
```

**Requirements:**

-   Only **attributed clients** (`IsAttributedClient = true`) can be withdrawn. Non-attributed clients return `409`.
-   All cases must be in `PendingContractSigning` status. If any case has progressed further, returns `409`.

**What happens:**

1.  Validates the client is attributed and all cases are in `PendingContractSigning`
2.  Closes all pending cases with the `CaseNeverStartedInternal` close code
3.  Archives the referral partner client link

Returns `204 No Content` on success. Returns `404` if no active link exists. Returns `409 Conflict` if the client is non-attributed or cases have progressed beyond onboarding.

Withdrawal archives the link and closes the pending cases. It does **not** delete the client — their Debitura account, users, KYC record and signed contracts all survive. They simply stop being attributed to you.

Archiving the link also **revokes everything that link authorised**. Bearer tokens can no longer be minted for that `externalTenantId` (`POST /oauth/token` answers `404`), and any onboarding, signing, Power of Attorney or KYC URL you had previously issued for the client stops working — the client sees the generic "Invalid or Missing Link" page instead. See [Revoked Links](https://docs.debitura.com/referral-partners/concepts/white-label-ui.md#revoked-links).

<a id="re-submitting-a-withdrawn-client"></a>

### Re-submitting a withdrawn client

You can bring a withdrawn client back by calling `POST /clients` again with the same `externalTenantId`. This **re-links** them and creates the cases in your payload as normal — you do not get the `ClientExistsNeedsLinking` 409 and your client is not asked to approve anything.

Re-linking applies when all of the following hold:

-   the same `externalTenantId`, submitted by the same referral partner
-   the previous link was an attributed one
-   the client Debitura resolves is the same client the archived link pointed at
-   that client currently has no active link to any referral partner

If any of these conditions does not hold, normal client detection applies: an active link to another partner returns `ClientAlreadyLinkedToAnotherPartner`; other existing-account conflicts can return [`ClientExistsNeedsLinking`](https://docs.debitura.com/referral-partners/integration-guides/client-onboarding-conflicts.md#clientexistsneedslinking); unrecognized contact emails can create a new client.

Terms are re-snapshotted

A re-link creates a **new** link, not a revival of the old one, so the withdrawal remains in the audit trail. Your attribution carries over. On the Contract plan, the new link snapshots your **current** agreed percentage rather than the archived link's rate. New cases record their rate under the applicable plan; Link tiers and the Integration payout allowance follow the [case referral rules](https://docs.debitura.com/referral-partners/concepts/attribution.md#fee-percentage-snapshot). Re-linking does not reprice existing cases.

The relink itself does not emit `client.linked`. If relinking supersedes a pending link request, that cleanup can emit `client.link_declined`; you will also receive `case.created` events for submitted cases.
