Zum Hauptinhalt springen

Java-SDK

Sender implementieren

Sendende Systeme übertragen Anträge (Submissions) über FIT-Connect an einen Empfänger. Das Senden von Anträgen ist hier beschrieben.

Das Java-SDK setzt Details wie Validierungen und Verschlüsselung intern um, sodass Sie sich bei der Anbindung auf die Implementierung der Schnittstelle fokussieren können.

Die folgende Beschreibung zeigt, wie Sie mit dem Java-SDK eine Submission erzeugen und an einen Zustellpunkt senden. Zudem wird gezeigt, wie Sie die bidirektionale Kommunikation (BiDiKo) in FIT-Connect verwenden.

Konfiguration laden

Für die Implementierung eines Senders benötigen Sie zunächst die Konfigurationsdatei config.yml. Hier ist beschrieben, wie Sie Informationen für einen Sender in diese Datei eintragen. Sie laden die Konfiguration mit dem ApplicationConfigLoader, wie das folgende Beispiel zeigt:

final ApplicationConfig config = ApplicationConfigLoader.loadConfigFromPath(Path.of("path/to/config.yml"));

Sender erstellen

Im nächsten Schritt wird ein SenderClient benötigt, der mittels der ClientFactory erzeugt werden kann.

final SenderClient senderClient = ClientFactory.getSenderClient(config);

Submission erzeugen und senden

Das Java-SDK kann im Umfeld einer Webapplikation im Backend eingesetzt werden. Bei diesem Anwendungsfall können die Daten sowohl verschlüsselt (zum Beispiel über das JavaScript-SDK, noch in Entwicklung) als auch unverschlüsselt vom Browser an das Java-SDK im Backend übergeben werden. Überträgt das Frontend Antragsdaten unverschlüsselt an das Backend, dann verschlüsselt das Java-SDK intern die Daten und überträgt diese über FIT-Connect an das adressierte Fachverfahren (Subscriber).

Für die Erstellung einer Submission stehen daher zwei Builder bereit, um alle notwendigen Daten zu setzen:

  • SendableEncryptedSubmission.Builder() für Submissions mit bereits verschlüsselten Daten
  • SendableSubmission.Builder() für Submissions mit noch nicht verschlüsselten Daten
Zustellpunkt finden

Liegt Ihnen die destinationId nicht vor, dann können Sie sie mithilfe des RoutingClient über findDestinations erfragen.

Bereits verschlüsselte Daten übergeben

Werden die Antragsdaten bereits vom Endgerät der Anwender:in verschlüsselt, dann ist eine Ende-zu-Ende-Verschlüsselung möglich und die Antragsdaten liegen im Backend des Onlinedienstes (des Senders) verschlüsselt vor. Das Backend des Onlinedienstes (das Java-SDK) hat dabei keinen Einblick in die Klardaten.

Zur Verschlüsselung von Antragsdaten im Frontend eines Onlinedienstes (im Browser der Antragsteller:in) kann das JavaScript-SDK von FIT-Connect genutzt werden. Das JavaScript-SDK benötigt für die Verschlüsselung der Antragsdaten den PublicKey des Zustellpunktes, an den der Antrag über FIT-Connect gesendet werden soll. Das JavaScript-SDK im Frontend kann jedoch nicht direkt den PublicKey vom Zustelldienst abrufen. Dafür benötigt es die Hilfe des Backends (des Java-SDKs).

Deshalb müssen Sie im Backend des Onlinedienstes den PublicKey vom Zustelldienst abrufen und für die Abfrage durch das JavaScript-SDK (durch das Frontend) zur Verfügung stellen. Das folgende Beispiel ruft den öffentlichen Schlüssel einer Destination (eines Zustellpunktes) ab.

Die Validierung des öffentlichen Schlüssels erfolgt automatisch durch das Java-SDK gemäß der kryptographischen Vorgaben. Weitere Informationen zur Ende-zu-Ende-Verschlüsselung finden Sie hier.

Schritte zur Übertragung verschlüsselter Daten

1. Public Key abrufen und für das Frontend zur Verfügung stellen

Sie rufen den PublicKey eines Zustellpunkts (einer Destination) mit der Methode getPublicKeyForDestination des Sender-Clients ab:

final SenderClient senderClient = ClientFactory.getSenderClient(config);

// Demo-Destination
final var destinationId = UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14");

final String publicJwkAsJsonString = senderClient.getPublicKeyForDestination(destinationId);

Das Frontend des Onlinedienstes ruft nun den öffentlichen Schlüssel vom Backend ab. Dafür müssen die Entwickler:innen des Onlinedienstes eine entsprechende Funktion implementieren.

Mit dem PublicKey des Zustellpunktes verschlüsselt das Frontend des Onlinedienstes die Antragsdaten und sendet die Daten verschlüsselt an das Backend.

Hier finden Sie eine Beschreibung des Zusammenspiels von Front- und Backend.

2. Submission für verschlüsselte Daten erstellen und senden

Sie verwenden den SendableEncryptedSubmission.Builder, um eine Submission für verschlüsselte Daten zu erstellen. Bei der Angabe der (optionalen) Attachments muss die UUID für jedes Attachment vom Frontend zur Verfügung gestellt werden. Dies ist notwendig, da das SDK die Attachments (Anhänge) zunächst beim Zustelldienst ankündigen muss, jedoch keinen Zugriff auf die verschlüsselten (Meta-) Daten hat, die die IDs der Attachments enthalten.

final SenderClient senderClient = ClientFactory.getSenderClient(config);

final SendableEncryptedSubmission encryptedSubmission = SendableEncryptedSubmission.Builder()
.setDestination(UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"))
.setServiceType("urn:de:fim:leika:leistung:99900000000000", "FIT-Connect Demo")
.setEncryptedMetadata("$encrpyt€ed metadata")
.setEncryptedData("{$encrpyt€ed json}")
.addEncryptedAttachment(UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"), "$encrpyt€ed @tt@chment") // optional
.build();

final SentSubmission sentSubmission = senderClient.send(encryptedSubmission);
Hinweis

Wenn die destinationId und der serviceIdentifier (im Beispiel oben urn:de:fim:leika:leistung:99900000000000) vom Frontend bereitgestellt werden, dann prüft das SDK, ob der angegebene Service-Typ an der spezifischen Destination erlaubt ist.

Noch nicht verschlüsselte Daten übergeben

Sie übergeben der Methode SendableSubmission.Builder() unverschlüsselte Daten, die dann intern verschlüsselt werden. Wenn das Frontend Antragsdaten unverschlüsselt an das Backend sendet, dann können Sie die Antragsdaten im Backend mit SendableSubmission.Builder() in ein verschlüsseltes Submission-Objekt umwandeln und verschlüsselt über FIT-Connect an das adressierte Fachverfahren (Subscriber) übertragen.

final SenderClient senderClient = ClientFactory.getSenderClient(config);

final SendableSubmission sendableSubmission = SendableSubmission.Builder()
.setDestination(UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"))
.setServiceType("urn:de:fim:leika:leistung:99900000000000", "FIT Connect Demo")
.setJsonData("{\"message\":\"Hello World\"}", URI.create("https://test.fitko.dev/fit-connect/schema/submission-data-schema.json"))
// optional properties
.addAttachment(Attachment.fromPath(Path.of("path/to/attachment.txt"), "text/plain"))
.setReplyChannel(ReplyChannel.fromEmail("test@mail.org"))
.build();

final SentSubmission sentSubmission = senderClient.send(sendableSubmission);
Hinweis

Diese Umsetzungsvariante bietet eine geringere Sicherheit als die Umsetzung der Ende-zu-Ende-Verschlüsselung ab dem Endgerät (Webbrowser) der Antragsteller:in. Zudem steigen die Anforderungen an die IT-Sicherheit und den Datenschutz, da Antragsdaten im Klartext durch das Backend des Onlinedienstes verarbeitet werden.
Eine Alternative stellt die Verschlüsselung von Antragsdaten im Frontend des Onlinedienstes dar (siehe Abschnitt Verschlüsselte Daten übertragen).

Submission Status abfragen

Nach der Einreichung einer Submission können Sie den aktuellen Status aus dem Event-Log mit getSubmissionStatus abfragen:

final SenderClient senderClient = ClientFactory.getSenderClient(config);
final SentSubmission sentSubmission = senderClient.send(submission);

final EventStatus submissionStatus = senderClient.getSubmissionStatus(sentSubmission);

LOGGER.info("Current status for submission {} => {}", sentSubmission.getSubmissionId(), submissionStatus.getStatus());

Das folgende Beispiel zeigt die Ausgabe des aktuellen Status einer Submission, nachdem sie angelegt wurde:

Current status for submission 43cf7163-5163-4bc8-865e-be96e271ecc3 => incomplete

Das folgende Diagramm zeigt alle Zustandsübergänge einer Submission, vom Anlegen der Submission bis zur Abholung durch den Empfänger. Klicken Sie auf das Diagramm, um es zu vergrößern.

Status

Bidirektionale Kommunikation (BiDiKo)

Dialog zwischen Sender und Empfänger

Nach dem Versenden der ersten Submission ist in FIT-Connect ein direkter Dialog zwischen Sender und Empfänger möglich, wenn Sie die Bidirektionale Kommunikation (BiDiKo) verwenden.

Bei BiDiKo tauschen Sender und Empfänger direkt Nachrichten miteinander aus:

  • Der Sender sendet einen Antrag (auch Einreichung oder Submission genannt) über FIT-Connect an den Empfänger mit den für die Bidirektionale Kommunikation notwendigen Informationen. Der Sender wird im SDK auch Sender-Client genannt.
  • Der Empfänger antwortet auf diese Einreichung, indem er eine Nachricht über FIT-Connect an den Sender zurücksendet. Diese Rücksendung kann zum Beispiel ein Bescheid oder eine Rückfrage zum eingereichten Antrag sein. Die Rücksendung des Empfängers wird als Antwort oder Reply bezeichnet. Der Empfänger wird im SDK auch Subscriber oder Subscriber-Client genannt.
  • Der Sender kann nun auf die Antwort reagieren und eine neue Einreichung an den Empfänger senden. Alle Einreichungen als auch alle dazugehörigen Antworten (Replies) werden über dieselbe Vorgangsnummer (Case-ID) identifiziert. Über diesen Bezug auf diese Case-ID können nun beide Parteien beliebig oft und in beliebiger Reihenfolge Nachrichten versenden.

Um die BiDiKo zu nutzen, verwenden Sie FIT-Connect als Rückkanal (ReplyChannel):
Rufen Sie am SendableSubmission.Builder() die Methode setReplyChannel(...) auf und setzen Sie den ReplyChannel auf ReplyChannel.fromFitConnect() (siehe Beispiel weiter unten zum SendableSubmission.Builder).

Schlüsselpaar erzeugen

Um dem Empfänger (z. B. einem Fachverfahren) die Möglichkeit zu geben, Antworten (Replies) zu verschlüsseln, müssen Sie im ReplyChannel einen öffentlichen Verschlüsselungsschlüssel übergeben. Der Empfänger muss diesen öffentlichen Schlüssel verwenden, um die Antworten zu verschlüsseln.
Sie können den öffentlichen Schlüssel mit der Utility-Klasse ReplyChannelKeyGenerator erzeugen (Für den Rückkanal ist kein V-PKI Zertifikat notwendig). Der Key-Generator generiert ein Schlüsselpaar mit einem öffentlichen Verschlüsselschlüssel und dem dazugehörigen privaten Entschlüsselungsschlüssel. Der private Schlüssel diese Schlüsselpaars wird vom Sender (z. B. einem Onlinedienst) benötigt, um die Replies des Empfängers zu entschlüsseln. Sie sollten ein Schlüsselpaar für die Dauer eines Vorgangs (Case) beibehalten. Das Schlüsselpaar sollte nur bei sicherheitskritischen Vorfällen geändert werden.

// Onlinedienst erzeugt Schlüsselpaar (einmal pro Case)
final JWKPair jwkPair = ReplyChannelKeyGenerator.generateKeyPair();
// Der private Schlüssel wird vom Onlinedienst sicher verwahrt
final JWK privateReplyDecryptionKey = jwkPair.getPrivateKey();
// Der öffentliche schlüssel wird vom Empfänger (Fachverfahren) genutzt um den Reply zu verschlüsseln
final JWK publicReplyEncryptionKey = jwkPair.getPublicKey();
Einsatz der generierten Keys

Sie können Schlüssel, die Sie mit ReplyChannelKeyGenerator erzeugen, nicht bei der Einrichtung eines Zustellpunktes verwenden, da sie nicht auf einem Zertifikat der V-PKI basieren. Es handelt sich hierbei um sogenannte Ephemeral Keys, die nur zum Ver- und Entschlüsseln von Replies innerhalb eines Vorgangs eingesetzt werden.

Nachdem Sie ein Schlüsselpaar erzeugt haben, fügen Sie den öffentlichen Schlüssel dem ReplyChannel hinzu: ReplyChannel.fromFitConnect(publicReplyEncryptionKey...) (siehe folgendes Beispiel).

Submission einem Vorgang zuordnen

Sie setzen bei allen Einreichungen (Submissions), die zu einem Vorgang gehören, dieselbe Vorgangsnummer (Case-ID) aus der ersten Vorgangsübermittlung. Das folgende Beispiel verwendet die Vorgangsnummer existingCaseId eines bestehenden Vorgangs, um eine neue Submission diesem Vorgang (Case) hinzuzufügen.

Sie müssen dem replyChannel den zuvor generierten publicKey mitgeben und auch den zulässigen Prozesstandard für den Rückkanal definieren. Im Anschluss kann die Submission über den SenderClient gesendet werden:

final ReplyChannel replyChannel = ReplyChannel.fromFitConnect(publicReplyEncryptionKey, List.of("urn:xoev-de:bmk:standard:xbau_2.3"))

// Die caseId für einen bestehenden Vorgang kann von einer bereits gesendeten Submission bezogen werden
final UUID existingCaseId = sentSubmission.getCaseId();

final SendableSubmission sendableSubmission = SendableSubmission.Builder()
.setDestination(UUID.fromString("d2d43892-9d9c-4630-980a-5af341179b14"))
.setServiceType("urn:de:fim:leika:leistung:99900000000000", "FIT Connect Demo")
.setJsonData("{\"message\":\"Hello World\"}", URI.create("urn:xoev-de:bmk:standard:xbau_2.2#baugenehmigung.antrag.0200"))
// hinzufügen der neuen Submission zu einem bestehenden Vorgang
.setCaseId(existingCaseId)
// wird für die FIT-Connect Reply-Channel Kommunikation benötigt
.setReplyChannel(replyChannel)
.build();

final SentSubmission sentSubmission = ClientFactory.getSenderClient(config).send(sendableSubmission);

Abholbereite Replies erfragen

Das Laden von Replies erfolgt in zwei Schritten.

  • Erfragen, ob abholbereite Replies vorliegen
  • Abholen eines bestimmten Replys

Um zu erfragen, ob Replys auf FIT-Connect vorliegen, rufen Sie die Methode getAvailableReplies auf:

final SenderClient senderClient = ClientFactory.getSenderClient(config);

// Abrufen der ersten 500 Replies
final RepliesForPickup repliesForPickup = senderClient.getAvailableReplies();

// Abrufen der ersten 25 Replies mit Pagination
final var limit = 25;
final var offset = 0;
final RepliesForPickup repliesForPickup = senderClient.getAvailableReplies(limit, offset);

Callback für Replies

Alternativ zur Methode getAvailableReplies können Sie einen Callback verwenden, eine Webadresse, die das Backend Ihres Senders (z. B. Onlinedienstes) bereitstellt. FIT-Connect ruft dann diesen Callback auf, um darüber zu informieren, dass eine Antwort (Reply) abgeholt werden kann.

Einen Reply abrufen

Mit der Methode getReply (siehe folgendes Beispiel) laden Sie einen abholbereiten Reply (ReceivedReply) inklusive der Fach- und Metadaten sowie der Anhänge (Attachments), falls Anhänge vorhanden sind. Sie übergeben dieser Methode die replyId der zu ladenden Reply und den privaten Entschlüsselungsschlüssel, den Sie vor dem Senden der zugehörigen Einreichung (Submission) erzeugt haben. Der private Schlüssel dient zum Entschlüsseln der Reply. Das Kapitel Schlüsselpaar erzeugen beschreibt das Erzeugen des öffentlichen Verschlüsselungsschlüssels und des privaten Entschlüsselungsschlüssels.

final UUID replyId = replyForPickup.getReplyId();
// Key zum Entschlüsseln wird vom Onlinedienst gespeichert und muss beim Abholen von Replies zur Verfügung stehen
final JWK privateReplyDecryptionKey = getPrivateReplyDecryptionKey();

final ReceivedReply receivedReply = senderClient.getReply(replyId, privateReplyDecryptionKey);

Hinweis

Das SDK prüft beim Abruf von Replies die erhaltenen Daten.
Schlägt eine Prüfung fehl, dann wird der Reply automatisch vom SDK zurückgewiesen. Das bedeutet, dass ein REJECT-Event in das Event-Log eingetragen wird, mit der darauf folgenden Löschung des invaliden Replys.
Dieses Verhalten ist in den Umgebungen PROD, STAGE und TEST aktiv, kann jedoch über die Eigenschaft enableAutoReject in den Einstellungen des SDKs deaktiviert werden.

Zugriff auf Fach- und Metadaten

Sie können über den ReceivedReply auf die Fach- und Metadaten sowie auf die Attachments des Replys zugreifen, nachdem der Reply geladen, entschlüsselt und erfolgreich validiert wurde:

 // Zugriff auf Fachdaten
final String data = receivedReply.getDataAsString();
final URI dataSchemaUri = receivedReply.getDataSchemaUri();
final String mimeType = receivedReply.getDataMimeType();

// Zugriff auf Metadaten
final Metadata metadata = receivedReply.getSubmissionMetdata();

// Zugriff auf Rückkanal
final ReplyChannel replyChannel = metadata.getReplyChannel();

// Zugriff auf Attachments
for(final Attachment attachment : receivedReply.getAttachments()){
final String originalFilename = attachment.getFilename();
final String attachmentMimeType = attachment.getMimeType();
// Rohdaten des Attachments als byte[]
final byte[] attachmentDataAsBytes = attachment.getDataAsBytes();
// Rohdaten des Attachments als String (per default UTF-8 encoded)
final String attachmentDataAsString = attachment.getDataAString();
}

// Zugriff auf IDs
final UUID caseId = receivedReply.getCaseId();
final UUID submissionId = receivedReply.getSubmissionId();
final UUID destinationId = receivedReply.getDestinationId();

Empfangsbestätigung für Replies

Um einen Reply zu akzeptieren oder abzulehnen, sendet der Sender entsprechende Events an das Event-Log (siehe folgende Beispiele fürs Akzeptieren und Ablehnen).

Event Log

Weitere Details finden Sie in der Dokumentation zu Events und zum Erzeugen von Security Event Tokens.

Reply akzeptieren

Nach der Prüfung durch den Sender (Sender-Client) können Sie den Reply mit einem accept-reply-Event annehmen. Sie können diesem Event Problems hinzufügen, die als Anmerkungen zu sehen sind, die nicht zur Zurückweisung des Replys führen:

// Akzeptieren des Replys ohne Angabe von Problems
receivedReply.acceptReply();

// Akzeptieren des Replys mit einer Liste von Problems
receivedReply.acceptReply(List.of(new MyCustomProblem());
Hinweis

Nach dem Senden des accept-reply-Events wird der Reply in den Status deleted überführt und im Zustelldienst gelöscht.

Reply zurückweisen

Wenn die Prüfung durch den Sender (Sender-Client) negativ ausfällt, zum Beispiel bei einer semantischen Unstimmigkeit in den Fachdaten (Städtename ist nicht real, Alter der Person ist nicht plausibel), dann kann der Reply zurückgewiesen werden.
Die Zurückweisung erlaubt die Angabe von Problems aus der Domäne *.api.domain.model.event.problems.*. Weitere Details finden Sie unter den verfügbaren (technischen) Problemen.

// Senden der Zurückweisung mit einer Liste von Problemen
receivedReply.rejectReply(List.of(new DataSchemaViolation(), new MyCustomProblem("Angegebene Stadt existiert nicht")));

Nach Senden des reject-reply-Events wird der Reply in den Status deleted überführt und im Zustelldienst gelöscht.

Routing-Informationen

Der folgende Abschnitt zeigt, wie Sie die DestinationID des Zustellpunktes für eine bestimmte Verwaltungsleistung in einem bestimmten Gebiet ermitteln.

Hinweis

Es können mehrere Zustellpunkte (unterschiedliche destinationID) und auch mehrere Zustelldienste (unterschiedliche submissionUrl) für eine bestimmte Verwaltungsleistung in einem bestimmten Gebiet zuständig sein.

Sie können folgendermaßen vorgehen:

  1. Nach der AreaID für ein Gebiet suchen, mit der Methode findAreas der Klasse RouterClient.
  2. Suchanfrage erzeugen mit der Klasse DestinationSearch.Builder.
  3. Nach den DestinationIDs suchen, mit der Methode findDestinations der Klasse RouterClient, mit dem Parameter AreadID, AGS oder ARS

Nach der AreaID für ein Gebiet suchen

Die AreaIDs für Gebiete können mit einem oder mehreren Kriterien gesucht werden. Das folgende Beispiel sucht die AreaIDs anhand des Städtenamens (mit Wildcard *) und der Postleitzahl:

final RouterClient routerClient = ClientFactory.getRouterClient(config);

final var citySearchCriterion = "Leip*";
final var zipCodeSearchCriterion = "04229";

// finde die ersten fünf Resultate
final List<Area> areas = routerClient.findAreas(List.of(citySearchCriterion, zipCodeSearchCriterion), 0, 5);

LOGGER.info("Found {} areas", areas.size());
for (final Area area : areas){
LOGGER.info("Area {} with id {} found", area.getName(), area.getId());
}

Suchanfrage erzeugen mit dem DestinationSearch.Builder.

Um eine Suchanfrage zu erzeugen, verwenden Sie die Klasse DestinationSearch.Builder

Für die Suchanfrage benötigen Sie den Leistungsschlüssel leikaKey und genau ein Suchkriterium für die Region:

Ein Suchkriterium für die Region kann sein:

Leistungsschlüssel

Sowohl der leikaKey, als auch die Regionalschlüssel ars und ags können nicht über den RoutingClient bezogen werden. Hierfür können Sie folgende Quellen nutzen:

Nach der DestinationID suchen, mit der AreaID

final RouterClient routerClient = ClientFactory.getRouterClient(config);

final DestinationSearch search = DestinationSearch.Builder()
.withLeikaKey("99123456760610")
.withAreaId("48566") // areaId der Stadt "Leipzig"
.withLimit(3)
.build();

// Finde die ersten drei Resultate
final List<Route> routes = routerClient.findDestinations(search);

LOGGER.info("Found {} routes for service identifier {}", routes.size(), leikaKey);
for (final Route route : routes){
LOGGER.info("Route {} with destinationId {} found", route.getName(), route.getDestinationId());
}

Nach der DestinationID suchen, mit ARS oder AGS

Als Suchkriterium können Sie auch den Amtlichen Regionalschlüssel ARS oder den Gebietsschlüssel AGS verwenden. Das folgende Beispiel nutzt den ARS:

final RouterClient routerClient = ClientFactory.getRouterClient(config);

final DestinationSearch search = DestinationSearch.Builder()
.withLeikaKey("99123456760610")
.withArs("147130000000") // beispiel ARS für "Leipzig"
.withLimit(3)
.build();

// Finde die ersten drei Resultate
final List<Route> routes = routerClient.findDestinations(search);

LOGGER.info("Found {} routes for service identifier {}", routes.size(), leikaKey);
for (final Route route : routes){
LOGGER.info("Route {} with destinationId {} found", route.getName(), route.getDestinationId());
}

Callbacks validieren

Beim Empfang von Callbacks sollte der Sender prüfen, ob die empfangenen Daten gültig sind, um unautorisierte Zugriffe auf Geschäftsprozesse und Nutzerdaten zu verhindern. Die für die Validierung notwendigen Daten befinden sich innerhalb des empfangenen HTTP-Request. FIT-Connect nutzt das HMAC-Verfahren, um die Gültigkeit eines Aufrufs zu überprüfen.

Hierfür bietet das SDK die Methode validateCallback der Klassse SenderClient:

final SenderClient senderClient = ClientFactory.getSenderClient(config);

final ValidationResult validationResult = senderClient.validateCallback("hmac", 0L, "body", "secret");

if(validationResult.hasError()){
LOGGER.error(validationresult.getError().getMessage());
}
Hinweis

Weitere Details zur Funktionsweise von Callbacks finden Sie hier.