https://api-sandbox.paywise.de directly; replace the
inline PAYWISE_API_KEY placeholder with a sandbox key (pw_sbx_…) created
in the developer portal. Other language tabs read the matching environment
variables.
A Partner key must add X-On-Behalf-Of-Company: <company-uuid> to every Case
request instead.
Use the documented trailing slash on /v2/ roots and resource routes.
Slashless requests such as /v2/info return JSON 404 for every method,
including GET, HEAD, OPTIONS, and writes, with no redirect or Location
header. Request /v2/info/ directly. See
canonical URLs.
The request tabs below are complete, copyable examples for individual
operations. The final section runs the whole flow end to end once in Python.
This shortest path uses the default invoice order type. For subtype-specific
data and validation, follow Submit a rental order
or Submit a titled order.
1. Create the debtor record
Use a freshDEBTOR_REFERENCE and DEBTOR_COMMAND_ID for a new logical write.
Keep the same body and idempotency key when retrying an ambiguous result.
import uuid
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
DEBTOR_COMMAND_ID = "your-debtor-command-id"
DEBTOR_REFERENCE = "quickstart-debtor-80000000-0000-4000-8000-000000000001"
debtor_payload = {
"your_reference": DEBTOR_REFERENCE,
"acting_as": "consumer",
"person": {"salutation": "mx", "first_name": "Alex", "last_name": "Example"},
"addresses": [{
"street": "Example Street 12", "postal_code": "10115",
"city": "Berlin", "country": "DE", "primary": True,
}],
}
response = requests.post(
f"{PAYWISE_API_URL}/v2/debtors/",
headers={
"Authorization": f"Bearer {PAYWISE_API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": DEBTOR_COMMAND_ID,
},
json=debtor_payload,
timeout=(5, 30),
)
if response.status_code != 201:
response.raise_for_status()
raise RuntimeError(f"Expected 201, received {response.status_code}")
debtor_id = str(uuid.UUID(response.json()["id"]))
const debtorPayload = {
your_reference: process.env.DEBTOR_REFERENCE,
acting_as: "consumer",
person: { salutation: "mx", first_name: "Alex", last_name: "Example" },
addresses: [{ street: "Example Street 12", postal_code: "10115", city: "Berlin", country: "DE", primary: true }],
};
const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/debtors/`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PAYWISE_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": process.env.DEBTOR_COMMAND_ID,
},
body: JSON.stringify(debtorPayload),
signal: AbortSignal.timeout(30000),
});
if (response.status !== 201) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
const debtorId = (await response.json()).id;
if (!/^[0-9a-f-]{36}$/i.test(debtorId)) throw new Error("Invalid debtor UUID");
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 QuickstartRequest {
static String jsonString(String value) {
if (value == null) throw new IllegalArgumentException("Missing environment value");
StringBuilder escaped = new StringBuilder("\"");
for (int index = 0; index < value.length(); index++) {
char character = value.charAt(index);
switch (character) {
case '\"': escaped.append("\\\""); break;
case '\\': escaped.append("\\\\"); break;
case '\b': escaped.append("\\b"); break;
case '\f': escaped.append("\\f"); break;
case '\n': escaped.append("\\n"); break;
case '\r': escaped.append("\\r"); break;
case '\t': escaped.append("\\t"); break;
default:
if (character < 0x20) escaped.append(String.format("\\u%04x", (int) character));
else escaped.append(character);
}
}
return escaped.append('\"').toString();
}
public static void main(String[] args) throws IOException, InterruptedException {
String json = "{\"your_reference\":" + jsonString(System.getenv("DEBTOR_REFERENCE"))
+ ",\"acting_as\":\"consumer\",\"person\":{\"salutation\":\"mx\","
+ "\"first_name\":\"Alex\",\"last_name\":\"Example\"},\"addresses\":[{"
+ "\"street\":\"Example Street 12\",\"postal_code\":\"10115\","
+ "\"city\":\"Berlin\",\"country\":\"DE\",\"primary\":true}]}";
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(System.getenv("PAYWISE_API_URL") + "/v2/debtors/"))
.timeout(Duration.ofSeconds(30))
.header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", System.getenv("DEBTOR_COMMAND_ID"))
.POST(HttpRequest.BodyPublishers.ofString(json)).build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 201) throw new IOException(response.body());
String debtorId = response.body().replaceFirst("(?s).*\\\"id\\\"\\s*:\\s*\\\"([0-9a-fA-F-]{36})\\\".*", "$1");
if (debtorId.equals(response.body())) throw new IOException("Missing debtor UUID");
}
}
using System;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
class QuickstartRequest
{
static async Task Main()
{
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
var payload = new {
your_reference = Environment.GetEnvironmentVariable("DEBTOR_REFERENCE"),
acting_as = "consumer",
person = new { salutation = "mx", first_name = "Alex", last_name = "Example" },
addresses = new[] { new { street = "Example Street 12", postal_code = "10115", city = "Berlin", country = "DE", primary = true } }
};
using var request = new HttpRequestMessage(HttpMethod.Post,
$"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/v2/debtors/");
request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
request.Headers.Add("Idempotency-Key", Environment.GetEnvironmentVariable("DEBTOR_COMMAND_ID"));
request.Content = JsonContent.Create(payload);
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
using var response = await client.SendAsync(request, cancellation.Token);
if ((int)response.StatusCode != 201) throw new HttpRequestException(await response.Content.ReadAsStringAsync());
using var debtor = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var debtorId = debtor.RootElement.GetProperty("id").GetGuid();
}
}
http_status="$(curl --fail-with-body --output quickstart-response.json --write-out '%{http_code}' --silent --show-error --connect-timeout 5 --max-time 30 \
--request POST "$PAYWISE_API_URL/v2/debtors/" \
--header "Authorization: Bearer $PAYWISE_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: $DEBTOR_COMMAND_ID" \
--data @- <<JSON
{
"your_reference": "$DEBTOR_REFERENCE",
"acting_as": "consumer",
"person": {"salutation": "mx", "first_name": "Alex", "last_name": "Example"},
"addresses": [{
"street": "Example Street 12",
"postal_code": "10115",
"city": "Berlin",
"country": "DE",
"primary": true
}]
}
JSON
)"
[ "$http_status" -eq 201 ] || exit 1
DEBTOR_ID="$(jq -er '.id' quickstart-response.json)" || exit 1
id directly. Every language tab
above parses and retains that exact UUID; reuse it as DEBTOR_ID in step 2:
{"id": "acaf6cc1-f711-42c9-a385-7a3f537325cc", "created_at": "…", …}
GET /v2/debtors/?your_reference=<reference> finds the
debtor again. The reference is not enforced unique and the filter is a
case-insensitive substring match, so compare your_reference exactly on the
results and treat more than one match as a reconciliation task.
2. Create the order draft
This is aReceivableClaim in an invoice order, so the claim declares
"type": "receivable" (required on every claim). Omitting the order-level
type defaults the order to invoice; no rental-agreement or
enforceable-title fields belong in this payload. Choose legal_basis.claim_type_code
from the complete German code catalog;
this example uses H05 for a service contract.
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
CLAIM_REFERENCE = "quickstart-claim-80000000-0000-4000-8000-000000000001"
DEBTOR_ID = "40000000-0000-4000-8000-000000000001"
DOCUMENT_REFERENCE = "INV-80000000-0000-4000-8000-000000000001"
ORDER_COMMAND_ID = "your-order-command-id"
order_payload = {
"debtor_id": DEBTOR_ID,
"additional_debtor_ids": [],
"starting_approach": "extrajudicial",
"creditor_obligation_fulfilled": True,
"claims": [{
"type": "receivable",
"your_reference": CLAIM_REFERENCE,
"document_reference": DOCUMENT_REFERENCE,
"subject_matter": "Consulting services for June 2026",
"principal_amount": {"value": "125.50", "currency": "EUR"},
"document_date": "2026-06-30", "due_date": "2026-07-14",
"delay_date": "2026-07-20",
"is_disputed": False, "items": [], "additional_charges": [],
"reminders": [], "documents": [],
"legal_basis": {"claim_type_code": "H05", "contract_date": "2026-06-01", "description": "Consulting agreement"},
}],
}
response = requests.post(
f"{PAYWISE_API_URL}/v2/orders/",
headers={
"Authorization": f"Bearer {PAYWISE_API_KEY}",
"Content-Type": "application/json",
"Idempotency-Key": ORDER_COMMAND_ID,
},
json=order_payload,
timeout=(5, 30),
)
if response.status_code != 201:
response.raise_for_status()
raise RuntimeError(f"Expected 201, received {response.status_code}")
order = response.json()
order_id = str(uuid.UUID(order["id"]))
matching_claims = [
claim for claim in order.get("claims", [])
if claim.get("your_reference") == CLAIM_REFERENCE
]
if len(matching_claims) != 1:
raise RuntimeError("Expected exactly one claim with the submitted business reference")
claim_id = str(uuid.UUID(matching_claims[0]["id"]))
const orderPayload = {
debtor_id: process.env.DEBTOR_ID,
additional_debtor_ids: [], starting_approach: "extrajudicial",
creditor_obligation_fulfilled: true,
claims: [{
type: "receivable",
your_reference: process.env.CLAIM_REFERENCE,
document_reference: process.env.DOCUMENT_REFERENCE,
subject_matter: "Consulting services for June 2026",
principal_amount: { value: "125.50", currency: "EUR" },
document_date: "2026-06-30", due_date: "2026-07-14", delay_date: "2026-07-20",
is_disputed: false,
items: [], additional_charges: [], reminders: [], documents: [],
legal_basis: { claim_type_code: "H05", contract_date: "2026-06-01", description: "Consulting agreement" },
}],
};
const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/orders/`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PAYWISE_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": process.env.ORDER_COMMAND_ID,
},
body: JSON.stringify(orderPayload),
signal: AbortSignal.timeout(30000),
});
if (response.status !== 201) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
const order = await response.json();
const matchingClaims = order.claims.filter((claim) => claim.your_reference === process.env.CLAIM_REFERENCE);
if (matchingClaims.length !== 1) throw new Error("Expected exactly one claim with the submitted business reference");
const claimId = matchingClaims[0].id;
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 QuickstartRequest {
static String jsonString(String value) {
if (value == null) throw new IllegalArgumentException("Missing environment value");
StringBuilder escaped = new StringBuilder("\"");
for (int index = 0; index < value.length(); index++) {
char character = value.charAt(index);
switch (character) {
case '\"': escaped.append("\\\""); break;
case '\\': escaped.append("\\\\"); break;
case '\b': escaped.append("\\b"); break;
case '\f': escaped.append("\\f"); break;
case '\n': escaped.append("\\n"); break;
case '\r': escaped.append("\\r"); break;
case '\t': escaped.append("\\t"); break;
default:
if (character < 0x20) escaped.append(String.format("\\u%04x", (int) character));
else escaped.append(character);
}
}
return escaped.append('\"').toString();
}
public static void main(String[] args) throws IOException, InterruptedException {
String json = "{\"debtor_id\":" + jsonString(System.getenv("DEBTOR_ID"))
+ ",\"additional_debtor_ids\":[],\"starting_approach\":\"extrajudicial\","
+ "\"creditor_obligation_fulfilled\":true,\"claims\":[{\"type\":\"receivable\",\"your_reference\":"
+ jsonString(System.getenv("CLAIM_REFERENCE")) + ",\"document_reference\":"
+ jsonString(System.getenv("DOCUMENT_REFERENCE")) + ",\"subject_matter\":\"Consulting services for June 2026\","
+ "\"principal_amount\":{\"value\":\"125.50\",\"currency\":\"EUR\"},"
+ "\"document_date\":\"2026-06-30\",\"due_date\":\"2026-07-14\","
+ "\"delay_date\":\"2026-07-20\",\"is_disputed\":false,"
+ "\"items\":[],\"additional_charges\":[],\"reminders\":[],\"documents\":[],"
+ "\"legal_basis\":{\"claim_type_code\":\"H05\",\"contract_date\":\"2026-06-01\","
+ "\"description\":\"Consulting agreement\"}}]}";
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(System.getenv("PAYWISE_API_URL") + "/v2/orders/"))
.timeout(Duration.ofSeconds(30))
.header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY"))
.header("Content-Type", "application/json")
.header("Idempotency-Key", System.getenv("ORDER_COMMAND_ID"))
.POST(HttpRequest.BodyPublishers.ofString(json)).build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 201) throw new IOException(response.body());
// Parse the JSON response with your JSON library and require exactly one
// claims[] entry whose your_reference equals CLAIM_REFERENCE; persist its id.
}
}
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
class QuickstartRequest
{
static async Task Main()
{
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
var payload = new {
debtor_id = Environment.GetEnvironmentVariable("DEBTOR_ID"),
additional_debtor_ids = Array.Empty<string>(), starting_approach = "extrajudicial",
creditor_obligation_fulfilled = true,
claims = new[] { new {
type = "receivable",
your_reference = Environment.GetEnvironmentVariable("CLAIM_REFERENCE"),
document_reference = Environment.GetEnvironmentVariable("DOCUMENT_REFERENCE"),
subject_matter = "Consulting services for June 2026",
principal_amount = new { value = "125.50", currency = "EUR" },
document_date = "2026-06-30", due_date = "2026-07-14",
delay_date = "2026-07-20", is_disputed = false,
items = Array.Empty<object>(), additional_charges = Array.Empty<object>(),
reminders = Array.Empty<object>(), documents = Array.Empty<object>(),
legal_basis = new { claim_type_code = "H05", contract_date = "2026-06-01", description = "Consulting agreement" }
} }
};
using var request = new HttpRequestMessage(HttpMethod.Post,
$"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/v2/orders/");
request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
request.Headers.Add("Idempotency-Key", Environment.GetEnvironmentVariable("ORDER_COMMAND_ID"));
request.Content = JsonContent.Create(payload);
using var cancellation = new CancellationTokenSource(TimeSpan.FromSeconds(30));
using var response = await client.SendAsync(request, cancellation.Token);
if ((int)response.StatusCode != 201) throw new HttpRequestException(await response.Content.ReadAsStringAsync());
using var order = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var matches = order.RootElement.GetProperty("claims").EnumerateArray()
.Where(claim => claim.GetProperty("your_reference").GetString() == Environment.GetEnvironmentVariable("CLAIM_REFERENCE"))
.ToArray();
if (matches.Length != 1) throw new InvalidOperationException("Expected exactly one matching claim");
var claimId = matches[0].GetProperty("id").GetGuid();
}
}
http_status="$(curl --fail-with-body --output quickstart-response.json --write-out '%{http_code}' --silent --show-error --connect-timeout 5 --max-time 30 \
--request POST "$PAYWISE_API_URL/v2/orders/" \
--header "Authorization: Bearer $PAYWISE_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: $ORDER_COMMAND_ID" \
--data @- <<JSON
{
"debtor_id": "$DEBTOR_ID",
"additional_debtor_ids": [],
"starting_approach": "extrajudicial",
"creditor_obligation_fulfilled": true,
"claims": [{
"type": "receivable",
"your_reference": "$CLAIM_REFERENCE",
"document_reference": "$DOCUMENT_REFERENCE",
"subject_matter": "Consulting services for June 2026",
"principal_amount": {"value": "125.50", "currency": "EUR"},
"document_date": "2026-06-30",
"due_date": "2026-07-14",
"delay_date": "2026-07-20",
"is_disputed": false,
"items": [],
"additional_charges": [],
"reminders": [],
"documents": [],
"legal_basis": {
"claim_type_code": "H05",
"contract_date": "2026-06-01",
"description": "Consulting agreement"
}
}]
}
JSON
)"
[ "$http_status" -eq 201 ] || exit 1
ORDER_ID="$(jq -er '.id' quickstart-response.json)" || exit 1
CLAIM_ID="$(jq -er --arg ref "$CLAIM_REFERENCE" '[.claims[] | select(.your_reference == $ref)] | if length == 1 then .[0].id else error("expected one matching claim") end' quickstart-response.json)" || exit 1
PATCH /v2/orders/{id}/
corrects any field; omitted fields and arrays stay unchanged.
3. Finalize exactly once
Finalize accepts an empty body or exactly{}; any member is
400 validation_error with unknown_field. The examples send an empty body,
so they do not need a Content-Type header.
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
FINALIZE_COMMAND_ID = "your-finalize-command-id"
ORDER_ID = "20000000-0000-4000-8000-000000000001"
response = requests.post(
f"{PAYWISE_API_URL}/v2/orders/{ORDER_ID}/finalize/",
headers={
"Authorization": f"Bearer {PAYWISE_API_KEY}",
"Idempotency-Key": FINALIZE_COMMAND_ID,
},
timeout=(5, 30),
)
if response.status_code != 200:
response.raise_for_status()
raise RuntimeError(f"Expected 200, received {response.status_code}")
const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/orders/${process.env.ORDER_ID}/finalize/`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PAYWISE_API_KEY}`,
"Idempotency-Key": process.env.FINALIZE_COMMAND_ID,
},
signal: AbortSignal.timeout(30000),
});
if (response.status !== 200) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
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 QuickstartRequest {
public static void main(String[] args) throws IOException, InterruptedException {
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(System.getenv("PAYWISE_API_URL") + "/v2/orders/" + System.getenv("ORDER_ID") + "/finalize/"))
.timeout(Duration.ofSeconds(30))
.header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY"))
.header("Idempotency-Key", System.getenv("FINALIZE_COMMAND_ID"))
.POST(HttpRequest.BodyPublishers.noBody()).build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new IOException(response.body());
}
}
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
class QuickstartRequest
{
static async Task Main()
{
using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
using var request = new HttpRequestMessage(HttpMethod.Post,
$"{Environment.GetEnvironmentVariable("PAYWISE_API_URL")}/v2/orders/{Environment.GetEnvironmentVariable("ORDER_ID")}/finalize/");
request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_KEY"));
request.Headers.Add("Idempotency-Key", Environment.GetEnvironmentVariable("FINALIZE_COMMAND_ID"));
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());
}
}
http_status="$(curl --fail-with-body --output quickstart-response.json --write-out '%{http_code}' --silent --show-error --connect-timeout 5 --max-time 30 \
--request POST "$PAYWISE_API_URL/v2/orders/$ORDER_ID/finalize/" \
--header "Authorization: Bearer $PAYWISE_API_KEY" \
--header "Idempotency-Key: $FINALIZE_COMMAND_ID"
)"
[ "$http_status" -eq 200 ] || exit 1
4. Accept the order in the sandbox
In production, paywise reviews and accepts a submitted order. The sandbox never accepts one on its own, and there is no API call for it: you accept the order in the sandbox portal. From the production portal, follow this path: Für Entwickler → Zur Sandbox → Mandate & Aufträge → Aufträge in Prüfung → open the order finalized in step 3 → open the sandbox drawer (the amber Sandbox-Tools button at the lower right, orAlt+S) → Kontext → Auftrag annehmen → Bestätigen
Your first Zur Sandbox entry activates the sandbox automatically.
The portal opens the newly created mandate. Later, the same drawer simulates
the debtor paying that mandate — Zahlung des Schuldners an paywise, in full
or as a partial amount — and every other counterparty step; see the
sandbox drawer.
5. Verify acceptance and find the mandate
Acceptance freezes the order’s claim set into exactly one mandate, and the order names it: oncestatus is accepted, the order’s read-only mandate
field carries the mandate UUID.
The production pattern is push, not poll: subscribe a webhook to
order.accepted (see
Consume Case webhooks
for subscription creation, signature verification, and deduplication). The
event payload names both sides of the boundary: the order (order_id,
order_url) and the mandate its claims were frozen into (mandate_id,
mandate_url). Fetch mandate_url on your configured API host directly;
mandate_id equals the accepted order’s mandate field:
order.accepted payload (data)
{
"company_id": "10000000-0000-4000-8000-000000000001",
"order_id": "20000000-0000-4000-8000-000000000001",
"order_url": "/v2/orders/20000000-0000-4000-8000-000000000001/",
"claim_ids": ["30000000-0000-4000-8000-000000000001"],
"mandate_id": "50000000-0000-4000-8000-000000000001",
"mandate_url": "/v2/mandates/50000000-0000-4000-8000-000000000001/"
}
claim_ids, then refetch /v2/claims/{claim_id}/. Trust that response’s
current order_id and nullable mandate_id, even when the event’s order_id
differs from the order where you originally submitted the claim. Do not follow
an order merge chain to locate it.
In this quickstart no webhook endpoint is needed: the sandbox simulation in
step 4 accepts the order synchronously. Read the order once and take the
mandate UUID from the response:
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
ORDER_ID = "20000000-0000-4000-8000-000000000001"
response = requests.get(
f"{PAYWISE_API_URL}/v2/orders/{ORDER_ID}/",
headers={"Authorization": f"Bearer {PAYWISE_API_KEY}"},
timeout=(5, 30),
)
if response.status_code != 200:
response.raise_for_status()
raise RuntimeError(f"Expected 200, received {response.status_code}")
const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/orders/${process.env.ORDER_ID}/`, {
method: "GET",
headers: { Authorization: `Bearer ${process.env.PAYWISE_API_KEY}` },
signal: AbortSignal.timeout(30000),
});
if (response.status !== 200) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
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 QuickstartRequest {
public static void main(String[] args) throws IOException, InterruptedException {
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(System.getenv("PAYWISE_API_URL") + "/v2/orders/" + System.getenv("ORDER_ID") + "/"))
.timeout(Duration.ofSeconds(30))
.header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY"))
.GET().build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new IOException(response.body());
}
}
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
class QuickstartRequest
{
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")}/v2/orders/{Environment.GetEnvironmentVariable("ORDER_ID")}/");
request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_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());
}
}
http_status="$(curl --fail-with-body --output quickstart-response.json --write-out '%{http_code}' --silent --show-error --connect-timeout 5 --max-time 30 \
--request GET "$PAYWISE_API_URL/v2/orders/$ORDER_ID/" \
--header "Authorization: Bearer $PAYWISE_API_KEY"
)"
[ "$http_status" -eq 200 ] || exit 1
mandate field:
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
MANDATE_ID = "50000000-0000-4000-8000-000000000001"
response = requests.get(
f"{PAYWISE_API_URL}/v2/mandates/{MANDATE_ID}/",
headers={"Authorization": f"Bearer {PAYWISE_API_KEY}"},
timeout=(5, 30),
)
if response.status_code != 200:
response.raise_for_status()
raise RuntimeError(f"Expected 200, received {response.status_code}")
const response = await fetch(`${process.env.PAYWISE_API_URL.replace(/\/$/, "")}/v2/mandates/${process.env.MANDATE_ID}/`, {
method: "GET",
headers: { Authorization: `Bearer ${process.env.PAYWISE_API_KEY}` },
signal: AbortSignal.timeout(30000),
});
if (response.status !== 200) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
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 QuickstartRequest {
public static void main(String[] args) throws IOException, InterruptedException {
HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(System.getenv("PAYWISE_API_URL") + "/v2/mandates/" + System.getenv("MANDATE_ID") + "/"))
.timeout(Duration.ofSeconds(30))
.header("Authorization", "Bearer " + System.getenv("PAYWISE_API_KEY"))
.GET().build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) throw new IOException(response.body());
}
}
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
class QuickstartRequest
{
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")}/v2/mandates/{Environment.GetEnvironmentVariable("MANDATE_ID")}/");
request.Headers.Authorization = new("Bearer", Environment.GetEnvironmentVariable("PAYWISE_API_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());
}
}
http_status="$(curl --fail-with-body --output quickstart-response.json --write-out '%{http_code}' --silent --show-error --connect-timeout 5 --max-time 30 \
--request GET "$PAYWISE_API_URL/v2/mandates/$MANDATE_ID/" \
--header "Authorization: Bearer $PAYWISE_API_KEY"
)"
[ "$http_status" -eq 200 ] || exit 1
6. Runnable end-to-end workflow
Installrequests, replace the inline PAYWISE_API_KEY placeholder, then run
this program. The API URL is already set to the Sandbox host. Every id it
needs comes straight from a command response. Before writing, it checks HTTPS
and confirms the authenticated API reports the sandbox environment. After
finalizing the order it pauses: accept the order in the sandbox portal as in
step 4, and the program picks the acceptance up on its next poll.
Python workflow
import time
import uuid
from urllib.parse import urlsplit
import requests
PAYWISE_API_URL = "https://api-sandbox.paywise.de"
PAYWISE_API_KEY = "pw_sbx_your_api_key"
TIMEOUT = (5, 30)
def normalized_https_origin(url):
parts = urlsplit(url)
if (parts.scheme.lower() != "https" or not parts.hostname
or parts.username is not None or parts.password is not None
or parts.port not in (None, 443) or parts.query or parts.fragment):
raise ValueError("PAYWISE_API_URL must be an HTTPS sandbox URL")
return (parts.scheme.lower(), parts.hostname.lower(), parts.port or 443)
def require_status(response, expected):
if response.status_code != expected:
response.raise_for_status()
raise RuntimeError(f"Expected {expected}, received {response.status_code}")
def main():
base_url = PAYWISE_API_URL
normalized_https_origin(base_url)
session = requests.Session()
session.headers.update({
"Authorization": f"Bearer {PAYWISE_API_KEY}",
"Accept": "application/json",
})
response = session.get(f"{base_url}/v2/info/", timeout=TIMEOUT)
require_status(response, 200)
environment = next((value for key, value in response.headers.items()
if key.lower() == "x-paywise-environment"), None)
if environment != "sandbox" or response.json().get("environment") != "sandbox":
raise RuntimeError("This quickstart requires an authenticated sandbox API")
run_id = str(uuid.uuid4())
debtor_payload = {
"your_reference": f"quickstart-debtor-{run_id}",
"acting_as": "consumer",
"person": {"salutation": "mx", "first_name": "Alex", "last_name": "Example"},
"addresses": [{
"street": "Example Street 12", "postal_code": "10115",
"city": "Berlin", "country": "DE", "primary": True,
}],
}
response = session.post(
f"{base_url}/v2/debtors/",
headers={"Content-Type": "application/json", "Idempotency-Key": str(uuid.uuid4())},
json=debtor_payload,
timeout=TIMEOUT,
)
require_status(response, 201)
debtor_id = response.json()["id"]
order_payload = {
"debtor_id": debtor_id,
"additional_debtor_ids": [],
"starting_approach": "extrajudicial",
"creditor_obligation_fulfilled": True,
"claims": [{
"type": "receivable",
"your_reference": f"quickstart-claim-{run_id}",
"document_reference": f"INV-{run_id}",
"subject_matter": "Consulting services for June 2026",
"principal_amount": {"value": "125.50", "currency": "EUR"},
"document_date": "2026-06-30", "due_date": "2026-07-14",
"delay_date": "2026-07-20",
"is_disputed": False, "items": [], "additional_charges": [],
"reminders": [], "documents": [],
"legal_basis": {"claim_type_code": "H05", "contract_date": "2026-06-01", "description": "Consulting agreement"},
}],
}
response = session.post(
f"{base_url}/v2/orders/",
headers={"Content-Type": "application/json", "Idempotency-Key": str(uuid.uuid4())},
json=order_payload,
timeout=TIMEOUT,
)
require_status(response, 201)
order = response.json()
order_id = order["id"]
matching_claims = [
claim for claim in order.get("claims", [])
if claim.get("your_reference") == f"quickstart-claim-{run_id}"
]
if len(matching_claims) != 1:
raise RuntimeError("Expected exactly one claim with the submitted business reference")
claim_id = matching_claims[0]["id"]
response = session.post(
f"{base_url}/v2/orders/{order_id}/finalize/",
headers={"Idempotency-Key": str(uuid.uuid4())},
timeout=TIMEOUT,
)
require_status(response, 200)
# Sandbox only: nothing accepts a submitted order on its own, and there is
# no API call for it. Accept the order in the sandbox portal (sandbox
# drawer -> Kontext -> Auftrag annehmen -> Bestätigen) while this program
# polls the order, at most 20 times, 15 seconds apart.
print(
f"Order {order_id} is submitted. Accept it in the sandbox portal: "
"Mandate & Aufträge -> Aufträge in Prüfung -> open the order -> "
"Sandbox-Tools -> Kontext -> Auftrag annehmen -> Bestätigen."
)
for attempt in range(20):
if attempt:
time.sleep(15)
response = session.get(f"{base_url}/v2/orders/{order_id}/", timeout=TIMEOUT)
require_status(response, 200)
current = response.json()
if current["status"] == "accepted":
break
if current["status"] != "submitted":
raise RuntimeError(f"Expected submitted or accepted, order is {current['status']}")
else:
raise RuntimeError("Order was not accepted in the sandbox portal in time")
mandate_id = current.get("mandate")
if not mandate_id:
raise RuntimeError("Accepted order did not return its mandate ID")
# The same acceptance rule applies to webhook claim_ids: refetch the claim
# and trust its current placement after review.
response = session.get(f"{base_url}/v2/claims/{claim_id}/", timeout=TIMEOUT)
require_status(response, 200)
accepted_claim = response.json()
if (accepted_claim.get("status") != "accepted" or accepted_claim.get("mandate_id") != mandate_id):
raise RuntimeError("Claim acceptance projection mismatch")
current_order_id = accepted_claim["order_id"]
# Fetch the mandate and confirm it holds this run's exact claim.
response = session.get(f"{base_url}/v2/mandates/{mandate_id}/", timeout=TIMEOUT)
require_status(response, 200)
mandate = response.json()
if not any(
claim["id"] == claim_id and claim["your_reference"] == f"quickstart-claim-{run_id}"
for claim in mandate["claims"]
):
raise RuntimeError("Mandate does not contain this run's exact claim")
return {
"order_id": order_id,
"claim_id": claim_id,
"current_order_id": current_order_id,
"mandate_id": mandate_id,
}
WORKFLOW_RESULT = main()
mandate from the accepted order, verifies that the
fetched mandate holds this run’s exact claim, and never prints credentials.
Unexpected order states stop the program for inspection.
