Zum Hauptinhalt springen
Ein Vertriebskanal (Sales Channel) bildet eine Marke deiner Organisation ab und bestimmt Branding, Domain, erlaubte Zahlungsmethoden, Coupon-Verhalten und Kundenbereich. Diese Seite erklärt das Verhalten aus Entwicklersicht. Die fachliche Einführung findest du unter Vertriebskanäle.
Jede Organisation hat immer einen Kanal mit dem technischen Namen default. Er ist der universelle Fallback und kann nicht gelöscht werden. Vertriebskanäle sind organisatorisch, sie isolieren keine Daten. Kunden, Subscriptions, Rechnungen, Produkte und Preise bleiben organisationsweit.
In den Beispielen ist $BASE_URL die Basis-URL deiner Fynn-API und $API_TOKEN ein API-Token, das als Authorization: Bearer mitgeschickt wird.

Kanal-Auflösung

Fynn löst den aktiven Vertriebskanal pro eingehendem Request auf, nachdem der Tenant feststeht. Es gewinnt der erste Treffer in dieser Kette:
1

Header X-Sales-Channel-Id

Expliziter Kanal per Header X-Sales-Channel-Id: <uuid>. Wird für interne Wallet- und Tool-Zugriffe genutzt, wenn der Aufrufer den Kanal kennt.
2

Header X-Cart-Token

X-Cart-Token: <token> löst den Warenkorb auf; dessen Kanal stammt aus dem CheckoutLink. Das ist der reguläre Weg im Checkout der Kundenfront.
3

Domain (Host / Origin / Referer)

Der Host der Anfrage wird über die Customerfront-Domain zum Kanal aufgelöst. Bei Cross-Origin-Aufrufen der Kundenfront-SPA (der Host ist dann die API-Domain) wird zusätzlich Origin, dann Referer, dann X-Source-Domain herangezogen, dieselbe Header-Reihenfolge, mit der auch der Tenant aufgelöst wird.
4

Default-Kanal

Greift keiner der Schritte, wird der default-Kanal der Organisation verwendet.
Cross-Tenant-Guard: Jeder aufgelöste Kanal wird gegen den Tenant des Requests geprüft. Gehört ein per Header oder Cart-Token referenzierter Kanal zu einer anderen Organisation, wird er still verworfen, und die Kette läuft zum nächsten Schritt weiter. Auf einen fremden Kanal fällt sie nie zurück.
Praktische Konsequenz: Verbindest du eine eigene Domain mit einem Kanal (siehe Eigene Domains), erreichen Aufrufe über diese Domain automatisch den richtigen Kanal, ohne dass du einen Header setzen musst. Eine Domain ist immer genau einem Kanal zugeordnet.

Der salesChannel am Kunden

Jeder Kunde gehört genau einem Vertriebskanal an. Die Zuordnung steuert, welches Branding der Kunde in Bestätigungen, Dokumenten und im Self-Service sieht.

Beim Anlegen und Aktualisieren (Eingabe)

Die Felder POST /customers und PATCH /customers/{id} akzeptieren ein optionales Feld salesChannel:
  • Wert ist die UUID oder der technische Name des Kanals (zum Beispiel "default").
  • Der Kanal muss existieren und zur aktuellen Organisation gehören, andernfalls antwortet die API mit 422 und einer Validierungsmeldung am Feld.
  • Lässt du das Feld bei der Anlage leer, weist Fynn den aktuell aufgelösten bzw. den default-Kanal zu.
Kunde einem Kanal zuordnen
curl -X POST "$BASE_URL/customers" \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "company",
    "companyName": "Acme GmbH",
    "salesChannel": "b2b-shop"
  }'
Kanal eines Kunden ändern
curl -X PATCH "$BASE_URL/customers/{id}" \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "salesChannel": "default" }'

In der Ausgabe (Lesen)

In Kunden-Antworten (sowie eingebettet in CheckoutLink, Cart und Webhook-Envelopes) erscheint der Kanal als kompakte Referenz:
{
  "id": "0f9b…",
  "companyName": "Acme GmbH",
  "salesChannel": {
    "id": "45d60990-cde6-4139-b64f-79b79c6c6d98",
    "name": "b2b-shop",
    "brandName": "Acme B2B"
  }
}
FeldBedeutung
idUUID des Kanals.
nameStabiler technischer Name (zum Beispiel default). Eindeutig pro Organisation, nicht änderbar.
brandNameAnzeigename der Marke für E-Mails, PDFs und Checkout.

Vertriebskanäle verwalten

Die folgenden Endpunkte verwalten Kanäle und ihre Einstellungen. Sie erfordern ein API-Token mit der Berechtigung sales-channel:read (lesend) bzw. sales-channel:write (schreibend).
MethodePfadZweckBerechtigung
GET/api/sales-channelsAlle Kanäle der Organisation auflistensales-channel:read
POST/api/sales-channelsKanal anlegensales-channel:write
GET/api/sales-channels/{id}Einen Kanal abrufensales-channel:read
PATCH/api/sales-channels/{id}Stammdaten, Coupons, Zahlungsmethoden, Weiterleitungs-URLs ändernsales-channel:write
DELETE/api/sales-channels/{id}Kanal löschen (nur ohne Referenzen, nicht den default)sales-channel:write
PATCH/api/sales-channels/{id}/appearanceLogo, Farben, Rechtstexte ändernsales-channel:write
GET · PATCH/api/sales-channels/{id}/customer-areaKundenbereich lesen/ändernsales-channel:read · write
GET · PATCH/api/sales-channels/{id}/email-settingsAbsender, Sendedomain, Mail-Vorlagesales-channel:read · write
GET · PATCH/api/sales-channels/{id}/document-settingsBelegvorlagen (Logo-Position, Fußbereich)sales-channel:read · write
GET/api/sales-channels/{id}/domainStandard-Domain des Kanalssales-channel:read
GET/api/sales-channels/payment-methods/availableVerfügbare Zahlungsmethoden (aktive Gateways)sales-channel:read

Kanal anlegen

curl -X POST "$BASE_URL/api/sales-channels" \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "b2b-shop",
    "brandName": "Acme B2B",
    "websiteUrl": "https://b2b.acme.example"
  }'
FeldPflichtRegeln
namejaTechnischer Name. Nur Kleinbuchstaben, Ziffern und Bindestriche (^[a-z0-9-]+$), 1 bis 255 Zeichen. Nicht mehr änderbar.
brandNamejaAnzeigename der Marke, 1 bis 255 Zeichen.
websiteUrlneinMuss https sein.
Beim Anlegen wird automatisch eine Standard-Customerfront-Domain aus name und einem festen Suffix erzeugt. Der name ist später unveränderlich, weil er in URLs und Logs auftaucht.

Kanal aktualisieren

PATCH /api/sales-channels/{id} ist partiell, nur übergebene Felder werden geändert.
curl -X PATCH "$BASE_URL/api/sales-channels/{id}" \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "allowCoupons": false,
    "allowedPaymentMethods": ["sepa_direct_debit"],
    "paymentSuccessUrl": "https://b2b.acme.example/danke"
  }'
FeldTypBedeutung
brandNamestringAnzeigename der Marke.
websiteUrlstring (https)„Zurück zur Website”-Link.
phonestringKontakt-Telefonnummer.
allowCouponsbooleanKanal-Gate für Coupons (siehe unten).
allowedPaymentMethodsstring[]Whitelist der Zahlungsmethoden. Leer = keine Einschränkung. Jeder Wert muss auf ein aktives Gateway abbildbar sein.
paymentSuccessUrl paymentCancelUrl paymentFailureUrl paymentPendingUrl returnUrlstring (https)Weiterleitungs-URLs nach dem Zahlvorgang.

Vollständige Kanal-Repräsentation

GET /api/sales-channels/{id} liefert die volle Sicht inklusive eingebetteter Appearance-Felder:
{
  "id": "45d60990-cde6-4139-b64f-79b79c6c6d98",
  "name": "default",
  "isDefault": true,
  "brandName": "myLife AG",
  "websiteUrl": "https://mylife.de",
  "phone": "+49 30 1234567",
  "allowCoupons": true,
  "allowedPaymentMethods": [],
  "paymentSuccessUrl": null,
  "paymentCancelUrl": null,
  "paymentFailureUrl": null,
  "paymentPendingUrl": null,
  "returnUrl": null,
  "logoUrl": "https://…/logo.svg",
  "primaryColor": "#000000",
  "secondaryColor": "#FF0000",
  "privacyUrl": "https://mylife.de/datenschutz",
  "conditionsUrl": "https://mylife.de/agb",
  "createdAt": "2026-01-10T08:00:00+00:00",
  "updatedAt": "2026-06-17T12:00:00+00:00"
}

Coupon-Gate

allowCoupons ist ein Kanal-weites Gate. Steht es auf false, sind Coupons in keinem CheckoutLink dieses Kanals einlösbar, egal was der einzelne CheckoutLink erlaubt. Ein CheckoutLink kann Coupons nur weiter einschränken, nie gegen den Kanal erlauben.
Kanal allowCouponsCheckoutLinkEinlösbar
trueerlaubtja
trueverbotennein
falseerlaubtnein (Kanal gewinnt)
falseverbotennein
Neu angelegte Kanäle haben allowCoupons = true.

Zahlungsmethoden-Whitelist

allowedPaymentMethods wirkt als Whitelist:
  • Leeres Array → keine kanalspezifische Einschränkung; alle Methoden mit aktivem Gateway sind erlaubt.
  • Befülltes Array → nur die gelisteten Methoden, sofern ihr Gateway aktiv konfiguriert ist.
Beim Speichern validiert Fynn, dass jeder Wert auf ein für die Organisation aktiv konfiguriertes Payment-Gateway abbildbar ist. Die im Kontext verfügbaren Methoden liefert GET /api/sales-channels/payment-methods/available.

Eigene Domains

Jeder Kanal hat genau eine Standard-Domain (automatisch, nicht editierbar) und optional eine eigene Domain. Ein einzelner CNAME genügt, das TLS-Zertifikat wird automatisch ausgestellt und erneuert. Es gibt keinen selbstverwalteten TXT-Nachweis.
MethodePfadZweck
GET/api/sales-channels/{id}/custom-domainDomain + DNS-Anweisungen + Status abrufen
POST/api/sales-channels/{id}/custom-domainEigene Domain hinzufügen
POST/api/sales-channels/{id}/custom-domain/verifyStatus sofort prüfen („Verifizieren”)
DELETE/api/sales-channels/{id}/custom-domainEigene Domain entfernen

Domain hinzufügen

curl -X POST "$BASE_URL/api/sales-channels/{id}/custom-domain" \
  -H "Authorization: Bearer $API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "host": "shop.example.com" }'
Die Antwort enthält die DNS-Anweisungen und beide Status:
{
  "id": "…",
  "host": "shop.example.com",
  "status": "pending",
  "sslStatus": "pending_validation",
  "isDefault": false,
  "dnsInstructions": [
    { "type": "CNAME", "name": "shop.example.com", "value": "cname.customerfront.app" }
  ],
  "note": "Lege den CNAME-Eintrag bei deinem DNS-Anbieter an. Das TLS-Zertifikat wird automatisch ausgestellt, sobald der CNAME auflöst …"
}
dnsInstructions enthält immer den CNAME und zusätzlich etwaige Validierungs-Einträge, die du ebenfalls anlegen musst.

Die zwei Status

Eine eigene Domain hat zwei voneinander unabhängige Status:
FeldBedeutungWerte
statusDNS-Kontrolle / Verifizierungpending, verified
sslStatusTLS-Lebenszykluspending, pending_validation, active, error
Die Domain wird erst produktiv (und zur Standard-Adresse des Kanals), wenn sslStatus = active ist. Ab da löst Fynn Anfragen über diese Domain auf den Kanal auf.

Verifizieren

POST /api/sales-channels/{id}/custom-domain/verify prüft den Status einmal synchron:
  • Ist der Status active, wird die Domain verifiziert und zur Standard-Customerfront-Domain des Kanals befördert.
  • pending / pending_validation werden ohne Fehler zurückgegeben, versuche es später erneut.
  • error (das Zertifikat kann nicht ausgestellt werden) führt zu einer Fehlerantwort.
Du musst nicht aktiv pollen: Ein wiederkehrender Reconcile-Job prüft den SSL-Status im Hintergrund und schaltet die Domain frei, sobald das Zertifikat steht. Der Verify-Endpunkt ist nur der „jetzt prüfen”-Pfad und idempotent, eine bereits aktive Domain bleibt unverändert.

Entfernen

DELETE /api/sales-channels/{id}/custom-domain entfernt die eigene Domain und setzt die Kanal-Adresse auf die Standard-Domain zurück. Der Checkout bleibt also durchgehend erreichbar.

Weiterleitungs-URLs und returnUrl

Die optionalen URLs auf dem Kanal steuern, wohin der Kunde nach dem Zahlvorgang geleitet wird. Bleibt eine URL leer, fällt der Kunde auf die Standardseite der Kundenfront (/self-service/transaction/{status}) zurück.
FeldAuslöser
paymentSuccessUrlStatus booked / captured (erfolgreich).
paymentCancelUrlKunde bricht beim Anbieter ab.
paymentFailureUrlExplizite Ablehnung durch den Anbieter.
paymentPendingUrlStatus pending / authorized (zum Beispiel wartende SEPA-Lastschrift).
returnUrlNach einem API-getriebenen Payment-Method-Setup (PaymentMethodSource::Api).
Startest du Payment-Method-Setups über die API, wird nach Abschluss die Kanal-returnUrl aufgerufen. Lässt du sie leer, kannst du pro Request dynamisch ein redirectUrl im PaymentMethodSetup mitgeben, das stattdessen verwendet wird.

Webhooks

Jeder ausgehende Webhook trägt den Header X-Sales-Channel mit dem technischen Namen des Kanals, dem das auslösende Objekt zugeordnet ist (oder default, wenn keiner gesetzt ist). So routest du eingehende Events empfängerseitig nach Marke, ohne den Payload zu parsen.
POST /your-webhook-endpoint HTTP/1.1
X-Sales-Channel: b2b-shop
Content-Type: application/json

Verwandte Themen

Vertriebskanäle (Anleitung)

Die Einstellungen in der Wallet, Schritt für Schritt mit Screenshots.

Kunden

Kundenverwaltung und das salesChannel-Feld im Kontext.

Eigene E-Mail-Domain

Sendedomain pro Kanal einrichten.

Webhooks

Echtzeit-Events empfangen und nach Kanal routen.