> ## Documentation Index
> Fetch the complete documentation index at: https://docs.paywise.de/llms.txt
> Use this file to discover all available pages before exploring further.

# Hosted onboarding

> Let the client complete hosted setup or connect an existing company, then retrieve its current data and access.

## Outcome

The client completed the hosted browser flow, your verified
receiver captured a `company.access.confirmed` event (or
`company.access.restored` when reconnecting), and your integration
reconciled that event's exact company UUID, current access/readiness, and every
membership page.

## Lifecycle context

Browser navigation is not an API request and has no language tabs. Treat its
return status only as a signal. `company.created` is not completion; hosted
completion is the verified `company.access.confirmed` event, or
`company.access.restored` for a reconnection. Webhook order is not current
state, so always refetch
the exact Company afterward.

## 1. Verify the exact completion subscription

Use the retained webhook UUID. Require an enabled subscription that contains
`company.access.confirmed` and `company.access.restored`, or contains only the
runtime wildcard `*`.

<CodeGroup>
  ```python Python theme={null}
  import requests

  PAYWISE_API_URL = "https://api-sandbox.paywise.de"
  PAYWISE_PARTNER_KEY = "pw_sbx_your_partner_key"
  PAYWISE_WEBHOOK_ID = "90000000-0000-4000-8000-000000000001"

  response = requests.get(
      f"{PAYWISE_API_URL}/partner/v2/webhooks/{PAYWISE_WEBHOOK_ID}/",
      headers={"Authorization": f"Bearer {PAYWISE_PARTNER_KEY}"},
      timeout=(5, 30),
  )
  if response.status_code != 200:
      response.raise_for_status()
      raise RuntimeError(f"Expected 200, received {response.status_code}")
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/partner/v2/webhooks/${process.env.PAYWISE_WEBHOOK_ID}/`, {
    method: "GET", headers: { Authorization: `Bearer ${process.env.PAYWISE_PARTNER_KEY}` }, signal: AbortSignal.timeout(30000),
  });
  if (response.status !== 200) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  ```

  ```java Java theme={null}
  import java.io.IOException;
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.time.Duration;
  class HostedOnboardingRequest {
    public static void main(String[] args) throws IOException, InterruptedException {
      String url = System.getenv("PAYWISE_API_URL") + "/partner/v2/webhooks/" + System.getenv("PAYWISE_WEBHOOK_ID") + "/";
      HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
      HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(30))
          .header("Authorization", "Bearer " + System.getenv("PAYWISE_PARTNER_KEY")).GET().build();
      HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
      if (response.statusCode() != 200) throw new IOException(response.body());
    }
  }
  ```

  ```csharp C# theme={null}
  using System;
  using System.Net.Http;
  using System.Threading;
  using System.Threading.Tasks;
  class HostedOnboardingRequest {
      static async Task Main() {
          using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
          using var request = new HttpRequestMessage(HttpMethod.Get, $"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/partner/v2/webhooks/{Environment.GetEnvironmentVariable("PAYWISE_WEBHOOK_ID")}/");
          request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_PARTNER_KEY"));
          using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
          using var response = await client.SendAsync(request, cancellation.Token);
          if ((int)response.StatusCode != 200) throw new HttpRequestException(await response.Content.ReadAsStringAsync());
      }
  }
  ```

  ```bash cURL theme={null}
  status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
    --request GET "$PAYWISE_API_URL/partner/v2/webhooks/$PAYWISE_WEBHOOK_ID/" \
    --header "Authorization: Bearer $PAYWISE_PARTNER_KEY" \
    --output hosted-webhook.json --write-out '%{http_code}')"
  [ "$status" != "200" ] && exit 1
  ```
</CodeGroup>

## 2. Send the client through the hosted browser flow

Use the Partner-specific hosted entry point and durable correlation supplied
during integration setup. Do not put API credentials in the URL, browser
storage, or return target. Your receiver must verify the signature over the
exact raw event bytes and durably deduplicate the event UUID before the
following resolution step.

### Connect an existing company

For clients who already use paywise, share the connection link using the
partner slug supplied during your partner setup:

```text theme={null}
https://app.paywise.de/account/partner-verbinden/{partner_slug}/
```

The client signs in, selects a company they actively administer, and explicitly
authorizes your partner. The existing company, users, and cases are reused.
If another partner is connected, the client must approve replacing that
connection. This revokes the previous partner's access.

Use the same link to reconnect after disconnection. A new connection emits
`company.access.confirmed`; reconnecting a previously revoked connection emits
`company.access.restored`. Handle either event by retrieving its exact company
UUID, as below.

## 3. Resolve the confirmed company's exact URL

Extract `data.company_id` only from the verified
`company.access.confirmed` or `company.access.restored` event. Then retrieve
that exact UUID; never list companies and choose the first or search by a
mutable business field.

<CodeGroup>
  ```python Python theme={null}
  import requests

  PAYWISE_API_URL = "https://api-sandbox.paywise.de"
  PAYWISE_PARTNER_KEY = "pw_sbx_your_partner_key"
  PAYWISE_COMPANY_ID = "20000000-0000-4000-8000-000000000001"

  response = requests.get(
      f"{PAYWISE_API_URL}/partner/v2/companies/{PAYWISE_COMPANY_ID}/",
      headers={"Authorization": f"Bearer {PAYWISE_PARTNER_KEY}"},
      timeout=(5, 30),
  )
  if response.status_code != 200:
      response.raise_for_status()
      raise RuntimeError(f"Expected 200, received {response.status_code}")
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/partner/v2/companies/${process.env.PAYWISE_COMPANY_ID}/`, {
    method: "GET", headers: { Authorization: `Bearer ${process.env.PAYWISE_PARTNER_KEY}` }, signal: AbortSignal.timeout(30000),
  });
  if (response.status !== 200) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  ```

  ```java Java theme={null}
  import java.io.IOException;
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.time.Duration;
  class HostedOnboardingRequest {
    public static void main(String[] args) throws IOException, InterruptedException {
      String url = System.getenv("PAYWISE_API_URL") + "/partner/v2/companies/" + System.getenv("PAYWISE_COMPANY_ID") + "/";
      HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
      HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(30))
          .header("Authorization", "Bearer " + System.getenv("PAYWISE_PARTNER_KEY")).GET().build();
      HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
      if (response.statusCode() != 200) throw new IOException(response.body());
    }
  }
  ```

  ```csharp C# theme={null}
  using System;
  using System.Net.Http;
  using System.Threading;
  using System.Threading.Tasks;
  class HostedOnboardingRequest {
      static async Task Main() {
          using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
          using var request = new HttpRequestMessage(HttpMethod.Get, $"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/partner/v2/companies/{Environment.GetEnvironmentVariable("PAYWISE_COMPANY_ID")}/");
          request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_PARTNER_KEY"));
          using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
          using var response = await client.SendAsync(request, cancellation.Token);
          if ((int)response.StatusCode != 200) throw new HttpRequestException(await response.Content.ReadAsStringAsync());
      }
  }
  ```

  ```bash cURL theme={null}
  status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
    --request GET "$PAYWISE_API_URL/partner/v2/companies/$PAYWISE_COMPANY_ID/" \
    --header "Authorization: Bearer $PAYWISE_PARTNER_KEY" \
    --output hosted-company.json --write-out '%{http_code}')"
  [ "$status" != "200" ] && exit 1
  ```
</CodeGroup>

Inspect `case_access` and `case_submission_readiness` independently. An
available company can still be unready, and a ready company can still have
unavailable access.

## 4. Read every membership page

The `next` URL is opaque. Follow it only when it is HTTPS, same-origin, unseen,
and within the page budget. Deduplicate stable membership IDs across pages.

<CodeGroup>
  ```python Python theme={null}
  import requests

  PAYWISE_API_URL = "https://api-sandbox.paywise.de"
  PAYWISE_PARTNER_KEY = "pw_sbx_your_partner_key"
  PAYWISE_COMPANY_ID = "20000000-0000-4000-8000-000000000001"

  response = requests.get(
      f"{PAYWISE_API_URL}/partner/v2/companies/{PAYWISE_COMPANY_ID}/users/?limit=100",
      headers={"Authorization": f"Bearer {PAYWISE_PARTNER_KEY}"},
      timeout=(5, 30),
  )
  if response.status_code != 200:
      response.raise_for_status()
      raise RuntimeError(f"Expected 200, received {response.status_code}")
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/partner/v2/companies/${process.env.PAYWISE_COMPANY_ID}/users/?limit=100`, {
    method: "GET", headers: { Authorization: `Bearer ${process.env.PAYWISE_PARTNER_KEY}` }, signal: AbortSignal.timeout(30000),
  });
  if (response.status !== 200) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
  ```

  ```java Java theme={null}
  import java.io.IOException;
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.time.Duration;
  class HostedOnboardingRequest {
    public static void main(String[] args) throws IOException, InterruptedException {
      String url = System.getenv("PAYWISE_API_URL") + "/partner/v2/companies/" + System.getenv("PAYWISE_COMPANY_ID") + "/users/?limit=100";
      HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
      HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(30))
          .header("Authorization", "Bearer " + System.getenv("PAYWISE_PARTNER_KEY")).GET().build();
      HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
      if (response.statusCode() != 200) throw new IOException(response.body());
    }
  }
  ```

  ```csharp C# theme={null}
  using System;
  using System.Net.Http;
  using System.Threading;
  using System.Threading.Tasks;
  class HostedOnboardingRequest {
      static async Task Main() {
          using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
          using var request = new HttpRequestMessage(HttpMethod.Get, $"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/partner/v2/companies/{Environment.GetEnvironmentVariable("PAYWISE_COMPANY_ID")}/users/?limit=100");
          request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_PARTNER_KEY"));
          using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
          using var response = await client.SendAsync(request, cancellation.Token);
          if ((int)response.StatusCode != 200) throw new HttpRequestException(await response.Content.ReadAsStringAsync());
      }
  }
  ```

  ```bash cURL theme={null}
  status="$(curl --fail-with-body --silent --show-error --connect-timeout 5 --max-time 30 \
    --request GET "$PAYWISE_API_URL/partner/v2/companies/$PAYWISE_COMPANY_ID/users/?limit=100" \
    --header "Authorization: Bearer $PAYWISE_PARTNER_KEY" \
    --output hosted-memberships.json --write-out '%{http_code}')"
  [ "$status" != "200" ] && exit 1
  ```
</CodeGroup>

## Runnable hosted-resolution workflow

`PAYWISE_VERIFIED_EVENT` must be the exact already-verified, durably accepted
event body supplied by your receiver—not an untrusted browser parameter.

```python Python workflow theme={null}
import json
import uuid
from urllib.parse import urlparse
import requests

PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_PARTNER_KEY = "pw_sbx_your_partner_key"
PAYWISE_VERIFIED_EVENT = "{\"id\":\"90000000-0000-4000-8000-000000000001\",\"type\":\"company.access.confirmed\",\"data\":{\"company_id\":\"20000000-0000-4000-8000-000000000001\",\"authorization_version\":1}}"
PAYWISE_WEBHOOK_ID = "90000000-0000-4000-8000-000000000001"

base_url = PAYWISE_API_URL
origin = urlparse(base_url)
if origin.scheme != "https" or not origin.netloc:
    raise ValueError("PAYWISE_API_URL must be an HTTPS origin")
session = requests.Session()
session.headers.update({"Authorization": f"Bearer {PAYWISE_PARTNER_KEY}"})

def get_json(url):
    response = session.get(url, timeout=(5, 30))
    if response.status_code != 200:
        response.raise_for_status()
        raise RuntimeError(f"Expected 200, received {response.status_code}")
    return response.json()

webhook_id = str(uuid.UUID(PAYWISE_WEBHOOK_ID))
subscription = get_json(f"{base_url}/partner/v2/webhooks/{webhook_id}/")
events = subscription.get("events", [])
if subscription.get("id") != webhook_id or subscription.get("enabled") is not True:
    raise RuntimeError("Completion subscription identity or state mismatch")
if events != ["*"] and "company.access.confirmed" not in events:
    raise RuntimeError("Subscription does not cover hosted completion")

event = json.loads(PAYWISE_VERIFIED_EVENT)
if event.get("type") != "company.access.confirmed":
    raise RuntimeError("Hosted onboarding is not complete")
version = event.get("data", {}).get("authorization_version")
if not isinstance(version, int) or isinstance(version, bool) or version <= 0:
    raise RuntimeError("Invalid authorization version")
company_id = str(uuid.UUID(event["data"]["company_id"]))
company = get_json(f"{base_url}/partner/v2/companies/{company_id}/")
if company.get("id") != company_id:
    raise RuntimeError("Confirmed company identity mismatch")
case_access = company.get("case_access")
readiness = company.get("case_submission_readiness")
if case_access not in {"available", "unavailable"} or not isinstance(readiness, dict):
    raise RuntimeError("Malformed current company projections")

next_url = f"{base_url}/partner/v2/companies/{company_id}/users/?limit=100"
seen_urls = set()
memberships = {}
for _ in range(5):
    parsed = urlparse(next_url)
    if parsed.scheme != "https" or parsed.netloc != origin.netloc:
        raise RuntimeError("Unsafe membership pagination URL")
    if next_url in seen_urls:
        raise RuntimeError("Membership pagination cycle")
    seen_urls.add(next_url)
    page = get_json(next_url)
    for membership in page.get("results", []):
        membership_id = str(uuid.UUID(membership["id"]))
        memberships[membership_id] = membership
    next_url = page.get("next")
    if next_url is None:
        break
else:
    raise RuntimeError("Membership pagination budget exhausted")
if next_url is not None:
    raise RuntimeError("Membership pagination did not terminate")
admin_ids = sorted(item_id for item_id, item in memberships.items() if item.get("role") == "admin" and item.get("status") != "cancelled")
if not admin_ids:
    raise RuntimeError("Confirmed company has no current admin membership")
WORKFLOW_RESULT = {"company_id": company_id, "admin_membership_ids": admin_ids, "case_access": case_access}
```

## Representative response

```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": "10000000-0000-4000-8000-000000000001",
  "customer_number": "5G0123",
  "name": "Hosted Client GmbH",
  "onboarding_mode": "web_flow",
  "onboarding_status": "confirmed",
  "legal_form": "gmbh",
  "address": {
    "street": "Musterstraße 1",
    "postal_code": "10115",
    "city": "Berlin",
    "country": "DE"
  },
  "phone": "+49301234567",
  "tax_treatment": "input_tax_deductible",
  "vat_number": "DE129273398",
  "default_claim_type": "H05",
  "payout_bank_account": {
    "account_holder": "Hosted Client GmbH",
    "iban": "DE89370400440532013000",
    "bic": null
  },
  "notification_channels": [{
    "type": "email",
    "value": "operations@example.test",
    "notifications": ["status_updates", "requests_to_client", "statements"]
  }],
  "legal_representatives": [{
    "type": "managing_director",
    "name": "Taylor Example"
  }],
  "case_access": "available",
  "case_submission_readiness": {"ready": true, "issues": []},
  "users": [{
    "id": "11000000-0000-4000-8000-000000000001",
    "email": "admin@example.test",
    "first_name": "Taylor",
    "last_name": "Example",
    "role": "admin",
    "status": "active",
    "invite_expires_at": null,
    "revoked_at": null,
    "revoked_by_token_id": null,
    "revocation_reason": "",
    "sandbox_origin": "sandbox_native",
    "created_at": "2026-08-27T10:00:00Z",
    "updated_at": "2026-08-27T10:05:00Z"
  }],
  "sandbox_origin": "sandbox_native",
  "created_at": "2026-08-27T10:00:00Z",
  "updated_at": "2026-08-27T10:05:00Z"
}
```

## Failure and recovery

* Missing confirmation event: retain the hosted correlation and verify the
  exact subscription. Never create a duplicate API company as compensation.
* Duplicate or delayed event: deduplicate by event UUID and refetch current
  state. `authorization_version` is provenance, not delivery order.
* `case_access: unavailable`: stop delegated Case traffic. Readiness false:
  render the issue list separately; it does not negate confirmed consent.
* A foreign, downgraded, cyclic, or over-budget `next` URL fails closed before
  it is requested.

## Related reference

* [GET `/partner/v2/webhooks/{id}/`](/api-docs/partner-api/reference/webhooks/get-webhook)
* [GET `/partner/v2/companies/{id}/`](/api-docs/partner-api/reference/companies/get-a-managed-company)
* [GET `/partner/v2/companies/{company_id}/users/`](/api-docs/partner-api/reference/company-users/list-company-memberships)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.