The Things Network Konfiguration: Mit Webhooks Messages an den eigenen Webserver weiterleiten

Im Artikel Elecrow ESP32 Basic LoRaWAN-Gateway und The Things Network: Firmware und Konfiguration hatte ich ja soweit alles schon erklärt: Was LoRaWAN ist, was das The Things Network (TTN) ist, wie ihr euer eigenes Gateway dort anmeldet, eure eigene Application dort erstellt und dieser Application dann Endgeräte hinzufügt. Und wie einen Message Speicher bei TTN einrichten, indem die Nachrichten eurer LoRaWAN-Endgeräte dann landen.

Mit der Konfiguration kann man schon einmal was anfangen: Man kann sich bei TTN einloggen und schauen, welche neuen Messages dort angekommen sind. Der Message-Speicher reicht für 90 Tage und man kann auch alle Daten komplett z. B. als CSV herunterladen, um diese dann weiter zu verarbeiten.

Doch was ist, wenn man die Daten sofort auswerten will, vielleicht um direkt darauf zu reagieren? Was, wenn man mehr will als die rudimentäre Message-Ansicht von TTN, in der man ohne Download nur die letzten zehn sieht, möchte? Wenn man die Daten in einer eigenen Datenbank speichern will und dann über ein Web-Interface anzeigen?

Webhooks in TTN definieren

In solchen Fällen benötigt man Webhooks. Diese leiten Messages sofort als JSON-Datenpakete per POST an einen Webserver. Dort braucht man natürlich ein Script, vielleicht mit PHP oder ASPX, das die Daten entgegen nimmt. Und schon kann man damit tun, was einem beliebt. Quasi in Echtzeit.

Am besten macht man sich vorher ein paar Gedanken über die Architektur seines TTN-Web-Interfaces auf der Webserver-Seite. Denn im Prinzip muss beides gleichzeitig stehen: Webhook und Webserver-Script, damit es funktioniert. Fangen wir bei der TTN-Seite an.

Auf der Webserver-Seite haben wir uns überlegt: wir programmieren uns ein PHP-Script. Das programmieren wir gleich noch. Um das mit Messages zu füttern, müssen wir es TTN bekannt machen.

Unter Application / Application-Name finden wir links im Menü einen Eintrag Webhooks. Klicken wir ihn an und danach den Button Add webhook oben rechts. Jetzt kommt eine lange Liste mit Logos. Die interessieren uns alle nicht. Wir machen unser eigenes Ding. Also klicken wir auf Custom webhook. Und schon können wir die Daten für unseren eigenen Webserver eingeben:



Den Namen für die ID können wir frei vergeben. Als Format lassen wir JSON. Dann brauchen wir noch eine Base-URL. Ich wähle hier mal "https://cool-web.de/ttn-interface", da mein Script für diese App unter "https://cool-web.de/ttn-interface/testapp/json-message-from-ttn.php" laufen wird. Anhalt der Ordnerstruktur habe ich dann gleich ein wenig Ordnung, welches Script für welche Application zuständig ist. Man kann natürlich auch ein Script für alle Applications schreiben und dann im Script die Apps auseinanderhalten. Das ist Geschmackssache und kommt auch darauf an, wie die Anwendungen sich verteilen.

Weiter unten haken wir dann noch Uplink message an und geben den zweiten Teil unserer PHP-URL an: "/testapp/json-message-from-ttn.php":



Und schon können wir das Ganze mit dem Knopf ganz unten Add webhook abschicken.

Ab sofort versucht TTN, jedes mal wenn eine Message hereinkommt, unseren Webserver unter der angegebenen URL zu kontaktieren und da die Message abzuliefern.

Wir landen automatisch in der Übersicht der Webhooks und das Pending beim Status bei unserem neuen Webhook verrät uns, dass die Verbindung noch nicht richtig funktioniert. Sonst stände da Healty. Ist ja auch kein Wunder: unsere Websrver-Gegenseite, unser PHP-Script ist ja noch nicht online.

Script auf dem Webserver für Messages erstellen

Unser PHP-Script auf Webserver-Seite soll hier mal einfach und universell sein und komplett alle Daten über e-mail an unsere e-mail-Adresse weiterleiten. In den angekommenen e-mails können wir dann alle Message-Daten sehen: Die Metadaten wie, wann die Message abgeschickt wurde, über welche Gateways sie geroutet wurde, wie der Empfang war und so weiter. Und natürlich unsere Nutzdaten.

Aber zuerst einmal das PHP-Script:
<?php $mailheader = "From: MailSystem <mailsystem@meine-domain.de>\r\n"; $mailheader .= "Content-Type: text/plain; charset=UTF-8\r\n"; // Daten kommen als JSON rein, die stehen nicht in $_POST, sondern müssen anders ausgewertet werden: // Rohdaten lesen $raw = file_get_contents('php://input'); // JSON in Array umwandeln $data = json_decode($raw, true); // Beispiel für Zugriff auf einzelne Felder //$pax = $data['uplink_message']['decoded_payload']['pax'] ?? null; $mailtext = print_r($data, true); // komplettes JSON als Mailtext if ($mailtext == "") $mailtext = "[keine Daten]"; $ok = mail("message-from-ttn-system@meine-domain.de", "TTN Message from TestApp", $mailtext, $mailheader); if (!$ok) { error_log("TTN-Mailversand FEHLGESCHLAGEN"); } ?>
Wichtig ist, dass wir die Daten mit file_get_contents('php://input'); auswerten müssen. Das sollte ab PHP-Version 5.2 gehen. Die Auswertung von $_POST hilft uns nichts, da steht nichts drin.

Nach einem $data = json_decode($raw, true); finden wir Daten dann in einem Array. Hier können wir dann natürlich auch nur einzelne Felder abfragen, zum Beispiel nur die Payload. Zu Anfang und zu Gunsten des Debuggings wandeln wir aber das gesamte Array in lesbaren Text via print_r um und versenden es dann per mail an uns selbst.

Hier ein Beispiel dessen, was dann in unserem Postfach landet. Ich habe die interessanteren Daten mal fett hervorgehoben:

Array ( [end_device_ids] => Array ( [device_id] => ttgo-paxcnt-1 [application_ids] => Array ( [application_id] => ttgo-pax-counter-868mhz ) [dev_eui] => C001000000000001 [join_eui] => 70B3D57ED001B94E [dev_addr] => 260B9C89 ) [correlation_ids] => Array ( [0] => gs:uplink:01K2S6WP12KKXDQZARXERAHK2T ) [received_at] => 2025-08-16T10:30:32.948384588Z [uplink_message] => Array ( [session_key_id] => AZiyUG2ebXhTtWXqzzYx5A== [f_port] => 1 [f_cnt] => 34 [frm_payload] => AAA= [decoded_payload] => Array ( [pax] => 0 ) [rx_metadata] => Array ( [0] => Array ( [gateway_ids] => Array ( [gateway_id] => fablabroofnuernberg [eui] => B827EBFFFEC3CEAA ) [time] => 2025-08-16T10:30:37.913842Z [timestamp] => 241508492 [rssi] => -120 [channel_rssi] => -120 [snr] => -11.5 [location] => Array ( [latitude] => 49.437718869324 [longitude] => 10.992465019226 [altitude] => 340 [source] => SOURCE_REGISTRY ) [uplink_token] => CiEKHwoTZmFibGFicm9vZm51ZXJuYmVyZxIIuCfr//7DzqoQjMGUcxoMCMi7gcUGEO3y/d8CIODFnNiD6YYB [received_at] => 2025-08-16T10:30:32.738163053Z ) ) [settings] => Array ( [data_rate] => Array ( [lora] => Array ( [bandwidth] => 125000 [spreading_factor] => 9 [coding_rate] => 4/5 ) ) [frequency] => 868100000 [timestamp] => 241508492 [time] => 2025-08-16T10:30:37.913842Z ) [received_at] => 2025-08-16T10:30:32.739554409Z [consumed_airtime] => 0.164864s [packet_error_rate] => 0.6875 [network_ids] => Array ( [net_id] => 000013 [ns_id] => EC656E0000000181 [tenant_id] => ttn [cluster_id] => eu1 [cluster_address] => eu1.cloud.thethings.network ) ) )
Die angekommene Mail ist gespickt mit interessanten Daten. Natürlich ist unsere Payload [pax] => 0 dabei, aber auch die Metadaten sind sehr interessant. Es steht alles drin. Z. B. auch Name und Geo-Koordinaten des LoRaWAN-Gateways, dass die Message aufgefangen hat und welcher spreading_factor dafür nötig war.

Was mir zeigt, dass mein PaxCounter zwar bei meinem eigenen 1-Channel-Gateway (SF7) joined, mein Gateway aber nicht für die Payload-Messages zu benutzen scheint, sondern weitersucht, bis es auf SF7 das FabLab findet. Da muss ich wohl noch ein bisschen probieren und debuggen, um herumzufinden, woran das genau liegt. Und genau dabei helfen diese umfangreichen Messages.

Vielleicht muss ich auch das Endgerät anders programmieren. Aber das ist Stoff für einen zukünftigen Artikel.



Quellen, Literaturverweise und weiterführende Links