Der Weg eines Antrags
Ein Antrag entsteht im Onlinedienst, reist verschlüsselt durch FIT-Connect, wird in der Verwaltung geprüft und beantwortet. Diese Seite geht den Weg einmal komplett ab — auf jeder Station steht, was beide Rollen tun und welches Rezept die Details hat. Was der Onlinedienst sendet, ist genau das, was die Verwaltung empfängt; deshalb lohnt es sich, beide Seiten nebeneinander zu sehen.
Die ganze Reise im Code
- Java
- .NET (C#)
Onlinedienst-Seite — jede Zeile ist eine Station:
FitConnectSdk sdk = FitConnectSdk.fromConfigYaml(Path.of("fitconnect.yaml")); // 1 Einrichten
OnlineService svc = sdk.onlineService(MY_ONLINE_SERVICE_ID, vault::find);
Participant amt = sdk.directory().forService(LEIKA_KEY, "Anmeldung", ars); // 2 Adressieren
SentSubmission sent = svc.send(OutgoingSubmission.to(amt) // 3 Senden
.setData(SubmissionData.json(formularJson, schemaUri)).build());
EventState status = svc.cases().logOf(sent).latest().state(); // 4 Status
for (ReplyForPickup r : svc.awaitingReplies()) svc.accept(svc.receive(r)); // Antwort lesen
Verwaltungs-Seite — abholen, prüfen, entscheiden, antworten:
Organisation amt = sdk.organisation(MY_DESTINATION_ID, keys); // 1 Einrichten
for (SubmissionForPickup waiting : amt.awaitingSubmissions()) { // 5 Empfangen
ReceivedSubmission received = amt.receive(waiting);
if (!received.acceptable()) {
amt.reject(received, received.report().asProblems());
continue;
}
amt.accept(received);
amt.send(OutgoingReply.answering(received) // 6 Antworten
.setData(SubmissionData.json(bescheidJson, bescheidSchemaUri)).build());
}
Onlinedienst-Seite — jede Zeile ist eine Station:
IOnlineServiceClient svc = client.AsOnlineService(); // 1 Einrichten (DI)
var routes = await client.Directory.FindByService(LEIKA_KEY, AreaFilter.Ars(ars)); // 2 Adressieren
SentSubmission sent = await svc.SendSubmission(OutgoingSubmissionBuilder.Builder() // 3 Senden
.WithDestinationId(routes[0].DestinationId).WithServiceType(LEIKA_KEY, "Anmeldung")
.WithMetadataVersion(new Version(1, 5, 0)).WithJsonData(formularJson, schemaUri).Build());
EventState status = await svc.GetSubmissionState(sent); // 4 Status
foreach (var r in await svc.FetchAvailableReplies() ?? []) // Antwort lesen
await svc.AcceptReply(await svc.FetchSpecificReply(r.ReplyId, ReplyKeys.FromLookup(vault.Find)));
Verwaltungs-Seite — abholen, prüfen, entscheiden, antworten:
IOrganisationClient amt = client.AsOrganisation(); // 1 Einrichten
foreach (var received in await amt.FetchAvailableSubmissionsFromDestination(myDestinationId)) // 5
{
if (!received.Acceptable())
{
await amt.RejectSubmission(received, received.Report.AsProblems().ToList());
continue;
}
await amt.AcceptSubmission(received);
await amt.SendReply(ReplyRequestBuilder.ForSubmission(received) // 6 Antworten
.WithMetadataVersion(new Version(1, 2, 0))
.WithJsonData(bescheidJson, bescheidSchemaUri).Build());
}
Station für Station
1 · Einrichten Beide
Abhängigkeit, Zugangsdaten, SDK-Instanz, Rolle — für beide Seiten fast identisch. Nur der Empfänger braucht Schlüssel: einen zum Entschlüsseln der Anträge, einen zum Signieren seiner Events (Regel 4). → Rezept: Einrichten
2 · Adressieren Onlinedienst
Welche Behörde ist zuständig? Leistung (LeiKa-Schlüssel) plus Region (ARS, AGS oder AreaId) ergibt den Zustellpunkt — ohne Login, mit signierter Antwort, die das SDK prüft. Die Verwaltung tut hier nichts im Code, aber im Portal: Nur was dort an Leistungen und Gebieten hinterlegt ist, findet das Directory. → Rezept: Empfänger finden
3 · Senden Onlinedienst
Daten, Schema, Anhänge in den Builder. Das SDK holt den öffentlichen Schlüssel des Empfängers,
verschlüsselt Daten und jeden Anhang einzeln (JWE), schreibt SHA-512-Hashes in die Metadaten,
lädt Anhänge hoch — bei Bedarf in Stücken — und reicht die validierten Metadaten ein. Ergebnis:
SentSubmission mit submissionId und caseId. Wer später eine Antwort lesen will, gibt hier
einen Rückkanal mit. → Rezept: Antrag senden
4 · Zustellen & Status Beide
Der Antrag öffnet einen Vorgang. Ab jetzt ist das Ereignisprotokoll die gemeinsame Wahrheit
beider Seiten: SUBMITTED schreibt die Plattform, ACCEPTED und REJECTED schreibt der Empfänger
— signiert. Statt zu pollen kann sich jede Seite per Callback benachrichtigen lassen.
→ Rezept: Status verfolgen · Konzept: Vorgänge und Ereignisprotokoll
5 · Empfangen & Prüfen Verwaltung
Abholen, entschlüsseln, Bericht lesen — und dann selbst entscheiden. Das SDK prüft Metadaten, Daten und Anhänge und legt einen Prüfbericht vor; es lehnt nie von allein ab. Annehmen oder Ablehnen schreibt ein signiertes Event in den Vorgang, beim Ablehnen mit den Gründen, die der Onlinedienst auf Station 4 liest. → Rezept: Anträge abholen und prüfen · Konzept: Der Prüfbericht
6 · Antworten Beide
Der Bescheid geht als Antwort in den Vorgang — nicht an einen Zustellpunkt (Regel 3). Der Onlinedienst öffnet ihn mit dem Rückkanalschlüssel, den er sich beim Senden pro Vorgang gemerkt hat, und bestätigt den Empfang. → Rezept: Antworten senden und lesen
Die Stationen sind genau die Reihenfolge, in der eine Integration entsteht — und genau die
Reihenfolge, in der Fehler auftreten. Wer bei Station 5 eine FitConnectReplyException sieht,
weiß damit sofort, dass auf Station 3 der Rückkanal fehlte.
Variante: Verwaltung beteiligt Verwaltung (Prozessnachrichten)
Nicht jeder Weg beginnt im Onlinedienst. Sobald ein Antrag bei der zuständigen Stelle liegt, muss diese oft weitere Behörden beteiligen — im Bauantrag etwa den Brandschutz oder die untere Naturschutzbehörde um eine Stellungnahme bitten. Das ist Kommunikation von Typ A/B zu Typ A/B, und sie läuft über denselben Weg, mit zwei Unterschieden:
- Adressiert wird ein Prozessschritt, keine Verwaltungsleistung. Statt eines LeiKa-Schlüssels
benennt der Absender Prozessmodell und Nachricht (
processModelId,messageId) — das Verzeichnis liefert den Zustellpunkt, der diesen Schritt bedient. - Alle Nachrichten hängen an einem Vorgang. Die Beteiligungsanfrage tritt dem Vorgang des
Bürgerantrags bei (
inCase), die Stellungnahme kommt als weitere Prozessnachricht in denselben Vorgang zurück. So bleibt im Ereignisprotokoll nachvollziehbar, wer wann wen beteiligt hat.
Die Stellungnahme ist dabei kein Reply: Antworten (Regel 3) liest nur der Onlinedienst, der den Vorgang eröffnet hat. Zwischen zwei Verwaltungen ist jede Nachricht ein Antrag — mit Prüfbericht, Annahme und Ablehnung wie auf Station 5.
- Java
- .NET (C#)
Bauaufsicht — hat den Bürgerantrag auf Station 5 angenommen und beteiligt eine Fachbehörde:
// Prozessmodell und Nachrichten-IDs gibt das Prozessmodell vor (hier Beispielwerte)
String PROCESS = "urn:example:prozess:baugenehmigung";
Participant naturschutz = sdk.directory().forProcessMessage( // 2 Adressieren
PROCESS, "beteiligung-anfordern", "DE147130000000");
SentSubmission anfrage = bauaufsicht.send(OutgoingSubmission.to(naturschutz) // 3 Senden
.inCase(received.caseId()) // dem Vorgang des Bürgerantrags beitreten
.setData(SubmissionData.xml(anfrageXml, xbauSchemaUri))
.addAttachment(Attachment.fromFile(lageplan, "application/pdf"))
.build());
Naturschutzbehörde — empfängt wie jeden Antrag, erkennt die Prozessnachricht und antwortet mit einer Prozessnachricht in denselben Vorgang:
for (SubmissionForPickup waiting : naturschutz.awaitingSubmissions()) { // 5 Empfangen
ReceivedSubmission in = naturschutz.receive(waiting);
if (!(in.addressing() instanceof Addressing.ByProcessMessage p)) continue; // hier: nur Prozessnachrichten
log.info("Beteiligung {} im Vorgang {}", p.processMessage().messageId(), in.caseId());
naturschutz.accept(in);
Participant bauaufsicht = Participant.of( // zurück an den Absender
in.senderDestinationId(), Addressing.toProcessMessage(PROCESS, "stellungnahme"));
naturschutz.send(OutgoingSubmission.to(bauaufsicht)
.inCase(in.caseId()) // derselbe Vorgang
.setData(SubmissionData.xml(stellungnahmeXml, xbauSchemaUri))
.build());
}
build() verlangt bei Prozessadressierung einen Vorgang: inCase(bestehende caseId) wie hier, oder
openingCase(serviceIdentifier, serviceName), wenn die Prozessnachricht selbst einen neuen Vorgang
eröffnen soll.
Bauaufsicht — hat den Bürgerantrag auf Station 5 angenommen und beteiligt eine Fachbehörde:
// Prozessmodell und Nachrichten-IDs gibt das Prozessmodell vor (hier Beispielwerte)
const string PROCESS = "urn:example:prozess:baugenehmigung";
var ziel = await client.Directory.FindByProcessMessage( // 2 Adressieren
PROCESS, "beteiligung-anfordern", "DE147130000000");
SentSubmission anfrage = await bauaufsicht.SendSubmission(OutgoingSubmissionBuilder.Builder() // 3 Senden
.WithDestinationId(ziel!.Destinations.Single().DestinationId)
.WithProcessMessage(PROCESS, "beteiligung-anfordern") // statt WithServiceType
.WithMetadataVersion(new Version(1, 5, 0))
.WithXmlData(anfrageXml, xbauSchemaUri)
.WithAttachment(Attachment.FromFile("lageplan.pdf", "application/pdf"), shouldBeChunked: false)
.WithCaseId(received.CaseId) // dem Vorgang des Bürgerantrags beitreten
.Build());
Naturschutzbehörde — empfängt wie jeden Antrag, erkennt die Prozessnachricht und antwortet mit einer Prozessnachricht in denselben Vorgang:
foreach (var incoming in await naturschutz.FetchAvailableSubmissionsFromDestination(naturschutzId)) // 5
{
if (incoming.ProcessMessage is not { } pm) continue; // hier: nur Prozessnachrichten
logger.LogInformation("Beteiligung {Message} im Vorgang {Case}", pm.MessageId, incoming.CaseId);
await naturschutz.AcceptSubmission(incoming);
await naturschutz.SendSubmission(OutgoingSubmissionBuilder.Builder() // zurück an den Absender
.WithDestinationId(incoming.FromDestinationId!.Value)
.WithProcessMessage(PROCESS, "stellungnahme")
.WithMetadataVersion(new Version(1, 5, 0))
.WithXmlData(stellungnahmeXml, xbauSchemaUri)
.WithCaseId(incoming.CaseId) // derselbe Vorgang
.Build());
}
Eine prozessadressierte Submission braucht einen Vorgang: WithCaseId(bestehende Id) wie hier, oder
WithNewCase(destinationId, publicService), wenn sie selbst einen eröffnen soll — beides zugleich
weist Build() zurück.
Warum keine Replies zwischen Behörden? Ein Reply ist an den Vorgang und dessen Rückkanal gebunden — und den Rückkanalschlüssel hält nur, wer den Vorgang eröffnet hat (der Onlinedienst). Eine Prozessnachricht ist dagegen ein vollwertiger Antrag an einen Zustellpunkt vom Typ A/B: mit eigenem Schlüssel des Empfängers, eigenem Prüfbericht und eigener Annahme. Das Prozessmodell ersetzt nur die Verwaltungsleistung als Adresse — sonst ändert sich nichts.
Die Adressierung im Detail: Rezept: Empfänger finden → Prozessnachrichten.