Arbeidstilsynet.Receiver
1.12.0
Prefix Reserved
dotnet add package Arbeidstilsynet.Receiver --version 1.12.0
NuGet\Install-Package Arbeidstilsynet.Receiver -Version 1.12.0
<PackageReference Include="Arbeidstilsynet.Receiver" Version="1.12.0" />
<PackageVersion Include="Arbeidstilsynet.Receiver" Version="1.12.0" />
<PackageReference Include="Arbeidstilsynet.Receiver" />
paket add Arbeidstilsynet.Receiver --version 1.12.0
#r "nuget: Arbeidstilsynet.Receiver, 1.12.0"
#:package Arbeidstilsynet.Receiver@1.12.0
#addin nuget:?package=Arbeidstilsynet.Receiver&version=1.12.0
#tool nuget:?package=Arbeidstilsynet.Receiver&version=1.12.0
Arbeidstilsynet.Receiver
Client library for consuming meldinger (messages/notifications) from MeldingerReceiver.
MeldingerReceiver receives meldinger from external sources — primarily Digdir's Altinn platform — scans and stores them, and notifies registered consumers over a Valkey (Redis) stream. This package lets a consumer application subscribe to that stream, react to new meldinger, and download the structured data and documents that belong to them.
Domain terms are kept in Norwegian on purpose (e.g.
Melding,ConsumerManifest) to stay consistent with the naming used across the domain.
📦 This client integrates with a MeldingerReceiver container
This package is a client — it does not host the receiver itself. It talks to a running
MeldingerReceiver service, distributed as the container image
ghcr.io/arbeidstilsynet/receiver.
Use the container image tag that matches this package's version. The NuGet package and the container image are built and released together from the same version, so a given package version is verified against the container image carrying the same tag:
Arbeidstilsynet.Receiver vX.Y.Z ⟷ ghcr.io/arbeidstilsynet/receiver:X.Y.Z
Point MeldingerReceiverApiConfiguration.BaseUrl at that container, and connect both the
container and this client to the same Valkey (Redis) instance. Running mismatched versions is
not supported and may break message compatibility.
🧑💻 Getting started
1. Register the receiver
Register MeldingerReceiver and your consumer in the service collection. Use
AddMeldingerReceiverWithBackgroundService<T> to also start a hosted background service that
listens on the stream and dispatches new meldinger to your consumer:
using Arbeidstilsynet.Receiver.DependencyInjection;
services.AddMeldingerReceiverWithBackgroundService<MyMeldingerConsumer>(
new ValkeyConfiguration { ConnectionString = "localhost:6379" },
new MeldingerReceiverApiConfiguration { BaseUrl = "https://your-receiver-host" }
);
Your consumer (T) is registered as a scoped IMeldingerConsumer. If you want to wire the
dependencies yourself (for example in tests), call AddMeldingerReceiver(...) instead, which
registers everything except the background listener.
BaseUrlmust point at a MeldingerReceiver container whose tag matches this package's version (see above), andConnectionStringmust reference the same Valkey instance the container is connected to.
2. Implement IMeldingerConsumer
IMeldingerConsumer is the single contract you implement. It tells the receiver what your
consumer subscribes to (ConsumerManifest), how it should be polled, and what to do
when a new melding arrives (ConsumeMelding).
Before dispatching a notification (including redrive), the client refetches the melding from
the receiver API so the consumer receives current metadata and document references rather
than the stream snapshot. The melding's Id, ApplicationId, and Source must still match
the notification. If the melding is missing, its identity differs, or the fetch fails, the
consumer is not called; the error is logged and the notification remains unacknowledged.
| Member | Purpose |
|---|---|
ConsumerManifest |
Declares the consumer's unique name and which app registrations (AppId + MessageSource) it should receive meldinger for. |
PollInterval |
Backup poll interval in milliseconds. Delivery is push-based, so polling is only a safety net. null uses the default (1h). |
MaxConcurrency |
Maximum number of meldinger processed in parallel. null means sequential processing (1). |
ConsumeMelding |
Called for each new Melding. Throw to signal failure — the melding may be re-driven (redelivered) later. |
using Arbeidstilsynet.MeldingerReceiver.Domain.Data;
using Arbeidstilsynet.Receiver.Ports;
public class MyMeldingerConsumer(
IMeldingerAdapter meldingerAdapter,
IOrderService orderService,
ILogger<MyMeldingerConsumer> logger
) : IMeldingerConsumer
{
private const string OrderAppId = "my-order-app";
public ConsumerManifest ConsumerManifest =>
new()
{
ConsumerName = "my-consumer",
AppRegistrations =
[
new() { AppId = OrderAppId, MessageSource = MessageSource.Altinn },
],
};
// Use the defaults: hourly backup poll, sequential processing.
public int? PollInterval => null;
public int? MaxConcurrency => null;
public async Task ConsumeMelding(Melding newMelding)
{
// Route on ApplicationId if your consumer handles more than one app.
if (newMelding.ApplicationId != OrderAppId)
{
throw new InvalidOperationException(
$"Unexpected ApplicationId '{newMelding.ApplicationId}' for melding {newMelding.Id}."
);
}
// Download and deserialize the structured data (JSON) into your own model.
if (await meldingerAdapter.FetchStructuredData<OrderForm>(newMelding) is not { } form)
{
throw new InvalidOperationException(
$"Melding {newMelding.Id} has no structured data to process."
);
}
// Do your work. Throwing here lets the receiver re-drive the melding later.
await orderService.Process(form, newMelding.Id, newMelding.AttachmentIds);
logger.LogInformation("Processed melding {MeldingId}", newMelding.Id);
}
}
3. Fetch structured data with IMeldingerAdapter
A Melding carries references (MainContentId, StructuredDataId, AttachmentIds), not
the document contents themselves. Inject IMeldingerAdapter and call
FetchStructuredData<T> to download and deserialize the structured data (always JSON) into your
own type:
Task<TStructuredData?> FetchStructuredData<TStructuredData>(Melding melding);
It returns null when the melding has no structured data, so pattern-match the result before
using it (as shown above). TStructuredData is your own DTO that mirrors the form's JSON shape.
📨 The Melding record
ConsumeMelding receives a Melding describing a received message:
| Property | Type | Description |
|---|---|---|
Id |
Guid |
Unique id of the melding. Assume the same id can be resubmitted (redrive). |
Source |
MessageSource |
Where the melding came from (Altinn or Api). |
ApplicationId |
string |
The app that produced the melding — use it to route between handlers. |
ReceivedAt |
DateTime |
When the melding was received. |
MainContentId |
Guid? |
Reference to the human-readable main document (e.g. a PDF form), if any. |
StructuredDataId |
Guid? |
Reference to the structured JSON payload, if any — fetched via FetchStructuredData<T>. |
AttachmentIds |
List<Guid> |
References to supplementary attachment documents, if any. |
Tags |
Dictionary<string, string> |
Tags supplied by the sender. |
InternalTags |
Dictionary<string, string> |
Tags added internally by the receiver. |
For meldinger received from Altinn, MainContentId, StructuredDataId, and each value in
AttachmentIds are the IDs of the corresponding Altinn data elements. This lets consumers use
the same ID to identify a document in the receiver and its source data element in Altinn.
Altinn file metadata is copied into Document.Tags for main content, structured data and
attachments. The receiver also adds AltinnId and AltinnDataType when available. These
canonical tags take precedence over application metadata with the same key. Conflicting
values produce a warning containing the key and document ID, but not the metadata values;
identical values do not produce a warning. This applies to webhook ingestion, recovery and
manual instance processing.
♻️ Delivery and redrive semantics
- Delivery is push-based over a Valkey stream; the
PollIntervaljob is only a backup. ConsumeMeldingshould be idempotent: a melding with the sameIdcan be delivered more than once.- Throw from
ConsumeMeldingto signal failure. The melding is left unacknowledged and may be re-driven later. Only return normally once the melding has been fully handled.
If you host redrive tooling, MapCommonRedriveEndpoints(...) exposes endpoints to inspect and
re-drive pending messages.
| Method | Default path | Behaviour |
|---|---|---|
GET |
/redrive-actions/get-pending-messages |
Returns pending meldinger keyed by Valkey stream message ID. |
POST |
/redrive-actions/redrive-pending-messages |
Reprocesses all pending messages, or only the IDs selected by the optional messageIds query parameter. Only successful messages are acknowledged. |
POST |
/redrive-actions/acknowledge-pending-messages |
Acknowledges all pending messages, or only the IDs selected by the optional messageIds query parameter, without reprocessing them. |
For example, to retry two pending messages:
POST /redrive-actions/redrive-pending-messages?messageIds=1720000000000-0&messageIds=1720000000001-0
These IDs are the Valkey stream message IDs returned as keys by get-pending-messages,
not melding IDs. Unknown or already acknowledged IDs are ignored and do not contribute to
the result counts. Failed messages are reported in RedriveExceptions and remain pending.
Omitting the query filter (or supplying an empty array) preserves the endpoint's redrive-all
behaviour. The query parameter is also exposed in the consumer host's generated OpenAPI.
For programmatic use, inject IMeldingerRedriver and call
RedrivePendingMessages(IReadOnlyCollection<MessageId> messageIds) (MessageId is
StackExchange.Redis.RedisValue). Unlike the HTTP endpoint, an empty collection redrives
nothing; use the parameterless RedrivePendingMessages() to retry everything.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Arbeidstilsynet.Common.AspNetCore.Extensions (>= 3.0.3)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.13)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.13)
- Microsoft.Extensions.Http.Resilience (>= 10.11.0)
- OpenTelemetry.Instrumentation.StackExchangeRedis (>= 1.15.1-beta.2)
- StackExchange.Redis (>= 3.4.5)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.12.0 | 480 | 10/16/2026 |
| 1.11.2 | 145 | 10/15/2026 |
| 1.11.1 | 61 | 10/15/2026 |
| 1.11.0 | 54 | 10/15/2026 |
| 1.10.3 | 72 | 10/14/2026 |
| 1.10.2 | 488 | 10/9/2026 |
| 1.10.1 | 234 | 10/6/2026 |
| 1.10.0 | 66 | 10/6/2026 |
| 1.9.6 | 68 | 10/6/2026 |
| 1.9.5 | 155 | 10/5/2026 |
| 1.9.4 | 196 | 10/1/2026 |
| 1.9.3 | 93 | 10/1/2026 |
| 1.9.2 | 83 | 10/1/2026 |
| 1.9.2-alpha1 | 65 | 10/1/2026 |
| 1.9.1 | 94 | 9/30/2026 |
| 1.9.0 | 93 | 9/30/2026 |
| 1.9.0-alpha2 | 67 | 9/25/2026 |
| 1.8.8 | 81 | 9/29/2026 |
| 1.8.7 | 1,015 | 9/8/2026 |
| 1.8.6 | 1,376 | 8/3/2026 |