Table of Contents

Webhooks

Webhooks melden Ereignisse aus einem Modul an externe Systeme. Das Modul erzeugt das Ereignis; die Runtime übernimmt dessen Zustellung. Jede Zustellung wird dauerhaft gespeichert, signiert, bei Fehlern mit wachsenden Abständen wiederholt und in Studio protokolliert.

Ereignistyp deklarieren

Deklarieren Sie Ereignisse mit [WebhookEvent] und IWebhookEvent:

[WebhookEvent("artikel.geaendert", Display = "Artikel geändert",
    Description = "Feuert für jeden Artikel, den der Datenmonitor als geändert meldet.")]
public sealed record ArtikelGeaendert(
    string? ArtNr,
    string? Suchbegriff,
    string? Bezeichnung,
    string? Einheit,
    double? VkPreis) : IWebhookEvent;

Das deklarierte Ereignis steht in Studio zur Auswahl für Webhook-Abonnements bereit. Vergeben Sie einen eindeutigen Namen aus Kleinbuchstaben, Ziffern, ., _ und -.

Ereignis zustellen

Mit IWebhooks.EmitAsync senden Sie ein Ereignis an alle abonnierten Ziele:

public sealed class ArtikelWebhookModule(
    IBpSession session,
    IWebhooks webhooks) : BpModule
{
    public override string ModuleName => "Artikel-Webhook";
    public override string ModuleVersion => "1.0.0";
    public override string ModuleDescription => "Sendet geänderte Artikel an abonnierte Ziele.";

    [Action("artikel-webhook", Display = "Artikel-Änderungen als Webhook senden")]
    [OnChanged]
    public async Task<int> OnArtikelChangedAsync(ChangedSet<Artikel> changes, CancellationToken cancellationToken)
    {
        var queued = 0;
        foreach (var artikel in changes.Items)
        {
            // Je geändertem Artikel ein Ereignis einreihen.
            queued += await webhooks.EmitAsync(new ArtikelGeaendert(
                artikel.ArtNr,
                artikel.SuchBeg,
                artikel.KuBez1,
                artikel.Einh,
                artikel.Vk0Preis), cancellationToken);
        }

        return queued;
    }
}

EmitAsync reiht Zustellungen ein und gibt deren Anzahl zurück. Der Versand erfolgt im Hintergrund.

Payload und Header verarbeiten

{
  "id": "0f8fad5bd9cb469fa165708867fc2f01",
  "module": "meinefirma.artikel-webhook",
  "event": "artikel.geaendert",
  "timestamp": "2026-07-27T12:34:56.789Z",
  "data": { "artNr": "1000", "suchbegriff": "Schraube", "vkPreis": 1.29 }
}
Header Bedeutung
Idempotency-Key Bleibt über alle Wiederholungen derselben Zustellung gleich, entspricht id im Umschlag
X-BpNexT-NET-Webhook-Id Identisch mit dem Idempotency-Key
X-BpNexT-NET-Module Modul-ID des Absenders
X-BpNexT-NET-Event Ereignisname, nur bei Abonnement-Zustellungen
X-BpNexT-NET-Signature Signatur, siehe unten

Eine 2xx-Antwort gilt als erledigt. Alles andere wird nach 1, 5, 10 und 30 Minuten sowie 1, 6, 12 und 24 Stunden erneut versucht. Die Abstände gelten jeweils ab dem vorherigen Versuch. Nach dem letzten fehlgeschlagenen Versuch werden die Administratoren benachrichtigt.

Entdoppeln Sie Wiederholungen anhand des Idempotency-Key. Ein erneuter EmitAsync-Aufruf erzeugt hingegen einen neuen Schlüssel. Wiederholt verarbeitete Änderungsbatches benötigen deshalb zusätzlich eine fachliche Duplikatprüfung.

Webhook-Signatur prüfen

Jeder Webhook erhält beim Anlegen ein Secret (whsec_...), das genau einmal angezeigt wird. Damit signiert die Runtime jede Zustellung:

X-BpNexT-NET-Signature: t=1712345678,v1=5257a869e7...

t ist die Unix-Zeit des Sendeversuchs, v1 ist HMAC-SHA256(secret, "{t}.{body}") in Hex-Darstellung. Verwenden Sie das vollständige Secret einschließlich whsec_ als UTF-8-Bytes und den unveränderten Request-Body. Die Prüfung beim Empfänger:

signierteEingabe = t + "." + roherRequestBody
erwartet         = hex(hmacSha256(secret, signierteEingabe))
gültig           = timingSafeEqual(erwartet, v1) und |jetzt - t| < 5 Minuten

Die Zeitschranke verhindert das Wiedereinspielen alter Anfragen. Der Zeitstempel wird pro Sendeversuch neu gesetzt, Wiederholungen bestehen die Prüfung also ebenfalls.

Ereignis direkt an eine URL senden

SendAsync sendet einen Payload ohne Ereignisumschlag direkt an eine URL:

await webhooks.SendAsync(
    zielUrl,
    new { text = $"Auftrag {auftrag.Nummer} ist da." },
    new WebhookSendOptions
    {
        SigningSecretName = "partner-webhook-secret",
        Label = "partner-sync"
    }, cancellationToken);

SigningSecretName benennt das Secret für die Signatur; modulspezifische Werte haben Vorrang vor globalen. Ohne Angabe wird unsigniert gesendet. Label beschriftet die Zustellung. Auch direkte Sendungen verwenden die Warteschlange und Wiederholungslogik.

Webhooks in Studio verwalten

Superadmins verwalten Abonnements im Paket unter Webhooks. Dort lassen sich Ziel-URL und Ereignisse auswählen, Testsendungen auslösen und Zustellversuche prüfen.

Das Secret wird nur beim Anlegen angezeigt. Geht es verloren, erstellen Sie ein neues und hinterlegen es beim Empfänger.

API-Referenz

Die API-Referenz enthält die vollständigen Webhook-Schnittstellen.