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
UserCreationFailedUnexpected onboarding-user identity conflict during new-client creation — e.g. the email belongs to a non-creditor accountCorrect the user identity or contact Debitura support

These are the three conflict types the API returns. A matched account that is not a creditor surfaces differently — see InvalidClientType below.

How Debitura detects existing clients​

Debitura matches the submitted email addresses against existing accounts using exact-email matching — domain-suffix matching is intentionally disabled for this endpoint. 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.

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 normally false — the user existed in Debitura before your referral. Exception: if your partnership previously held an attributed link to that client that was since archived (withdrawn), attribution is preserved (true). 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 JPA are not yet signed at the time of approval, cases enter the Pending contract signing lifecycle state instead of failing with a 422. KYC is not checked on replay, so missing KYC neither blocks the case nor puts it in Pending contract signing. They advance automatically once the client signs. You receive case.created webhooks for each successful case; failures are reported through the cases.replay_failed webhook, one per failed case (a failed case is never created, so it has no lifecycle). 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.

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 JPA are not yet signed at replay time, cases enter the Pending contract signing lifecycle state instead of failing with a 422. KYC is not checked on replay, so missing KYC neither blocks the case nor puts it in Pending contract signing. They automatically advance once the client signs. You receive case.created webhooks for each successful case; failures are reported through the cases.replay_failed webhook, one per failed case (a failed case is never created, so it has no lifecycle). Cases whose creditorReference already exists for this client link are skipped silently — no duplicate is created and no cases.replay_failed webhook is sent. 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

POST /clients is an onboarding endpoint only. Once a link exists for the externalTenantId, re-calling POST /clients does not create or retry cases. Every case in the request is reported as a failure with errorType: "IdempotencyViolation" (message "Client already exists; cases not created on repeated call") and the case-creation pipeline is never run — nothing is created.

The cases included in your original onboarding request are still created automatically when the link is established (see Path A and Path B above). But to add new cases or retry failed ones after the link exists, switch to the bearer-token Customer API flow documented in case submission. Do not re-call POST /clients to submit cases — it will silently report every case as an IdempotencyViolation and create none.

A repeat call returns 201 or 202 (depending on SDCA status) with all submitted cases listed under caseResults.failedCases:

{
"externalTenantId": "your-tenant-id",
"caseResults": {
"successfulCases": [],
"failedCases": [
{
"caseIndex": 0,
"creditorReference": "INV-001",
"errorType": "IdempotencyViolation",
"errorMessage": "Client already exists; cases not created on repeated call"
}
]
}
}

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 does not return a 409 when the user's only existing account is Company A, already linked to you under tenant-a. Company B is created directly (201/202), the existing user is reused, and Company B has isAttributedClient: true. A 409 ClientExistsNeedsLinking (and false via "Set up a new account") happens only if the email matches some other creditor not linked to you.

After Company B is created, the user 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)tenant-btrue

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 at contact@debitura.com 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​

InvalidClientType is defined in the conflict-type enum but is not currently returned by POST /clients. A non-creditor account (e.g. a collection-partner user) is never matched by client detection. If its email is submitted as a user email, the request fails with 409 UserCreationFailed; if only the supportEmail matches, a new client is created.

Attribution for 409 flows​

409 resolution paths normally set isAttributedClient = false — meaning you earn referral fees only on cases submitted through your API, not on cases the client creates directly. Exception: on "Connect to existing account", if your partnership previously held an attributed link to that client that was since archived (withdrawn), attribution is preserved (true). 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.