Auf dieser Seite dokumentiert die gematik die Schnittstellen des E-Rezept-Fachdienstes für Apotheken, über welche sie sich für Benachrichtigungen bei neuen Communications, registrieren können.
Die Umsetzung der Notification-Schnittstelle des E-Rezept-Fachdienstes folgt den Vorgaben des FHIR-Standards.
Das AVS sendet eine Registrierungsanforderung an die VAU des E-Rezept-Fachdienstes, dieser generiert ein Pseudonym auf Basis der Telematik-ID und ein Bearer Token.
Mit diesem Bearer Token baut das AVS eine Websocket-Verbindung an der Subscription-Schnittstelle des Fachdienstes auf und erhält je neu vorliegender Communications-Ressource für die Telematik-ID ein Ping
. Das Ping
ist dann Trigger für das Abrufen der ungelesenen Communications.
Der Aufbau der WebSocket-Verbindung erfolgt zweistufig. Als erstes erfolgt die Authentisierung durch die VAU. Diese stellt ein eigenes Bearer-Token aus, das im zweiten Schritt beim Aufbau der eigentlichen Socketverbindung an den Subscription-Service übergeben wird.
Zunächst muss für die Apotheke als authentisierter Client (gültiges AccessToken des IDP) ein Subscription-Request an die VAU gesendet werden.
Request
URI |
/Subscription |
||
---|---|---|---|
Method |
POST |
||
Requester |
|||
Responder |
|||
Request |
POST /Subscription HTTP/1.1
Host: erp.zentral.erp.splitdns.ti-dienste.de
User-Agent: Avs/1.0 AvSoft/GEMAvwepokrpxnwiorlc
Cache-Control: no-cache
Authorization: Bearer eyJraWQ.ewogImL2pA10Qql22ddtutrvx4FsDlz.rHQjEmB1lLmpqn9J
Content-Type: application/fhir+xml; charset=UTF-8
Accept: application/fhir+xml; charset=utf-8
<Subscription xmlns="http://hl7.org/fhir">
<id value="erp-notification-avs-01-request-PostSubscriptionPseudo"/>
<status value="requested"/>
<reason value="Communication notifications"/>
<criteria value="Communication?received=null&recipient=3-abc-12345678"/>
<channel>
<type value="websocket"/>
</channel>
</Subscription>
|
ℹ️
|
Das Feld ` <criteria value=""` benennt die Suchparameter, bei denen eine Notification verschickt werden soll. Das sind zum einen received=null für ungelesene Nachrichten und recipient=3-abc-12345678 die Telematik-ID der Apotheke, die mit der Telematik-ID des IDP-AccessToken übereinstimmen muss.
Andere Parameter werden aktuell nicht unterstützt.
*ACHTUNG: das "&" muss als "&" codiert werden.
|
Response
HTTP/1.1 200 OK
Content-Length: 510
Content-Type: application/fhir+xml;charset=utf-8
<Subscription xmlns="http://hl7.org/fhir">
<id value="erp-notification-avs-02-response-PostSubscriptionPseudo"/>
<status value="active"/>
<end value="2025-01-15T15:29:00+00:00"/>
<reason value="Communication notifications"/>
<criteria value="Communication?received=null&recipient=3-abc-12345678"/>
<channel>
<type value="websocket"/>
<header value="Authorization: Bearer eyJhbGciOiAiYnJhaW5wb29sUDI1NnIxIiwidHlwIjogIkpXVCJ9.eyJpc3MiOiAiTWF0aGlzIGJyYWlucG9vbCBqd3QiLCJpYXQiOiAxNjMyMjk0MzY1LCJleHAiOiAxNjYzODMwMzY1LCJhdWQiOiAibG9jYWxob3N0Iiwic3ViIjogIm15VXNlcm5hbWUiLCJzdWJzY3JpcHRpb25JZCI6ICIxMjNhYmMifQ.MEUCIAKqlB50xqNhnHkP6qoOoll33l3rWQ-_b5XfQJAUErnFAiEAlGR-cEl7DCzaoHqifh0drreFInsqo1xVy3SrWSMmNCI"/>
</channel>
</Subscription>
ℹ️
|
In ` <id value="df694c098c2fb373524150461cfd9d23"/>` ist eine eindeutige ID (Pseudonym der Telematik-ID) hinterlegt |
ℹ️
|
Der timestamp in <end value="2022-01-01T00:00:00Z"/> ` errechnet sich aus jetzt + 12h (UTC Timestamp) um den Verbinungsabbau nach Ablauf zu testen
|
ℹ️
|
die Header müssen in ` <header value="*"` beim Web Socket Upgrade mitgegeben werden |
Die Schnittstelle antwortet mit den typischen http-StatusCodes des RESTful-Paradigmas
Code |
Type Success |
201 |
Created |
Code |
Type Error |
400 |
Bad Request |
401 |
Unauthorized |
403 |
Forbidden |
429 |
Too Many Requests |
500 |
Server Errors |
Nach der Registrierung der Subscription wird eine WebSocket-Verbindung zum eigentlichen NotificationService aufgebaut.
Request
URI |
subscription |
||||
---|---|---|---|---|---|
Method |
GET |
||||
Requester |
|||||
Responder |
|||||
Request |
GET /subscription HTTP/1.1 Host: subscription.zentral.erp.splitdns.ti-dienste.de Authorization: Bearer eyJhbGciOiAiYnJhaW5wb29sUDI1NnIxIiwidHlwIjogIkpXVCJ9.eyJpc3MiOiAiTWF0aGlzIGJyYWlucG9vbCBqd3QiLCJpYXQiOiAxNjMyMjk0MzY1LCJleHAiOiAxNjYzODMwMzY1LCJhdWQiOiAibG9jYWxob3N0Iiwic3ViIjogIm15VXNlcm5hbWUiLCJzdWJzY3JpcHRpb25JZCI6ICIxMjNhYmMifQ.MEUCIAKqlB50xqNhnHkP6qoOoll33l3rWQ-_b5XfQJAUErnFAiEAlGR-cEl7DCzaoHqifh0drreFInsqo1xVy3SrWSMmNCI Connection: Upgrade Pragma: no-cache Cache-Control: no-cache Upgrade: websocket Sec-WebSocket-Version: 13 Sec-WebSocket-Key: q4xkcO32u266gldTuKaSOw==
|
ℹ️
|
In Authorization: wird das von der VAU generierte Bearer Token mit dem Pseudonym über die Telematik-ID übergeben.
|
ℹ️
|
In Sec-WebSocket-Key werden clientseitig generierte Nonce (16-Byte Zufallswert in base64-Codierung), siehe RFC-6455 Seite 18, Punkt 7
|
Response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fA9dggdnMPU79lJgAE3W4TRnyDM=
ℹ️
|
Der Subscription-Service antwortet mit einem Connection: Upgrade
|
💡
|
Der Subscription-Service antwortet beim Schließen der Websocket-Verbindung mit den Status-Codes gemäß RFC-6455, bspw. mit Status 1000 wenn ein abgelaufenes Bearer Token übergeben wird.Beendet der Service die WebSocket-Verbindung aufgrund eines (internen) Fehlers, liefert er einen http-Status [502 Bad Gateway, 504 Gateway Timeout]. |
🔥
|
Der Websocket-Client MUSS eine zufällig gewählte Pause zw. 5 - 60 Sekunden warten, bevor eine neue Websocket-Verbindung aufgebaut wird. |
Das AVS registriert sich für die Subscription aus dem vorherigen Schritt, in dem eine bind
Text Nachricht über die Websocket-Verbindung an den Subscription-Service geschickt wird.
bind: df694c098c2fb373524150461cfd9d23
ℹ️
|
Im Value für bind befindet sich die Subscription.id
|
Der Subscription Service antwortet mit einer "bound" um die Einrichtung der Subscription zu bestätigen.
bound: df694c098c2fb373524150461cfd9d23
ℹ️
|
Subscription.id
|
Ist eine neue Communication eingegangen, benachrichtigt der Subscription-Service das AVS, indem eine ping <Subscription.id>
Text-Nachricht über die Websocket-Verbindung gesendet wird.
ping: df694c098c2fb373524150461cfd9d23
ℹ️
|
Subscription.id
|
|
Hinweis: die Nachricht ping: df694c098c2fb373524150461cfd9d23 ist KEIN Ping der Ping/Pong Control Frames für das Aufrechterhalten der Verbindung (siehe https://datatracker.ietf.org/doc/html/rfc6455#section-5.5).
|
Empfängt das AVS nun ein ping: df694c098c2fb373524150461cfd9d23
, liegt eine neue Nachricht vor, die über das VAU-Protokoll zum Abrufen neuer Nachrichten heruntergeladen werden kann. Über die Websockets werden selbst keine Nachrichten oder andere E-Rezept-bezogenen Daten verschickt.
using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
class Program {
static void Main() {
//subscriptionId und bearertoken aus VAU-Request /Subcription extrahieren
CreateSocket("df694c098c2fb373524150461cfd9d23",
"Bearer eyJhbGciOiJFUzI1NiJ9.CnsKInN1YnNjcmlwdGlvbklkIjogImRmNjk0YzA5OGMyZmIzNzM1MjQxNTA0NjFjZmQ5ZDI…");
}
private static void CreateSocket(string subscriptionId, string bearertoken) {
var _websocketObj = new ClientWebSocket();
_websocketObj.Options.SetRequestHeader("Authorization", bearertoken);
//url RU: "wss://subscription-ref.zentral.erp.splitdns.ti-dienste.de" und PU: "wss://subscription.zentral.erp.splitdns.ti-dienste.de"
_websocketObj.ConnectAsync(new Uri("wss://subscription-ref.zentral.erp.splitdns.ti-dienste.de/subscription"), CancellationToken.None)
.Wait();
if (_websocketObj.State != WebSocketState.Open) {
throw new Exception("Websocket ist nicht geöffnet");
}
{
var bind = $"bind: {subscriptionId}";
_websocketObj.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(bind)), WebSocketMessageType.Text, true, CancellationToken.None)
.Wait();
Console.Out.WriteLine($"Websocket-Bind: {bind}");
var buffer = new ArraySegment<byte>(new byte[2048]);
WebSocketReceiveResult wsr = _websocketObj.ReceiveAsync(buffer, CancellationToken.None).Result;
var res = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, wsr.Count);
Console.Out.WriteLine($"Websocket-Bound: {res}");
}
while (true) {
var buffer = new ArraySegment<byte>(new byte[2048]);
WebSocketReceiveResult wsr = _websocketObj.ReceiveAsync(buffer, CancellationToken.None).Result;
// ReSharper disable once AssignNullToNotNullAttribute
var res = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, wsr.Count);
if (wsr.Count > 0) {
Console.Out.WriteLine($"Websocket-Empfangen: {res} ({wsr.Count} Bytes) -> es liegen neue Nachrichten bereit!");
}
}
}
}
🔥
|
Jede eingestellte Nachricht führt zu einem Ping, ggfs. im Millisekundenbereich, wenn viele Nachrichten an einen Empfänger gerichtet werden. In Abhängigkeit von der Implementierung kann dieses Verhalten zu einer Überlastung des PS führen, wenn bspw. jedes einzelne Ping den Anwendungsfall "Nachrichten von Versicherten empfangen" triggert. Im Zweifel ist eine Wartezeit im AVS hilfreich, in der die zuletzt abgerufenen Nachrichten bearbeitet werden. Zwischenzeitlich "gepingte" Nachrichten gehen nicht verloren, da sie beim nächsten Abruf ungelesener Nachrichten gesammelt heruntergeladen werden. |
🔥
|
Wird die WebSocket-Verbindung aufgrund eines Fehlers unerwartet terminiert, MUSS der Websocket-Client eine zufällig gewählte Pause zw. 5 - 60 Sekunden warten, bevor eine neue Websocket-Verbindung aufgebaut wird. |
ℹ️
|
Je Telematik-ID ist nur ein Websocket möglich |
ℹ️
|
Die Websocket-Verbindung wird nach 12h automatisch geschlossen und muss neu registriert werden. |