Skip to main content

Handling 409 Conflicts

What happens when POST /clients returns 409 — and how to handle each scenario.

This guide assumes you've read Client Onboarding.

Overview

A 409 Conflict means the submitted user already exists in Debitura. The type field tells you which scenario you're in:

TypeWhat it meansWhat to do
ClientExistsNeedsLinkingUser has a Debitura account, not linked to youPresent the approval URL — user decides what to do
ClientAlreadyLinkedToAnotherPartnerUser is linked to a different referral partnerHard block — contact Debitura support
InvalidClientTypeThe matched account is not a creditor (e.g. a collection partner)Contact Debitura support

How Debitura detects existing clients

Debitura matches the submitted email addresses against existing accounts using domain-based and exact-email matching. If a match is found and the user isn't already linked to your partnership, you get a 409 with an approval URL.

ClientExistsNeedsLinking

{
"type": "ClientExistsNeedsLinking",
"message": "Client already exists in Debitura. Approval required to link to referral partner.",
"data": {
"externalTenantId": "your-tenant-id",
"onboardingLinks": {
"url": "https://app.debitura.com/ReferralPartner/Approve?token=abc123..."
},
"isAttributedClient": false
}
}

Present onboardingLinks.url to the user — as with all referral partner flows, Debitura does not send emails, so you are responsible for delivering this URL.

Approval URLs expire after a configurable per-partner TTL (ApprovalTtlDays, default 7 days, clamped 1–30 days). Subscribe to client.link_expired to detect expiry; resubmit POST /clients with the same externalTenantId to generate a fresh approval URL.

You receive a client.link_requested webhook immediately when the 409 fires — this confirms the approval URL has been issued. If the user doesn't act, you receive a client.link_expired webhook when the link request expires.

:::warning No information about the matched account is returned For privacy, the 409 response does not reveal any details about the user's existing Debitura account. You only receive back your own externalTenantId and the approval URL.

The response body includes client (an empty object) and users (an empty array) for JSON-shape stability — do not rely on either being populated. Emails of the matched account are never returned. :::

What the user sees

When the user follows the approval URL, they log in and see a choice page with up to two options:

"Connect to existing account" — shown when the user manages a Debitura creditor that is not already linked to your partnership. The user can select which existing account to link.

"Set up a new account" — always available. Creates a fresh creditor from the company details you submitted in the original request.

If all of the user's existing accounts are already linked to you (the multi-entity scenario), only "Set up a new account" is shown.

Path A: User connects an existing account

The user selects one of their existing creditor accounts to link to your partnership.

  1. A ReferralPartnerClientLink is created for the existing creditor (the link points at the creditor the user picked — not a brand-new one)
  2. You receive a client.linked webhook
  3. isAttributedClient is false — the user existed in Debitura before your referral. See Attribution for how this affects revenue.

Cases from your original POST /clients payload are created automatically on this path immediately after the user approves the link. AllowPendingContracts=true is forced on replay regardless of what you sent on the original request — so if the SDCA, PoA, or KYC are not yet signed at the time of approval, cases enter the Pending contract signing lifecycle state instead of failing with a 422. They advance automatically once the client signs. You receive case.created webhooks for each successful case; there is no case.failed webhook on this path — detect failures via the case lifecycle field. Other case creation failures (e.g. duplicate creditorReference) are reported per-case but do not block the link.

After approval, the user is directed to the signing flow to complete any outstanding contracts.

:::note Concurrent approval requests If two approval requests for the same client arrive simultaneously, only one succeeds. The second returns a 409 indicating the link request has already been processed. This is expected — you will receive a single client.linked webhook. :::

Path B: User creates a new account

The user creates a fresh creditor account from the company details you originally submitted.

  1. A new creditor is created from the stored company details
  2. The user is added as admin and redirected to sign the SDCA
  3. You receive a client.linked webhook
  4. Any cases included in your original POST /clients payload are replayed against the new creditor from the stored request (same validation pipeline as first-time creation). AllowPendingContracts=true is forced on replay regardless of what you sent on the original request — so if the SDCA, PoA, or KYC are not yet signed at replay time, cases enter the Pending contract signing lifecycle state instead of failing with a 422. They automatically advance once the client signs. You receive case.created webhooks for each successful case; there is no case.failed webhook on this path — detect failures via the case lifecycle field. Cases whose creditorReference already exists are reported as failures — no duplicates. Case creation failures do not block the account creation; the link is already persisted.
  5. isAttributedClient is false — because the user already existed in Debitura, even though the creditor account is new

Whenever you want to add cases to an existing link — on Path B to retry failures, or later on to add new work — call POST /clients again with the same externalTenantId. On Path A, the originally-submitted cases are created automatically at approval time; use this endpoint to retry any that failed or to add new cases later. The endpoint detects the existing link and runs the case creation pipeline against the already-linked creditor.

  • Cases with a creditorReference that already exists on the creditor are reported in caseResults.failedCases with errorType: "DuplicateReference" — no duplicates are created
  • New cases are created normally on the existing client
  • Duplicate creditorReference values within a single request are rejected upfront with 400 Bad Request

This idempotent retry path is safe to call repeatedly and is the recommended way to recover from any partial case creation. For ongoing case creation after onboarding is complete, switch to the bearer-token flow documented in case submission.

POST /clients
XApiKey: YOUR_API_KEY
Content-Type: application/json

{
"externalTenantId": "your-tenant-id",
"client": { ... },
"users": [ ... ],
"cases": [
{
"creditorReference": "INV-001",
"currencyCode": "EUR",
"amountToRecover": 3500.00,
"date": "2026-03-01",
"dueDate": "2026-04-01",
"debtor": { ... }
}
]
}

The response is 201 or 202 (depending on SDCA status) with caseResults showing which cases were created.

Multi-entity: same user, multiple companies

A common real-world scenario: your user manages Company A (already onboarded through your platform) and now wants to add Company B.

Calling POST /clients with a new externalTenantId but the same user email returns 409 ClientExistsNeedsLinking. Since Company A is already linked to you, the choice page only shows "Set up a new account" for Company B.

After the user creates the new account, they can switch between their companies in the Debitura portal. Both companies are linked to your partnership, each with its own externalTenantId:

CompanyexternalTenantIdisAttributedClient
Company A (first onboarded)tenant-atrue
Company B (added later via 409 flow)tenant-bfalse

Each company has independent cases, contracts, and revenue tracking. Use the externalTenantId to distinguish them in your system.

ClientAlreadyLinkedToAnotherPartner

The user's Debitura account is already linked to a different referral partner. Only one partner link per creditor is allowed.

{
"type": "ClientAlreadyLinkedToAnotherPartner",
"message": "This client is already linked to another referral partner. Only one referral partner link per client is currently supported. Please contact Debitura support for assistance.",
"data": {
"externalTenantId": "your-tenant-id"
}
}

This is a hard block. Contact partnerships@debitura.com to discuss resolution.

The data object echoes your externalTenantId for request correlation only — no information about the other partner or the matched creditor is returned.

InvalidClientType

The matched Debitura account is not a creditor — for example, it's a collection partner account. Contact partnerships@debitura.com to resolve.

Attribution for 409 flows

All 409 resolution paths set isAttributedClient = false — meaning you earn referral fees only on cases submitted through your API, not on cases the client creates directly. The flag and the fee percentage are locked at link creation time and never change.

Business context: See Referral Program Overview for the full revenue model. For the technical details of attributed vs non-attributed, see Attribution.