Zum Inhalt

Notifizierung mit dem SDK

Setup und je Stage der Aufruf über den NotificationDocumentHandler.

Die fachlichen Hintergründe stehen in der Anleitung Notification (Company-Rolle).

Setup

// Client + Session — for details see Getting started
var client = ZedalClientFactory.Default.CreateClient(
    TargetSystem.Test, "MyIntegrationApp", "1.0.0");

await client.CreateSession(
    userId:   "mysubscriber-myaccount@1:mypassword",
    appToken: new AppToken("<APP_TOKEN_JWT>"));

Der Notification-Lebenszyklus wird im SDK über einen Notification-Handler geführt. Der Handler kapselt das Dokument inklusive Versionierung, Validierung und Push-Logik:

NotificationDocumentHandler handler = client.DocumentHandler.CreateNotificationHandler();

Pro Notification ein Handler. Wiederholtes Bearbeiten derselben Notification (z.B. Antwort auf Nachforderung) erfolgt über LoadFromZedalByNotificationId(notificationId) auf einem neuen Handler.

Die spiegelbildliche Variante für die Behörden-Seite heißt CreateAuthorityNotificationHandler() und liefert einen AuthorityNotificationDocumentHandler — siehe Authority.

Stage 1 — CreateNotification

Die Verschachtelung des Dokuments

Die Feldtypen sind Protobuf-Wrapper (StringField, BoolField, Int32Field, DoubleField, TimestampField) und keine .NET-Primitiven. Das SDK bringt Kurzkonstruktoren dafür mit:

using static Zedal.Sdk.Protobuf.ProtoFields;   // Sf, Bf, I32, I64, Df, DateUtc, DateTimeUtc

Aufbau in Code

// 1) Operator lookups via EORI — returns the DiWaSS-internal operator IDs
//    (FindOperatorIdAsync: see the helper functions at the end of this page)
long notifierId  = await FindOperatorIdAsync(client, "DE0001234567");
long consigneeId = await FindOperatorIdAsync(client, "FR0009876543");

// 2) Assemble the submission
var submission = new SubmissionDocumentType
{
    Base = new BaseSubmissionDocumentType
    {
        TakeBackIndication        = Bf(false),
        IllegalActivityIndication = Bf(false),

        Submission = new SubmissionType
        {
            // Parties — one UUID, one contact and the DiWaSS operator ID per party
            Notifier = new WasteOperatorType
            {
                PartyUuid          = Sf(notifierPartyUuid),
                Contact            = notifierContact,      // ContactType
                OperatorInternalId = I64(notifierId)
            },
            Consignee = new WasteOperatorType
            {
                PartyUuid          = Sf(consigneePartyUuid),
                Contact            = consigneeContact,
                OperatorInternalId = I64(consigneeId)
            },

            IndividualShipmentIndicator = Bf(false),
            DestinedForRecoveryDisposal = Sf(FacilityTypeCode.Disposal.ToWire()),
            PreConsentIndicator         = Bf(false),

            // Intended number of shipments — later limits the
            // number of movement documents, see movement guide
            ShipmentQuantity = I32(100),

            TotalWasteMeasurement = new NotificationMassVolumeMeasureType
            {
                MassMeasure = new MassMeasureType { Tonnes = Df(50000.0) }
            },

            ShipmentPeriod = new ShipmentPeriodType
            {
                FirstStartDate = DateUtc(2026, 10, 1),
                LastStartDate  = DateUtc(2027, 9, 30)
            },

            Packaging = new PackagingType
            {
                PackageTypeCode = { Sf(PackageTypeCode.Box.ToWire()) }
            },

            WasteClassification = new WasteClassificationType
            {
                // Code WITH group spaces and ListId token — either one is
                // grounds for rejection if it is wrong. See code lists.
                WasteTypeCode =
                {
                    new WasteCodeType
                    {
                        Code   = Sf("01 03 06"),
                        ListId = Sf(WasteCodeListId.Low.ToWire())   // "LoW"
                    }
                }
            },

            // Further mandatory fields, not shown here: WasteProducer[],
            // Carrier[], RecoveryDisposalFacility[], DisposalRecoveryOperations,
            // ExportState, ImportState, TransitState[]
        }
    }
};

// 3) Save + send to DiWaSS
await handler.CreateSubmission(submission);
await handler.SaveToZedal();
await handler.SendToDiwass();

string notificationId = handler.DiwassNotificationId;
// Format: <CA code><YY><sequence number>i, example DE0126999901i

Vollständige Wertelisten und Regeln

Welche Werte die einzelnen Felder annehmen dürfen, steht in den Codelisten — inklusive der Schreibweisen, an denen Einreichungen regelmäßig scheitern. Welche Felder DiWaSS über das Schema hinaus erzwingt und welche Querbezüge geprüft werden, steht in den Feldregeln.

Beides gehört zum Bauen einer Submission dazu. Das Schema allein reicht nicht: Es gibt Felder, die als optional annotiert sind und trotzdem abgelehnt werden, wenn sie fehlen.

Der Präfix der Notifizierungsnummer ist der Behördencode

Die Nummer ist aufgebaut als <CA-Code><Jahr><Laufnummer>i. Der Präfix ist der Code der Versandbehörde — passt er nicht zur zuständigen Behörde, wird die Einreichung abgewiesen.

Und: Bei einer abgelehnten Einreichung liefert DiWaSS als Notifizierungsnummer den String UNKNOWN. Wer den Rückgabewert ungeprüft übernimmt, speichert diesen Wert als Vorgangsnummer und verliert die Zuordnung. Prüfe vor dem Persistieren, ob die Einreichung überhaupt erfolgreich war.

SaveToZedal persistiert das Dokument als ZeWaSS-XML in ZEDAL (kann jederzeit wieder geladen werden, ist aber noch nicht versandt). SendToDiwass triggert anschließend den asynchronen Versand an DiWaSS. Beide Methoden haben keine Rückgabewerte; den persistierten Stand siehst du in handler.ZedalDocumentId und handler.TargetFileId, den Versand-Status in handler.LastTransferLogId.

Sobald die Plattform die DiWaSS-Antwort als neue Version des Dokuments abgelegt hat, ist die Notification im Status SUBMITTED — die beteiligten Behörden werden im weiteren Verlauf aktiv (Acknowledgement, Nachforderung, Decision). Die genaue Mechanik der Antwort-Abholung ist im Lebenszyklus → Versandverfahren beschrieben.

Auf Übermittlungs-Bestätigung warten

Wer den Push synchron behandeln möchte, ruft direkt nach SendToDiwass():

// Time budget depends on the payload: without attachment around 50 s, WITH attachment over 90 s.
// The client default of 30 s is not enough in either case.
await handler.WaitForDiwassTransfer(
    timeout:      TimeSpan.FromMinutes(3),
    pollInterval: TimeSpan.FromSeconds(5));
Wirft DiwassTransferFailedException bei Ablehnung durch DiWaSS, TimeoutException bei Zeitüberschreitung. Richtwerte für andere Fälle stehen im Lebenszyklus.

Validierung vor dem Push — und warum sie hier besonders zählt

Nach jedem Stage-Aufruf stehen strukturierte Fehler in handler.ValidationErrors, mit handler.HasValidationErrors als Kurzabfrage. Prüfe beides, bevor du sendest.

Das ist hier wichtiger als bei anderen APIs: Über die Schnittstelle gibt es keinen Entwurfsstatus. Eine per API erzeugte Notifizierung ist unmittelbar eingereicht, es gibt keine Vorstufe zum Ausprobieren. Was du sendest, ist bei den Behörden.

Die Bedeutung der zurückgemeldeten Codes steht in den Fehlercodes.

Stage 3 — Reaktion auf Nachforderung

Variante A: SetReplyToRequestInformation

var handler = client.DocumentHandler.CreateNotificationHandler();
await handler.LoadFromZedalByNotificationId(notificationId);

// ID of the open RequestInformation: the request that no reply refers to yet.
// See Lifecycle -> Determining the state.
string requestInformationId = /* … */;

await handler.SetReplyToRequestInformation(new RequestInformationDocumentType
{
    // RequestInformationId, reply text, attachment references …
});
await handler.SaveToZedal();
await handler.SendToDiwass();

Antworten des Notifizierenden stehen in der Retrieval-Sicht ebenfalls unter requestInformation; sie tragen eine eigene replyRequestInformationId und dieselbe requestInformationId wie die Nachforderung, auf die sie sich beziehen. Offen ist eine Nachforderung, auf die sich noch keine Antwort bezieht. Siehe Lebenszyklus → Zustand bestimmen.

Variante B: SetSubmission (Update)

var handler = client.DocumentHandler.CreateNotificationHandler();
await handler.LoadFromZedalByNotificationId(notificationId);

var updatedSubmission = /* adjusted copy of the original submission */;
updatedSubmission.Base.NotificationId = notificationId; // reference to the existing notification

await handler.SetSubmission(updatedSubmission);
await handler.SaveToZedal();
await handler.SendToDiwass();

Update nur bei offener Nachforderung

SetSubmission auf eine bereits eingereichte Notification ist nur zulässig, wenn aktuell eine RequestInformation offen ist. Ohne offene Nachforderung wird der Aufruf serverseitig abgewiesen. Wer nach Consent ändern muss, nutzt stattdessen Stage 5 (SetRequestForChangeAfterConsent).

Stage 5 — SetRequestForChangeAfterConsent (optional)

Tritt nach erteilten Consents Änderungsbedarf auf (z.B. Anpassung der genehmigten Menge gemäß Art. 7 VO 2024/1157), reicht der Notifizierende einen Änderungsantrag ein:

var handler = client.DocumentHandler.CreateNotificationHandler();
await handler.LoadFromZedalByNotificationId(notificationId);

await handler.SetRequestForChangeAfterConsent(new RequestForChangeAfterConsentDocumentType
{
    // Justification, specific changes requested, attachment references if needed
});
await handler.SaveToZedal();
await handler.SendToDiwass();

Stage 8 — SubmitNotificationSignal (optional)

Solange die Notification nicht bereits im Endzustand CANCELLED ist, kann der Notifizierende sie stornieren (Art. 6(2)). Stornierung und andere Notifier-Signale (z.B. Rückzug vor Consent) erfolgen über SubmitNotificationSignal:

var handler = client.DocumentHandler.CreateNotificationHandler();
await handler.LoadFromZedalByNotificationId(notificationId);

await handler.SubmitNotificationSignal(new NotificationSignalDocumentType
{
    // Signal type (e.g. NotificationCancelled), justification
});
await handler.SaveToZedal();
await handler.SendToDiwass();

Hilfsfunktionen im Sample-Code

Den EORI-Lookup fasst das Beispiel notification als private Hilfsmethode zusammen (Beispiele). Sie ist nicht Teil der öffentlichen SDK-API; bau sie bei Bedarf in deiner eigenen Codebasis nach.

FindOperatorForEori liefert eine Liste der passenden DiWaSS-Operatoren. Übernimm die InternalId nur, wenn genau ein Operator passt; eine mehrdeutige EORI muss ein Mensch auflösen. Die Operator-Id deines eigenen Unternehmens liest das Beispiel mit client.MyAccount.GetOwnDiwassOperatorIds() aus dem Account.

private static async Task<long> FindOperatorIdAsync(IZedalClient client, string eori)
{
    var matches = await client.DiwassApi.OperatorApi.FindOperatorForEori(eori);
    if (matches == null || matches.Count != 1)
    {
        // Never pick one at random - an ambiguous EORI has to be resolved by a person.
        throw new InvalidOperationException($"EORI '{eori}' must match exactly one DiWaSS operator.");
    }

    return matches[0].InternalId.Value;
}