Interpolation values in MQTT topic configuration

By default the event topic

# from  https://www.chirpstack.io/docs/chirpstack/configuration.html


  # MQTT integration configuration.
  [integration.mqtt]

    # Event topic template.
    event_topic="application/{{application_id}}/device/{{dev_eui}}/event/{{event}}"

Is there a documented list of all variables that can be interpolated in event_topic? I’d like to add the tennant id in the topic. Would this work?

event_topic="{{ tennant_id }}/application/{{application_id}}/device/{{dev_eui}}/event/{{event}}"

Also, the interpolation variables in the gateway bride seem to be a little bit different:


# from https://www.chirpstack.io/docs/chirpstack-gateway-bridge/configuration.html

# Integration configuration.
[integration]
# Payload marshaler.
#
# This defines how the MQTT payloads are encoded. Valid options are:
# * protobuf:  Protobuf encoding
# * json:      JSON encoding (for debugging)
marshaler="protobuf"

  # MQTT integration configuration.
  [integration.mqtt]
  # Event topic template.
  event_topic_template="gateway/{{ .GatewayID }}/event/{{ .EventType }}"

Is there a list of properties that I can use in the event topic? Here I would also like to prepend the TennantID as well. So would this work?

event_topic_template="{{ .TennantID }}/gateway/{{ .GatewayID }}/event/{{ .EventType }}"

Is your Chirpstack even working at the moment? Looks like you are missing the region-prefix in your gateway bridge MQTT topic. It should generally look like “<region.prefix>/gateway/<gateway.ID>/event/<event.type>” without the region prefix Chirpstack does not know what region you are using and will ignore the uplink. Infact Chirpstack only subscribes to topics starting with the regions enabled in your chirpstack.toml.

Quick explanation - Chirpstacks gateways (or rather the gateway bridge) post to the topic <region.prefix>/gateway/… , the messages in these topic are messages directly to/from the gateways and are still encrypted. These <region.prefix> topics are what Chirpstack actually runs off. Once Chirpstack receives a message on one of these topics, it decrypts the message and posts the unencrypted version back in an “application/…” topic for debugging or integration purposes. So the “<region.prefix>/…” topics are the MQTT backend and the “application/…” topics are the MQTT integration.

To my understanding, not without serious changes to the code. You can see in these functions (specifically the publish function) the applicationID, devEUI, and eventType are inputs to the function, it’s not like the regex is just being filled by some large variable pool, each of those 3 variables are directly passed in.

Is your Chirpstack even working at the moment?

yes it does. I get MQTT message on my broker.

Looks like you are missing the region-prefix in your gateway bridge MQTT topic

The config I posted is not my actual config, it’s a copy from the official documentation at https://www.chirpstack.io/docs/chirpstack-gateway-bridge/configuration.html

My actual config looks like this:

# docker-compose.yml
services:
   ...

    chirpstack-gateway-bridge:
    image: chirpstack/chirpstack-gateway-bridge:4.0.10
    restart: unless-stopped
    ports:
      - 1700:1700/udp
    volumes:
      - ./configuration/chirpstack-gateway-bridge:/etc/chirpstack-gateway-bridge
    environment:
      - INTEGRATION__MQTT__EVENT_TOPIC_TEMPLATE=eu868/gateway/{{ .GatewayID }}/event/{{ .EventType }}
      - INTEGRATION__MQTT__STATE_TOPIC_TEMPLATE=eu868/gateway/{{ .GatewayID }}/state/{{ .StateType }}
      - INTEGRATION__MQTT__COMMAND_TOPIC_TEMPLATE=eu868/gateway/{{ .GatewayID }}/command/#
    depends_on:
      - mosquitto

...

  mosquitto:
    image: eclipse-mosquitto:2
    restart: unless-stopped
    ports:
      - 1883:1883
    volumes:
      - ./configuration/mosquitto/config/:/mosquitto/config/
      - ./CA/mosquitto:/CA

And my configuration/mosquitto/config/mosquitto.conf file looks like this:

listener 1883
allow_anonymous true

connection mondas-iot-bridge
address mybroker.company-domain.com:8883
    
bridge_cafile /CA/ca.crt
bridge_certfile /CA/client.crt
bridge_keyfile /CA/client.key
      
topic # out 0 "" lorawan/

The chirpstack services uses the default configuration.

What I like to do is to prepend the tennant ID in the chirpstack-gateway-bridge service. I want to know whether I can use {{ tennant_id }} in the event_topic config section. But it looks like that I cannot do that (based on your explanation as to how chirpstack uses the MQTT integration and on line 308 of the file you posted),

func (i *Integration) publish(ctx context.Context, applicationID uint64, devEUIB []byte, eventType string, msg proto.Message) error {
  ...
  topic, err := i.getTopic(applicationID, devEUI, eventType)
}

it only get’s these 3 variables. I’d like to have the tennant_id in the topic so that on my final broker I could write a ACL like this_

user my-customer-1
topic lorawan/<the tennant id of the customer>/#

otherwise I have to manually add a new topic line with the specific application ID evey time my customer creates a new application and I wanted to do it only once. But it seems that this is not possible.

Does chirpstack have support for Web Hooks like for example when a tennant admin creates a new application? Then I could use that to automatically generate my ACL file on my target broker.

Not that I am aware of. There almost definitely should be a way to get “alerts” when someone creates an application though, maybe in the redis streams.

One option you could do, if it’s absolutely necessary to have the tenant ID’s in the topic, is write a quick script that pulls the tenant ID from the “application/…” payloads and reposts the messages to “lorawan/<tenant.id>/…”, then you can create an ACL that blocks the application/… topics to anyone that’s not some admin user. Obviously this doubles the MQTT broker usage though.

Not that I am aware of.

what a shame, that would be great.

There almost definitely should be a way to get “alerts” when someone creates an application though, maybe in the redis streams.

Do you know how?

One option you could do, if it’s absolutely necessary to have the tenant ID’s in the topic […] Obviously this doubles the MQTT broker usage though.

I had a similar thought. Luckily I only have few tennants so I can manage it manually but I would have liked to have a more scaleable solution.

Thanks for your help.

All of the API requests are logged in redis streams, and all interaction through the web interface is just an API request. It’s been a long time since I’ve looked at the redis streams, but I assume that when a new application request is logged it would also give the application ID.
https://www.chirpstack.io/docs/chirpstack/streams/api-requests.html

Did some redis digging. The redis stream is api:stream:requests, unfortunately the create request itself does not give you the tenant ID, but it is always directly followed by a get request for the tenant that does contain the tenant ID. I can not guarantee that it will always be the direct next request if you have many users clicking around at once, but in my basic tests it always was:

local@lorawan-ns:~/chirpstack-docker$ sudo docker exec -it chirpstack-docker-redis-1 sh
/data # redis-cli XREVRANGE api:stream:request + - COUNT 50
 1) 1) "1745520963889-0"
    2) 1) "request"
       2) "\n\x11api.DeviceService\x12\x04List\x1a6\n\x0eapplication_id\x12$29c6aa40-b468-4b68-bd91-8d18fb9355de"
 2) 1) "1745520963794-0"
    2) 1) "request"
       2) "\n\x10api.RelayService\x12\x04List\x1a6\n\x0eapplication_id\x12$29c6aa40-b468-4b68-bd91-8d18fb9355de"
 3) 1) "1745520963792-1"
    2) 1) "request"
       2) "\n\x11api.DeviceService\x12\x04List\x1a6\n\x0eapplication_id\x12$29c6aa40-b468-4b68-bd91-8d18fb9355de"
 4) 1) "1745520963792-0"
    2) 1) "request"
       2) "\n\x19api.MulticastGroupService\x12\x04List\x1a6\n\x0eapplication_id\x12$29c6aa40-b468-4b68-bd91-8d18fb9355de"
 5) 1) "1745520963669-0"
    2) 1) "request"
       2) "\n\x16api.ApplicationService\x12\x03Get\x1a6\n\x0eapplication_id\x12$29c6aa40-b468-4b68-bd91-8d18fb9355de"
 6) 1) "1745520963666-0"
    2) 1) "request"
       2) "\n\x11api.TenantService\x12\x03Get\x1a1\n\ttenant_id\x12$52f14cd4-c6f1-4fbd-8f87-4025e1d49242"
 7) 1) "1745520963665-0"
    2) 1) "request"
       2) "\n\x13api.InternalService\x12\nGetVersion"
 8) 1) "1745520963562-0"
    2) 1) "request"
       2) "\n\x16api.ApplicationService\x12\x06Create\x1a6\n\x0eapplication_id\x12$29c6aa40-b468-4b68-bd91-8d18fb9355de"
 9) 1) "1745520959144-0"
    2) 1) "request"
       2) "\n\x11api.TenantService\x12\x03Get\x1a1\n\ttenant_id\x12$52f14cd4-c6f1-4fbd-8f87-4025e1d49242"
10) 1) "1745520959143-0"
    2) 1) "request"
       2) "\n\x13api.InternalService\x12\nGetVersion"
/data # 

Request 8 is the create application call, this contains the application ID: “29c6aa40-b468-4b68-bd91-8d18fb9355de”

Request 9 is the get tenant request, this contains the tenant ID: “52f14cd4-c6f1-4fbd-8f87-4025e1d49242”

But I think the simplest / most rugged solution is to have a script that watches the redis stream for a Create request, then uses the application ID from that request in a getApplication API call, then uses the Tenant ID associated with that application to update your ACL. A few steps but should be relatively straight forward.

Anyway, all food for thought.

Hi Liam,

thanks for the explanation. I will consider whether implementing this is even worth since most of my customers use TTN anyway, only 2 customers actually use my self-hosted chirpstack.