My Factura Public API Review

Purpose

This document provides a comprehensive review of the My Factura (Cash360) Public API that the Membership backend consumes for all payment processing, billing, consumer synchronization, and financial reporting operations. It serves two purposes:

  1. Reference specification -- The complete current API surface as implemented in Cash360, with all endpoints, data models, statuses, and behaviors documented for Membership developers.
  2. Improvement proposals -- Analysis of API design issues, naming inconsistencies, missing features, and a proposed redesign for future versions.

Membership integrates with My Factura exclusively through this public API. No internal Cash360 APIs, database access, or code-level dependencies exist.


Current API Specification

Authentication

All requests require an API_KEY header. The API key is entity-specific -- each organization (entity) in Cash360 has its own key that scopes all operations to that entity's data.

Header: API_KEY: <entity-specific-api-key>

There is no OAuth2 flow, no token expiration, and no refresh mechanism. The API key is static until manually rotated by a Cash360 administrator.

Base URL Structure

All endpoints are served under the /api/public path prefix with version identifiers:

Resource Base Path Version
Consumer /api/public/p2/v1/consumer v1
Transaction /api/public/p2/v1/transaction v1
Payment /api/public/p2/v1/payment v1
Billing Reporting /api/public/p2/v2/billing-statement-reporting v2

The p2 segment indicates the public API tier (P2). Cash360 also has P1 and P3 tiers for different access levels, but Membership exclusively uses P2.


Consumer API

The Consumer API manages the lifecycle of consumers (members) within Cash360. Each consumer in Membership must have a corresponding consumer record in Cash360 to enable billing.

Endpoints

Method Path Description
POST /api/public/p2/v1/consumer Create one or more consumers (bulk)
PUT /api/public/p2/v1/consumer/{id} Update a consumer by internal Cash360 ID
GET /api/public/p2/v1/consumer/{id} Get a consumer by internal Cash360 ID
GET /api/public/p2/v1/consumer/external/{id} Get a consumer by external ID
GET /api/public/p2/v1/consumer Filter consumers by email or external ID
GET /api/public/p2/v1/consumer/custom-attribute Filter consumers by custom attribute

Consumer Data Model

Field Type Required Constraints Notes
Id long Yes (response) Auto-generated Internal Cash360 consumer ID
IdExternal number No Entity-unique External system reference (Membership member ID)
flgDunningEnabled boolean Yes Default: true Whether dunning is enabled for this consumer
firstName string Conditional Required if type = PERSON First name
lastName string Conditional Required if type = PERSON Last name
type enum Yes PERSON or COMPANY Consumer type
companyName string Conditional Required if type = COMPANY Company/organization name
email string No Entity-unique Email address
gender enum No MALE, FEMALE, OTHER Gender
birthday date No ISO 8601 date Date of birth
street string No Street address
addressSupplement string No Address line 2
postCode string No Postal code
city string No City
isoCountry enum No ISO 3166-1 alpha-2 Country code
countryName string No Country name (human-readable)
isoLanguag enum No ISO 639-1 Language code (note: typo in API, missing 'e')
telephone string No Phone number
cellphone string No Mobile phone number
fax string No Fax number
isBlacklisted boolean No Default: false Blacklist status

Total fields: 23 (including Id)

Consumer Create Request

The create endpoint accepts an array of consumer objects, enabling bulk creation:

POST /api/public/p2/v1/consumer
API_KEY: <key>

[
  {
    "IdExternal": 12345,
    "firstName": "Max",
    "lastName": "Mustermann",
    "type": "PERSON",
    "email": "max@example.com",
    "gender": "MALE",
    "birthday": "1990-05-15",
    "street": "Hauptstrasse 42",
    "postCode": "10115",
    "city": "Berlin",
    "isoCountry": "DE",
    "isoLanguag": "de",
    "flgDunningEnabled": true,
    "contract": {
      "contractNumber": "MBR-2026-0001",
      "contractStartDate": "2026-01-01"
    },
    "bankAccount": {
      "iban": "DE89370400440532013000",
      "accountOwner": "Max Mustermann",
      "sepaMandanteId": "MBR-MNDT-12345",
      "sepaMandanteDateOfSigniture": "2025-12-15"
    }
  }
]

Consumer Update Request

Update is a single consumer operation (not bulk):

PUT /api/public/p2/v1/consumer/42
API_KEY: <key>

{
  "firstName": "Maximilian",
  "street": "Neue Strasse 7",
  "city": "Munich"
}

Consumer Filter Query Parameters

Endpoint Parameters Description
GET /consumer email Filter by exact email address
GET /consumer externalId Filter by external ID
GET /consumer/custom-attribute Attribute-specific Filter by custom attribute key/value

Contract Data Model (Embedded in Consumer)

Contract data is embedded within the consumer object during creation and cannot be managed as a separate resource.

Field Type Required Constraints Notes
customAttributes Map No Key-value pairs for custom data
contractNumber string No Entity-unique Contract reference number
contractSigningDate date No ISO 8601 Date the contract was signed
contractStartDate date No ISO 8601 Contract effective start date
contractEndDate date No ISO 8601 Contract end date
contractCancellationDate date No ISO 8601 Date cancellation was requested
contractCancellationReason string No Reason for cancellation
contractCancellationActiveOnDate date No ISO 8601 Date cancellation takes effect

Total fields: 8 (all optional)


Bank Account API

Bank accounts are managed as sub-resources of consumers. They hold SEPA mandate information required for direct debit processing.

Endpoints

Method Path Description
POST /api/public/p2/v1/consumer/{consumerId}/bank-account Create a bank account
GET /api/public/p2/v1/consumer/{consumerId}/bank-account/{id} Get by internal ID
GET /api/public/p2/v1/consumer/{consumerId}/bank-account Filter bank accounts
PUT /api/public/p2/v1/consumer/{consumerId}/bank-account/{id} Update bank account
PUT /api/public/p2/v1/consumer/{consumerId}/bank-account/{id}/archive Archive (soft delete)
PUT /api/public/p2/v1/consumer/{consumerId}/bank-account/{id}/restore Restore archived
PUT /api/public/p2/v1/consumer/{consumerId}/bank-account/{id}/set-primary Set as primary account

Bank Account Data Model

Field Type Required Constraints Notes
iban string Yes Valid IBAN International Bank Account Number
accountOwner string Yes Name of the account holder
sepaMandanteId string Yes System-unique, max 35 chars SEPA mandate reference (note: typo, should be "Mandate")
sepaMandanteDateOfSigniture date Yes ISO 8601 SEPA mandate signing date (note: typo, should be "Signature")
bic string No Valid BIC/SWIFT Bank Identifier Code
bankName string No Name of the bank
flgPrimary boolean No Default: false Whether this is the primary bank account
idCsrConsumer long No Consumer reference (usually implicit from URL)

Total fields: 8


Transaction API

The Transaction API manages billing transactions -- the individual charges that Cash360 collects from consumers via SEPA direct debit, manual payment, or other collection methods.

Endpoints

Method Path Description
POST /api/public/p2/v1/transaction Create one or more transactions (bulk)
GET /api/public/p2/v1/transaction/{id} Get by internal Cash360 ID
GET /api/public/p2/v1/transaction Get by filter
GET /api/public/p2/v1/transaction/external/{id} Get by external ID
PUT /api/public/p2/v1/transaction/collection-type Update collection type

Transaction Data Model

Field Type Required Direction Notes
idConsumer long Yes Request Cash360 consumer ID
collectionType enum Yes Request DO_NOT_COLLECT, DIRECT_DEBIT, DRAFT
amount BigDecimal Yes Request Gross amount (total to charge)
dueDate date Yes Request Due date (cannot be in the past)
flgTermination boolean Yes Request Default: false. Final transaction for this consumer
idExternal number No Both External system reference
status string No Response Transaction status code
reason string No Both Reason/description for the transaction
amountNet BigDecimal No Both Net amount before VAT
vatRate BigDecimal No Both Primary VAT rate (e.g., 19.00)
vatAmount BigDecimal No Both Primary VAT amount
vatRate2 BigDecimal No Both Secondary VAT rate (e.g., 7.00)
vatAmount2 BigDecimal No Both Secondary VAT amount
amountDue BigDecimal No Response Remaining amount due (after partial payments)
description string No Both Human-readable description
dunningStatus string No Response Current dunning level
paused boolean No Both Whether collection is paused
pauseStartDate date No Both Pause start date
pauseEndDate date No Both Pause end date
pauseUnpauseReason string No Both Reason for pausing/unpausing
sentToInkassoDateTime datetime No Response When sent to debt collection
createdAt datetime No Response Creation timestamp
updatedAt datetime No Response Last modification timestamp
webhook string (URL) No Request Webhook URL for status updates

Total fields: 24

Transaction Create Request

POST /api/public/p2/v1/transaction
API_KEY: <key>

[
  {
    "idConsumer": 42,
    "collectionType": "DIRECT_DEBIT",
    "amount": 29.90,
    "amountNet": 25.13,
    "vatRate": 19.00,
    "vatAmount": 4.77,
    "dueDate": "2026-02-01",
    "description": "Monthly membership - Adult Gold (Feb 2026)",
    "idExternal": 100234,
    "webhook": "https://membership.example.com/api/webhooks/cash360/transaction"
  },
  {
    "idConsumer": 43,
    "collectionType": "DIRECT_DEBIT",
    "amount": 14.90,
    "dueDate": "2026-02-01",
    "description": "Monthly membership - Youth Basic (Feb 2026)",
    "idExternal": 100235,
    "webhook": "https://membership.example.com/api/webhooks/cash360/transaction"
  }
]

Transaction Status Lifecycle

stateDiagram-v2 [*] --> NEW: Transaction created state "Happy Path (Direct Debit)" as happy { NEW --> ACCEPTED: Validated by Cash360 ACCEPTED --> EXPORTED: Included in SEPA XML export EXPORTED --> PAID: Bank confirms payment PAID --> SETTLED: Reconciled in billing statement } state "Dunning Path" as dunning { NEW --> FOR_DUNNING: Payment overdue FOR_DUNNING --> SHOULD_GO_TO_INKASSO: Dunning escalated FOR_DUNNING --> SENDING_TO_INKASSO: Sent to debt collection SHOULD_GO_TO_INKASSO --> SENDING_TO_INKASSO: Processing SENDING_TO_INKASSO --> SENT_TO_INKASSO: Confirmed sent SENT_TO_INKASSO --> RETURNED_FROM_INKASSO: Case resolved } state "Terminal States" as terminal { CANCELLED: Cancelled by operator RETURNED: Bank returned direct debit REJECTED: Validation rejected INSTALLMENT: Converted to installment plan } NEW --> CANCELLED ACCEPTED --> CANCELLED EXPORTED --> RETURNED: Bank returns debit NEW --> REJECTED: Validation fails FOR_DUNNING --> CANCELLED FOR_DUNNING --> INSTALLMENT SETTLED --> [*] CANCELLED --> [*] RETURNED_FROM_INKASSO --> [*]

Status codes:

Status Description Typical Trigger
NEW Transaction created, awaiting processing POST create
ACCEPTED Validated, queued for SEPA export Cash360 validation
EXPORTED Included in SEPA XML file sent to bank SEPA batch export
PAID Bank confirmed payment received Bank settlement report
SETTLED Reconciled in billing statement Billing cycle close
FOR_DUNNING Overdue, entered dunning workflow Dunning scheduler
SHOULD_GO_TO_INKASSO Dunning escalated, pending debt collection Dunning escalation
SENDING_TO_INKASSO Being transmitted to debt collection agency Operator action
SENT_TO_INKASSO Confirmed received by debt collection Agency acknowledgment
RETURNED_FROM_INKASSO Case closed by debt collection (paid or written off) Agency report
CANCELLED Transaction voided Manual cancellation or storno
RETURNED Bank returned the direct debit (e.g., insufficient funds, closed account) Bank PAIN.002 report
REJECTED Validation failed (invalid consumer, missing mandate, etc.) Cash360 validation
INSTALLMENT Converted to installment payment plan Operator action

Collection Type Update

PUT /api/public/p2/v1/transaction/collection-type
API_KEY: <key>

{
  "idTransaction": 98765,
  "collectionType": "DO_NOT_COLLECT"
}

Collection types: - DO_NOT_COLLECT -- Transaction exists but no automated collection (manual payment expected) - DIRECT_DEBIT -- Collect via SEPA direct debit from consumer's bank account - DRAFT -- Draft/pending transaction, not yet submitted for collection


Payment API

The Payment API handles manual payment recording and transaction cancellation (storno).

Endpoints

Method Path Description
POST /api/public/p2/v1/payment/pay Record a payment against a transaction
PUT /api/public/p2/v1/payment/storno Cancel (storno) a transaction

Pay Request

Supports payment by internal transaction ID or external ID. Partial payments are supported.

POST /api/public/p2/v1/payment/pay
API_KEY: <key>

{
  "idTransaction": 98765,
  "amount": 29.90,
  "paymentMethod": "CASH"
}

Or by external ID:

{
  "idExternal": 100234,
  "amount": 15.00,
  "paymentMethod": "CREDIT_CARD"
}

Payment methods: CASH, CREDIT, CREDIT_CARD

Partial payments: If amount is less than the transaction's amountDue, the transaction remains open with a reduced amountDue. Multiple partial payments can be applied until the full amount is settled.

Storno (Cancel) Request

PUT /api/public/p2/v1/payment/storno
API_KEY: <key>

{
  "idTransaction": 98765,
  "reason": "Duplicate charge"
}

Webhook System

Cash360 sends HTTP POST notifications to a per-transaction webhook URL when transaction statuses change. The webhook URL is specified during transaction creation.

Basic Webhook Payload

POST <webhook-url>
Content-Type: application/json

{
  "type": "transaction",
  "transactionId": 98765
}

Extended Webhook Payload

The extended payload provides richer context, reducing the need for a follow-up GET call:

POST <webhook-url>
Content-Type: application/json

{
  "type": "transaction",
  "transactionId": 98765,
  "statusCd": "PAID",
  "collectionTypeCd": "DIRECT_DEBIT",
  "amountDue": 0.00,
  "adjustmentDescription": null,
  "beneficiaryEntityId": 1,
  "paymentMethodCd": "SEPA",
  "adjustmentTypeCd": null,
  "adjustmentStatusCd": null,
  "transactionDunningStatus": null
}

Extended Webhook Fields

Field Type Description
type string Event type (currently always "transaction")
transactionId number Cash360 internal transaction ID
statusCd string Current transaction status code
collectionTypeCd string Collection type code
amountDue decimal Remaining amount due
adjustmentDescription string Description of adjustment (if applicable)
beneficiaryEntityId number ID of the entity that benefits from this transaction
paymentMethodCd string Payment method used
adjustmentTypeCd string Type of adjustment (if applicable)
adjustmentStatusCd string Status of adjustment (if applicable)
transactionDunningStatus string Current dunning status

Retry Policy

When webhook delivery fails (non-2xx response or network timeout), Cash360 retries with exponential backoff:

Attempt Delay After Failure Cumulative Wait
1 Immediate 0
2 1 second 1s
3 2 seconds 3s
4 4 minutes ~4min
5 8 minutes ~12min
6 16 minutes ~28min
7 32 minutes ~1h
8 64 minutes ~2h
9 128 minutes ~4h
10 ~22 hours ~26h

After 10 failed attempts, the webhook is abandoned. No notification is sent about the failure.

Webhook Resend

Manually trigger webhook resend for specific transactions:

PUT /api/public/p2/v1/transaction/resend-webhook
API_KEY: <key>

{
  "transactionIds": [98765, 98766, 98767]
}

Financial Reporting API

Monthly billing statement reports providing aggregated financial data per entity.

Endpoint

Method Path Description
GET /api/public/p2/v2/billing-statement-reporting Get billing statement reports

Report Fields

Field Type Description
payoutDate date Date of the payout to the entity
totalPayoutAmount decimal Total amount paid out
fees decimal Cash360 processing fees deducted
directDebitReturnAmount decimal Amount returned due to failed direct debits
reminderCasesAmount decimal Amount in active dunning/reminder cases
specialCases decimal Amount in special handling (manual review, disputes)
pendingAmount decimal Amount still being processed
preMonthAmount decimal Carryover amount from previous month
totalExpectedAmount decimal Total expected income for the period
paymentByPeriod object Breakdown by time period

Payment By Period Breakdown

Field Type Description
paymentByPeriod.current decimal Payments received in the current billing period
paymentByPeriod.previous decimal Payments from the previous billing period
paymentByPeriod.earlier decimal Payments from earlier periods (catch-up)

Example Response

{
  "payoutDate": "2026-02-15",
  "totalPayoutAmount": 12450.00,
  "fees": 249.00,
  "directDebitReturnAmount": 89.70,
  "reminderCasesAmount": 450.00,
  "specialCases": 125.00,
  "pendingAmount": 890.00,
  "preMonthAmount": 234.50,
  "totalExpectedAmount": 14488.20,
  "paymentByPeriod": {
    "current": 11200.00,
    "previous": 1015.00,
    "earlier": 235.00
  }
}


API Improvement Proposals

1. Authentication Enhancement

Current state: Static API key passed in the API_KEY header. No expiration, no scoping, no rotation mechanism.

Issues: - API key compromise requires manual rotation and coordinated update across all integrating systems - No fine-grained permission scoping (an API key grants full access to all P2 endpoints) - No rate limiting documentation (unclear if rate limits exist or how they are enforced) - API key is exposed in example documentation (should be redacted)

Proposed improvements:

Improvement Priority Effort
Document rate limits per API key (requests/minute, requests/day) P0 Low
Add OAuth2 client credentials flow as an alternative auth method P1 Medium
Implement API key rotation with overlap period (old key valid for 24h after new key issued) P1 Medium
Add permission scopes to API keys (read-only, write, admin) P2 Medium
Add X-RateLimit-Remaining and X-RateLimit-Reset response headers P2 Low

For Membership integration, the static API key is acceptable for v1.0 but should be complemented with OAuth2 client credentials for production deployments. Membership should store the API key in an encrypted secrets vault, never in application configuration files.


2. Versioning Inconsistency

Current state: Consumer, Transaction, and Payment APIs use v1. Billing Reporting uses v2. All use the p2 public tier prefix.

Issues: - No documentation explaining when versions increment or what constitutes a breaking change - The v2 on billing reporting suggests a v1 existed but there is no deprecation notice - The URL structure /api/public/p2/v1/ is verbose and redundant

Proposed improvements: - Standardize on a single version across all endpoints (bump all to v2 when a breaking change occurs) - Document the versioning policy: what triggers a new version, how long old versions are supported, deprecation timeline - Simplify the URL structure: /api/p2/v1/consumers instead of /api/public/p2/v1/consumer - Add API-Version response header to all responses for programmatic version detection


3. Naming Inconsistencies

Current state: Multiple typos and naming convention inconsistencies exist across the API surface.

Typos

Current Name Should Be Location
sepaMandanteId sepaMandateId Bank Account
sepaMandanteDateOfSigniture sepaMandateDateOfSignature Bank Account
isoLanguag isoLanguage Consumer

These typos are in production and changing them would be a breaking change. They should be fixed in the next major version, with the old names accepted as aliases during a migration period.

Convention Inconsistencies

Issue Examples Recommended Standard
Boolean prefix inconsistency flgDunningEnabled, flgPrimary, flgTermination vs. isBlacklisted, paused Use is/has prefix consistently: isDunningEnabled, isPrimary, isTermination, isBlacklisted, isPaused
Field name case inconsistency IdExternal (PascalCase) vs. idConsumer (camelCase) Use camelCase consistently: idExternal
Status field naming collectionType (in transaction) vs. collectionTypeCd (in webhook) Use consistent naming: always collectionType or always collectionTypeCd
Resource naming (singular) /consumer, /transaction Use plural: /consumers, /transactions

4. Missing Features

4.1 Pagination

Current state: List endpoints (GET /consumer, GET /transaction) return results without pagination parameters. There is no documented way to paginate through large result sets.

Impact: A Membership entity with 10,000+ members cannot efficiently retrieve consumer or transaction lists. Responses may time out or consume excessive memory.

Proposed solution:

GET /api/public/p2/v1/consumer?page=0&size=20&sort=lastName,asc

Response envelope:

{
  "content": [...],
  "page": 0,
  "size": 20,
  "totalElements": 1234,
  "totalPages": 62,
  "sort": "lastName,asc"
}

4.2 Sorting and Ordering

Current state: No sort parameters documented on any endpoint.

Proposed solution: Add sort query parameter accepting field name and direction: sort=createdAt,desc. Support multiple sort fields: sort=status,asc&sort=dueDate,desc.

4.3 Webhook Management

Current state: Webhooks are set per-transaction during creation. There is no global webhook configuration, no way to list active webhooks, update URLs, or view delivery history.

Proposed new endpoints:

GET    /api/public/p2/v1/webhook                    -- List registered webhooks
POST   /api/public/p2/v1/webhook                    -- Register a global webhook URL
PUT    /api/public/p2/v1/webhook/{id}               -- Update webhook URL
DELETE /api/public/p2/v1/webhook/{id}               -- Remove webhook
GET    /api/public/p2/v1/webhook/{id}/deliveries    -- View delivery history
POST   /api/public/p2/v1/webhook/{id}/test          -- Send test event

4.4 Batch Transaction Status Query

Current state: Transaction status can only be queried one at a time (GET /transaction/{id}). The filter endpoint exists but its capabilities are undocumented.

Proposed solution:

POST /api/public/p2/v1/transaction/batch-status
{
  "transactionIds": [98765, 98766, 98767, 98768]
}

Response: array of { id, status, amountDue, updatedAt } objects.

4.5 Consumer Deletion / GDPR Anonymization

Current state: No endpoint exists to delete or anonymize a consumer. The API does not support the GDPR right to erasure.

Proposed new endpoints:

DELETE /api/public/p2/v1/consumer/{id}              -- Soft delete (archive)
POST   /api/public/p2/v1/consumer/{id}/anonymize    -- GDPR anonymization (irreversible)
GET    /api/public/p2/v1/consumer/{id}/data-export   -- GDPR data export (all data for consumer)

4.6 Health Check

Current state: No health check or status endpoint exists. Integrating systems cannot programmatically verify that the Cash360 API is operational.

Proposed solution:

GET /api/public/p2/v1/health

Response:

{
  "status": "UP",
  "version": "2.1.0",
  "timestamp": "2026-02-22T10:30:00Z"
}

5. Data Model Improvements

5.1 Bulk Create vs. Single Update Asymmetry

Current state: Consumer creation accepts an array (bulk), but consumer update accepts a single object. This is inconsistent and makes batch updates impossible in a single API call.

Proposed solution: Add a batch update endpoint:

PUT /api/public/p2/v1/consumer/batch
[
  { "id": 42, "email": "new@example.com" },
  { "id": 43, "street": "Updated Street 1" }
]

5.2 Contract as Embedded Object

Current state: Contract data is embedded in the consumer object during creation. There is no way to create, update, or query contracts independently.

Issues: - A consumer can have multiple contracts over time (renewal, plan change), but the embedded model implies one contract per consumer - Contract lifecycle operations (renewal, cancellation) must be performed through consumer update - No contract history or versioning

Proposed solution: Elevate contracts to a first-class resource:

POST   /api/public/p2/v1/consumer/{id}/contract       -- Create contract
GET    /api/public/p2/v1/consumer/{id}/contract        -- List contracts
GET    /api/public/p2/v1/consumer/{id}/contract/{cid}  -- Get contract
PUT    /api/public/p2/v1/consumer/{id}/contract/{cid}  -- Update contract
DELETE /api/public/p2/v1/consumer/{id}/contract/{cid}  -- Terminate contract

Current state: Consumers can only be filtered by email and external ID. No name-based search exists.

Proposed solution: Add full-text search:

GET /api/public/p2/v1/consumer/search?q=Mustermann&fields=firstName,lastName,email

Also add filter parameters for common queries:

GET /api/public/p2/v1/consumer?city=Berlin&isoCountry=DE&isBlacklisted=false

5.4 VAT Model Limitation

Current state: Transactions support two VAT rates (vatRate/vatAmount and vatRate2/vatAmount2). This is a rigid model that fails when more than two VAT rates apply.

Proposed solution: Replace the dual-field model with a VAT line items array:

{
  "amount": 100.00,
  "amountNet": 84.03,
  "vatEntries": [
    { "rate": 19.00, "amount": 15.13, "description": "Standard VAT" },
    { "rate": 7.00, "amount": 0.84, "description": "Reduced VAT" }
  ]
}

This supports any number of VAT rates and is extensible for country-specific tax requirements.

5.5 Decimal Precision

Current state: Transaction amounts are described as BigDecimal in the documentation, but JSON has no native decimal type. There is no documentation about expected precision, rounding behavior, or how string vs. number representation should be handled.

Proposed documentation additions: - All monetary amounts must be transmitted as JSON numbers with exactly 2 decimal places (e.g., 29.90, not 29.9 or 29.900) - Internal precision: 4 decimal places for intermediate calculations - Rounding: half-up (standard commercial rounding) - Currency: assumed EUR unless otherwise specified (no multi-currency support documented)


6. Webhook Improvements

6.1 No Webhook Signature Verification

Current state: Webhook payloads are sent as plain HTTP POST requests with no authentication or integrity verification. A recipient cannot verify that the webhook originated from Cash360.

Impact: Any party that knows the webhook URL can send fake status updates, potentially corrupting the integrating system's transaction state.

Proposed solution: HMAC-SHA256 signature in a header:

X-Cash360-Signature: sha256=<HMAC(webhook_secret, request_body)>
X-Cash360-Timestamp: 1708675200

The receiving system verifies the signature using a shared secret and validates the timestamp is within a 5-minute window to prevent replay attacks.

6.2 Limited Event Types

Current state: The webhook type field is always "transaction". There are no webhooks for consumer changes, billing report availability, SEPA export completion, or system events.

Proposed event types:

Event Type Trigger
transaction.status_changed Transaction status change (current functionality)
transaction.payment_received Partial or full payment recorded
consumer.created Consumer created in Cash360
consumer.updated Consumer data modified
consumer.archived Consumer archived/deactivated
bank_account.created Bank account added
bank_account.archived Bank account deactivated
billing_report.available Monthly billing report ready
sepa_export.completed SEPA XML export file generated

6.3 Basic vs. Extended Payload

Current state: Two webhook payload formats exist (basic and extended). The basic version contains only type and transactionId, requiring a follow-up GET call for any useful information.

Proposed solution: Deprecate the basic payload. The extended payload should be the only format. The basic payload provides insufficient information for any real-world integration -- every consumer of the basic webhook must immediately make a GET call, doubling API traffic.

6.4 No Consumer Change Webhooks

Current state: Webhooks exist only for transactions. If consumer data is modified directly in Cash360 (e.g., by an operator), the integrating system has no way to know without polling.

Proposed solution: Add consumer lifecycle webhooks (see event types above).

6.5 No Dead Letter Queue or Dashboard

Current state: After 10 failed delivery attempts, the webhook is silently abandoned. No notification, no dashboard, no retry mechanism.

Proposed improvements: - Add a webhook delivery dashboard showing recent deliveries, failures, and retry status - Persist failed webhooks in a dead letter queue accessible via API - Send an email notification to the entity admin when webhook delivery fails repeatedly - Add a bulk resend endpoint for all failed webhooks in a date range


7. Error Handling

7.1 No Standardized Error Response Format

Current state: Error response format is not documented. It is unclear whether errors return HTTP status codes, error bodies, or both. Different endpoints may return different error structures.

Proposed standard error response:

HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Consumer creation failed: 2 of 3 items have validation errors",
    "timestamp": "2026-02-22T10:30:00Z",
    "requestId": "req-abc-123",
    "details": [
      {
        "index": 0,
        "field": "iban",
        "code": "INVALID_IBAN",
        "message": "The IBAN 'DE123' is not a valid International Bank Account Number"
      },
      {
        "index": 2,
        "field": "email",
        "code": "DUPLICATE_EMAIL",
        "message": "A consumer with email 'max@example.com' already exists"
      }
    ]
  }
}

7.2 No Error Code Catalog

Proposed error code catalog:

Code HTTP Status Description
AUTHENTICATION_FAILED 401 Invalid or missing API key
FORBIDDEN 403 API key does not have permission for this operation
NOT_FOUND 404 Requested resource does not exist
VALIDATION_ERROR 422 Request body failed validation
DUPLICATE_ENTRY 409 Unique constraint violation (email, external ID, mandate ID)
INVALID_IBAN 422 IBAN checksum validation failed
INVALID_STATUS_TRANSITION 422 Requested status change is not valid from current status
TRANSACTION_ALREADY_SETTLED 422 Cannot modify a settled transaction
CONSUMER_BLACKLISTED 422 Operation not allowed for blacklisted consumer
PAST_DUE_DATE 422 Due date cannot be in the past
RATE_LIMIT_EXCEEDED 429 Too many requests
INTERNAL_ERROR 500 Unexpected server error

7.3 Bulk Operation Error Behavior

Current state: Unclear whether bulk operations (consumer create, transaction create) are all-or-nothing (atomic) or partial (best-effort). If one item in a batch of 50 fails validation, are the other 49 created?

Proposed behavior: Partial success with detailed reporting: - Each item in the batch is validated independently - Successfully validated items are created - Failed items are returned in the error details array with their batch index - Response HTTP status: 207 Multi-Status for partial success, 201 Created for full success, 422 Unprocessable Entity for full failure


8. Security Improvements

8.1 API Key Exposure

Current state: API keys appear in plain text in example documentation and Postman collections. No guidance on secure storage.

Proposed improvements: - Redact all API keys in documentation (use <your-api-key> placeholder) - Document secure storage recommendations (environment variables, secrets vault) - Add API key audit log (who used the key, when, from which IP)

8.2 IP Whitelisting

Current state: No IP-based access restriction is documented.

Proposed improvement: Add IP whitelist configuration per API key:

POST /api/admin/api-key/{id}/ip-whitelist
{
  "allowedIps": ["203.0.113.10/32", "198.51.100.0/24"]
}

When configured, requests from non-whitelisted IPs are rejected with 403 Forbidden.

8.3 Request/Response Encryption

Current state: TLS is assumed but not explicitly documented. No additional encryption layer for sensitive fields.

Proposed improvements: - Explicitly document TLS 1.2+ requirement - Add certificate pinning documentation for high-security integrations - Consider field-level encryption for IBAN and bank account data in transit (beyond TLS)


9. Proposed API Redesign (Future Version)

The following outlines a complete redesign for a future major version of the My Factura Public API, incorporating all improvements above and modern API design best practices.

9.1 Design Principles

  1. OpenAPI 3.1 specification -- Machine-readable API contract, auto-generated client SDKs
  2. RESTful resource naming -- Plural nouns, consistent URL structure
  3. Standard pagination -- Page, size, sort query parameters on all list endpoints
  4. Standard error format -- Consistent error envelope with codes, messages, and field-level details
  5. HMAC-SHA256 webhook signatures -- Cryptographic verification of webhook authenticity
  6. First-class sub-resources -- Contracts and bank accounts as independently manageable resources
  7. GDPR compliance -- Data export and anonymization endpoints built in
  8. OAuth2 authentication -- Client credentials flow for service-to-service communication

9.2 Proposed URL Structure

Authentication: OAuth2 Bearer token or API_KEY header (backward compatible)

# Consumers
GET    /api/p2/v2/consumers                           -- List (paginated)
POST   /api/p2/v2/consumers                           -- Create (single or bulk)
GET    /api/p2/v2/consumers/{id}                       -- Get by ID
PUT    /api/p2/v2/consumers/{id}                       -- Update
DELETE /api/p2/v2/consumers/{id}                       -- Archive (soft delete)
GET    /api/p2/v2/consumers/search                     -- Full-text search
POST   /api/p2/v2/consumers/{id}/anonymize             -- GDPR anonymize
GET    /api/p2/v2/consumers/{id}/data-export            -- GDPR data export

# Contracts (first-class resource)
GET    /api/p2/v2/consumers/{id}/contracts              -- List contracts
POST   /api/p2/v2/consumers/{id}/contracts              -- Create contract
GET    /api/p2/v2/consumers/{id}/contracts/{cid}        -- Get contract
PUT    /api/p2/v2/consumers/{id}/contracts/{cid}        -- Update contract
POST   /api/p2/v2/consumers/{id}/contracts/{cid}/cancel -- Cancel contract

# Bank Accounts
GET    /api/p2/v2/consumers/{id}/bank-accounts          -- List bank accounts
POST   /api/p2/v2/consumers/{id}/bank-accounts          -- Create bank account
GET    /api/p2/v2/consumers/{id}/bank-accounts/{bid}    -- Get bank account
PUT    /api/p2/v2/consumers/{id}/bank-accounts/{bid}    -- Update bank account
DELETE /api/p2/v2/consumers/{id}/bank-accounts/{bid}    -- Archive bank account
POST   /api/p2/v2/consumers/{id}/bank-accounts/{bid}/set-primary -- Set primary

# Transactions
GET    /api/p2/v2/transactions                          -- List (paginated, filterable)
POST   /api/p2/v2/transactions                          -- Create (single or bulk)
GET    /api/p2/v2/transactions/{id}                     -- Get by ID
POST   /api/p2/v2/transactions/batch-status             -- Batch status query
PUT    /api/p2/v2/transactions/{id}/collection-type     -- Update collection type

# Payments
POST   /api/p2/v2/transactions/{id}/payments            -- Record payment
POST   /api/p2/v2/transactions/{id}/storno              -- Cancel transaction

# Webhooks
GET    /api/p2/v2/webhooks                              -- List registered webhooks
POST   /api/p2/v2/webhooks                              -- Register global webhook
PUT    /api/p2/v2/webhooks/{id}                         -- Update webhook
DELETE /api/p2/v2/webhooks/{id}                         -- Remove webhook
GET    /api/p2/v2/webhooks/{id}/deliveries              -- Delivery history
POST   /api/p2/v2/webhooks/{id}/test                    -- Send test event

# Reporting
GET    /api/p2/v2/billing-reports                       -- Billing statement reports

# Health
GET    /api/p2/v2/health                                -- Service health check

9.3 Naming Convention Fix Summary

Current (v1) Proposed (v2) Backward Compatibility
sepaMandanteId sepaMandateId Accept both in v2, remove old in v3
sepaMandanteDateOfSigniture sepaMandateDateOfSignature Accept both in v2, remove old in v3
isoLanguag isoLanguage Accept both in v2, remove old in v3
flgDunningEnabled isDunningEnabled Accept both in v2, remove old in v3
flgPrimary isPrimary Accept both in v2, remove old in v3
flgTermination isTermination Accept both in v2, remove old in v3
IdExternal idExternal Accept both in v2, remove old in v3
/consumer (singular) /consumers (plural) v1 routes remain active during migration
/transaction (singular) /transactions (plural) v1 routes remain active during migration

9.4 Standard Pagination Response

All list endpoints return a paginated envelope:

{
  "content": [
    { "id": 1, "firstName": "Max", ... },
    { "id": 2, "firstName": "Anna", ... }
  ],
  "pagination": {
    "page": 0,
    "size": 20,
    "totalElements": 1234,
    "totalPages": 62
  },
  "sort": [
    { "field": "lastName", "direction": "asc" }
  ]
}

9.5 Standard Error Response

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "requestId": "req-abc-123-def",
    "timestamp": "2026-02-22T10:30:00Z",
    "details": [
      {
        "field": "iban",
        "code": "INVALID_IBAN",
        "message": "IBAN checksum validation failed",
        "rejectedValue": "DE123"
      }
    ]
  }
}

9.6 Webhook Signature

Every webhook delivery includes verification headers:

X-Cash360-Signature: sha256=5d7a9f1e3c4b2a8d6e0f7c9b1a3d5e7f...
X-Cash360-Timestamp: 1708675200
X-Cash360-Event: transaction.status_changed
X-Cash360-Delivery-Id: dlv-abc-123

Verification algorithm:

payload = timestamp + "." + request_body
expected = HMAC-SHA256(webhook_secret, payload)
verify: expected == signature
verify: abs(now - timestamp) < 300 seconds

Membership Integration Strategy

Based on the current API capabilities and the identified limitations, Membership adopts the following integration strategy:

Consumer Synchronization

  1. When a member is created in Membership, a corresponding consumer is created in Cash360 via POST /consumer with the Membership member ID as IdExternal
  2. Member profile updates that affect billing (name, address, email) are propagated to Cash360 via PUT /consumer/{id}
  3. The Cash360 consumer ID is stored in the Membership Member.externalBillingId field for bidirectional linking
  4. Consumer lookup uses GET /consumer/external/{id} with the Membership member ID

Transaction Flow

  1. Membership's billing engine calculates due amounts from active contracts
  2. Transactions are submitted in bulk via POST /transaction with the Membership transaction ID as idExternal
  3. Each transaction specifies a webhook URL pointing to POST /api/webhooks/cash360/transaction
  4. Status updates arrive via webhook; a polling fallback runs every 15 minutes for reliability

Bank Account Management

  1. SEPA mandates are collected in Membership and registered in Cash360 via the bank account endpoints
  2. Membership generates the mandate reference: MBR-{entityId}-{memberId}-{seq}
  3. The primary bank account is used for direct debit collection

Financial Reporting

  1. Monthly billing reports are fetched via the reporting endpoint
  2. Data feeds into Membership's financial dashboard
  3. Discrepancies between Membership's expected and Cash360's reported amounts trigger alerts

Workarounds for Missing Features

Missing Feature Workaround
No pagination Fetch all, cache locally, paginate in Membership
No consumer name search Maintain a local consumer index synced from Cash360
No webhook signatures Validate webhook source IP + require HTTPS
No batch status query Poll individual transactions, rate-limit to avoid overload
No GDPR anonymization Anonymize in Membership, update Cash360 consumer with anonymized data
No health check Periodic GET on a known consumer ID as a health probe