Zum Hauptinhalt springen

Zentrales Bürgerpostfach (ZBP) anbinden

TL;DR – ZBP-Modul installieren, Client mit mTLS-Zertifikat einrichten, dann CreateMessage (Nachricht) oder CreateState (Statusmeldung) bauen. Innerhalb des Verbindungsnetzes senden Sie direkt an die ZBP-API; außerhalb verpacken Sie dieselbe Payload signiert in eine normale FIT-Connect-Submission an den ZBP-Adapter. Hintergrund: FIT-Connect und das ZBP.

Zwei Wege, eine Payload​

Direkt an die ZBP-APIÜber den ZBP-Adapter von FIT-Connect
WannIhre Anwendung läuft im Verbindungsnetz (NdB / NdB-VN) oder erreicht die INT-Umgebung über das InternetIhre Anwendung läuft außerhalb des Verbindungsnetzes
TransportMutual TLS mit Ihrem Client-ZertifikatVerschlüsselte Submission an den Adapter-Zustellpunkt, signiert mit Ihrem Zertifikat
ErgebnisSynchron (CreateMessageResponse)Asynchron — accept-submission / reject-submission im Ereignisprotokoll
FIT-Connect-RollekeineSie senden — also OnlineService / AsOnlineService() mit eigenem Typ-C-Zustellpunkt

CreateMessage und CreateState sind auf beiden Wegen identisch.

Modul einbinden​

<dependency>
<groupId>dev.fitko.fitconnect</groupId>
<artifactId>zbp-client</artifactId>
<version>4.0.0-rc.1</version>
</dependency>

zbp-client hängt nicht von sdk-client ab — für den Weg über FIT-Connect binden Sie beide ein.

ZBP-Client konfigurieren​

Die Authentifizierung gegenüber dem ZBP erfolgt per Mutual TLS mit Ihrem RSA-Schlüsselpaar (privater Schlüssel und Client-Zertifikat im PEM-Format).

import dev.fitko.fitconnect.zbp.ZBPCertConfig;
import dev.fitko.fitconnect.zbp.ZBPClient;
import dev.fitko.fitconnect.zbp.ZBPClientFactory;
import dev.fitko.fitconnect.zbp.ZBPEnvironment;

ZBPCertConfig certConfig = new ZBPCertConfig(
Path.of("/path/to/private.key"),
Path.of("/path/to/client.crt")); // alternativ: PEM-Strings statt Pfaden

ZBPClient zbpClient = ZBPClientFactory.createZBPClient(certConfig, ZBPEnvironment.INT_INTERNET);

Timeouts, Retries und Proxy stellen Sie über dieselbe HttpConfig ein wie im sdk-client:

HttpConfig httpConfig = HttpConfig.builder()
.timeouts(TimeoutConfig.builder().readTimeout(30).build())
.retryConfig(RetryConfig.builder().maxRetryCount(3).initialDelayInMs(200).build())
.build();

ZBPClient zbpClient = ZBPClientFactory.createZBPClient(certConfig, ZBPEnvironment.PROD_NDB, httpConfig);

Eine eigene Umgebung (z. B. ein Testsystem) erzeugen Sie mit new ZBPEnvironment("MEIN_ZBP", "https://…"). Mit dem dritten Konstruktor von ZBPCertConfig können Sie zusätzlich das ZBP-Serverzertifikat pinnen (new ZBPCertConfig(rsaKey, clientCertPem, zbpServerCertPem)).

Umgebungen​

Java-Konstante.NET-TagURLNetz
ZBPEnvironment.INT_INTERNETINT_INTERNEThttps://int.zbp.bund.deInternet
ZBPEnvironment.INT_NDBINT_NDBhttps://int.zbp.bmi.in.bund.deNdB
ZBPEnvironment.INT_NDB_VNINT_NDB_VNhttps://int.zbp.bundid.doi-de.netNdB-VN
ZBPEnvironment.PROD_NDBPROD_NDBhttps://prod.zbp.bmi.in.bund.deNdB
ZBPEnvironment.PROD_NDB_VNPROD_NDB_VNhttps://prod.zbp.bundid.doi-de.netNdB-VN
new ZBPEnvironment(name, url)CUSTOM + CustomBaseUrlfrei—

Nachricht bauen​

Das Postkorb-Handle der Adressat:innen (mailboxUuid) steht in der BundID unter Zugänge & Daten → Betroffenenauskunft. content darf HTML enthalten; die erlaubten Tags beschreibt die ZBP-Dokumentation.

import dev.fitko.fitconnect.zbp.model.AuthenticationLevel;
import dev.fitko.fitconnect.zbp.model.CreateMessage;

CreateMessage message = CreateMessage.builder()
.mailboxUuid(UUID.fromString("..."))
.applicationId(submissionId) // Bezug zum Antrag, optional
.sender("Musteramt")
.service("Antragsstatus")
.title("Ihr Antrag wurde bearbeitet")
.content("<p>Bitte prüfen Sie die Rückmeldung.</p>")
.stork_qaa_level(AuthenticationLevel.ONE)
.build();

Optional sind außerdem retrievalConfirmationAddress (Abrufbestätigung), replyAddress, reference, senderUrl und die Anhang-Metadaten (siehe unten).

Direkt an die ZBP-API senden​

CreateMessageResponse response = zbpClient.sendMessage(message);

Mit Anhängen — die Metadaten (SHA-512, Länge) berechnet das SDK:

import dev.fitko.fitconnect.zbp.model.ZBPApiAttachment;
import dev.fitko.fitconnect.zbp.model.ZBPAttachmentMetadata;

ZBPApiAttachment bescheid = new ZBPApiAttachment(pdfBytes, "bescheid.pdf", "application/pdf");

CreateMessage message = CreateMessage.builder()
// ... wie oben ...
.attachmentMetadata(List.of(ZBPAttachmentMetadata.from(bescheid)))
.build();

CreateMessageResponse response = zbpClient.sendMessageWithAttachments(message, List.of(bescheid));

Statusmeldung senden​

Statusmeldungen beziehen sich über die applicationId auf einen Antrag und erscheinen im Postfach als Verlauf: INITIATED → SUBMITTED → RECEIVED → PROCESSING → ACTION_REQUIRED → COMPLETED.

import dev.fitko.fitconnect.zbp.model.CreateState;
import dev.fitko.fitconnect.zbp.model.State;

CreateState state = CreateState.builder()
.applicationId(submissionId)
.state(State.PROCESSING)
.senderName("Musteramt")
.createdDate(Instant.now())
.build();

zbpClient.createState(state);
Im Auftrag eines Autors senden

Tritt Ihr System nur als Übermittler auf und stammt die Nachricht fachlich von einer dritten Anwendung, geben Sie deren Zertifikat und Token mit — der Eintrag im Postfach weist dann den Autor aus, nicht Sie:

zbpClient.sendMessage(message, authorCertificatePem, authorToken);
zbpClient.sendMessageWithAttachments(message, attachments, authorCertificatePem, authorToken);
zbpClient.createState(state, authorCertificatePem, authorToken);

Über den ZBP-Adapter von FIT-Connect senden​

Außerhalb des Verbindungsnetzes verpacken Sie die Payload signiert in ein ZBP-Envelope (content, sha512sum, authorCertificate, authorToken) und senden es als normale Submission an den Adapter-Zustellpunkt. Der Adapter leitet weiter und antwortet asynchron im Ereignisprotokoll.

Wert
Zustellpunkt (TEST)b21c51d0-324b-4df4-ae6c-c0568a2bb067 — STAGE/PROD über das Anbindungsmanagement
Leistungsschlüssel Nachrichturn:schema-fitko-de:fit-connect:id.bund.de:message_v6
Leistungsschlüssel Statusmeldungurn:schema-fitko-de:fit-connect:id.bund.de:status_v6
Fachdaten-Schemahttps://schema.fitko.de/fit-connect/id.bund.de/message_v6/1.0.0/zbp-message.schema.json — in beiden SDKs eingebettet, Validierung läuft offline

Das Signieren übernimmt ZBPEnvelopeBuilder aus zbp-client, das Senden der OnlineService aus sdk-client:

import dev.fitko.fitconnect.zbp.internal.ZBPEnvelopeBuilder;
import dev.fitko.fitconnect.zbp.model.AuthorKeyPair;

// Ihr ZBP-Zertifikat — dasselbe wie für den direkten Weg
AuthorKeyPair authorKeyPair = AuthorKeyPair.builder()
.authorCertificatePath(Path.of("/path/to/client.crt"))
.authorPrivateKeyPath(Path.of("/path/to/private.key"))
.build();

// 1. Payload signieren und in das Envelope verpacken
byte[] envelopeJson = ZBPEnvelopeBuilder.fromAuthorPayload(message, authorKeyPair);

// 2. Als Submission an den Adapter senden — Sie sind hier Sender (Typ C)
OnlineService onlineService = sdk.onlineService(myDestinationId);

Participant zbpAdapter = Participant.of(
UUID.fromString("b21c51d0-324b-4df4-ae6c-c0568a2bb067"), // TEST
Addressing.toService("urn:schema-fitko-de:fit-connect:id.bund.de:message_v6", "ZBP-Nachricht"));

URI schemaUri = URI.create(
"https://schema.fitko.de/fit-connect/id.bund.de/message_v6/1.0.0/zbp-message.schema.json");

SentSubmission sent = onlineService.send(OutgoingSubmission.to(zbpAdapter)
.setData(SubmissionData.json(envelopeJson, schemaUri))
.build());

// 3. Ergebnis abwarten: SUBMITTED → ACCEPTED / REJECTED
EventState state = onlineService.cases().logOf(sent).latest().state();

Anhänge hängen Sie als normale Submission-Anhänge an; Dateinamen müssen mit den Metadaten in der CreateMessage übereinstimmen, Chunking bleibt aus:

ZBPApiAttachment bescheid = new ZBPApiAttachment(pdfBytes, "bescheid.pdf", "application/pdf");
CreateMessage message = CreateMessage.builder()
// ...
.attachmentMetadata(List.of(ZBPAttachmentMetadata.from(bescheid)))
.build();

OutgoingSubmission submission = OutgoingSubmission.to(zbpAdapter)
.setData(SubmissionData.json(ZBPEnvelopeBuilder.fromAuthorPayload(message, authorKeyPair), schemaUri))
.addAttachment(Attachment.builder()
.fromBytes(pdfBytes).fileName("bescheid.pdf").mimeType("application/pdf")
.shouldBeChunked(false).build())
.build();

Statusmeldung — gleiche Mechanik, anderer Leistungsschlüssel:

byte[] stateEnvelope = ZBPEnvelopeBuilder.fromAuthorPayload(state, authorKeyPair);

Participant zbpStatus = Participant.of(zbpAdapterId,
Addressing.toService("urn:schema-fitko-de:fit-connect:id.bund.de:status_v6", "ZBP-Status"));

onlineService.send(OutgoingSubmission.to(zbpStatus)
.setData(SubmissionData.json(stateEnvelope, schemaUri))
.build());
Paket internal

ZBPEnvelopeBuilder liegt in dev.fitko.fitconnect.zbp.internal, ist aber öffentlich und der dokumentierte Weg für den Adapter. Das frühere setZBPMessage(…) des Submission-Builders gibt es in 4.0 nicht mehr — sdk-client kennt das ZBP-Modul nicht.

Anhänge über den Adapter

Pro Nachricht maximal 30 MB insgesamt, 30 MB pro Datei und 100 Anlagen; erlaubt sind u. a. pdf, jpg/jpeg, png, gif, svg, tif/tiff, txt, csv, rtf, ics. Fehlermeldungen von FIT-Connect nennen ca. 33 % größere Werte, weil sie sich auf die verschlüsselten Daten beziehen.

Fehler​

Alle Methoden des ZBPClient werfen ZBPException (unchecked). Bei einer Nicht-2xx-Antwort steckt darin die RestApiException mit Statuscode und Antwortkörper:

try {
zbpClient.sendMessage(message);
} catch (ZBPException e) {
e.getApiException().ifPresent(api ->
log.error("ZBP lehnt ab: HTTP {} — {}", api.getStatusCode(), api.getResponseBody()));
}

Über den Adapter erreichen Sie Fehler nicht als Exception, sondern als reject-submission mit Problem-Liste im Ereignisprotokoll — dieselbe Mechanik wie bei jeder anderen Submission (Status verfolgen).

Häufige Stolperfallen
  • Falscher Leistungsschlüssel: Nachricht = …:message_v6, Statusmeldung = …:status_v6 — beide mit Versionsangabe. Das Fachdaten-Schema ist für beide dasselbe.
  • Falsche Rolle: Über den Adapter senden Sie. Ein Typ-A/B-Zustellpunkt (Organisation) kann das nicht — Sie brauchen einen eigenen Typ-C-Zustellpunkt (Rollenmodell).
  • Dateinamen passen nicht: Anhang-Metadaten in der CreateMessage und Submission-Anhänge müssen denselben Dateinamen tragen, sonst weist der Adapter die Nachricht zurück.
  • Synchron gedacht: send/SendSubmission bestätigt nur den Eingang bei FIT-Connect. Ob das ZBP die Nachricht angenommen hat, sagt erst das Ereignisprotokoll.
  • mTLS am Proxy: Reverse-Proxies müssen Mutual TLS durchreichen, sonst terminiert der Proxy die Verbindung und das ZBP sieht kein Client-Zertifikat.

Weiterführende Themen​