> For the complete documentation index, see [llms.txt](https://docs.cybus.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.cybus.io/2-4-2/guides/system-connectivity/analytics-monitoring-alerting/grafana-integration.md).

# Grafana Integration

This guide describes how to integrate Grafana with Connectware. You configure a service commissioning file that posts annotations for shop floor events, such as machine stops and batch changes, to the Grafana HTTP API and receives Grafana alert notifications back in the MQTT broker through a Connectware-hosted webhook. A complete example file is available at the end of this guide.

## Objectives

* Establishing a token-authenticated connection between Connectware and the Grafana HTTP API.
* Posting annotations for shop floor events, such as machine stops and batch changes.
* Receiving Grafana alert notifications in Connectware through a webhook contact point.

## Prerequisites

To follow this guide, you will need the following:

* A running instance of Cybus Connectware.
* A Grafana instance, either Grafana Cloud or self-managed, that Connectware can reach over HTTPS. For the alert webhook, Grafana must also be able to reach Connectware.
* A Grafana [service account](https://grafana.com/docs/grafana/latest/administration/service-accounts/) with a service account token. The Editor role is sufficient for creating annotations.
* Access to the [Admin UI](/2-4-2/access/admin-ui.md) with sufficient [user permissions](/2-4-2/access/user-management.md).
* Basic knowledge of MQTT and the Connectware [services](/2-4-2/data-flows/services.md) concept (for example, [service commissioning files](/2-4-2/data-flows/service-commissioning-files.md), [connections](/2-4-2/data-flows/service-commissioning-files/resources/cybus-connection.md), and [endpoints](/2-4-2/data-flows/service-commissioning-files/resources/cybus-endpoint.md)).

## Connectware and Grafana Integration

Grafana exposes its functionality as REST APIs, authenticated with service account tokens. Service accounts replaced the legacy API keys of earlier Grafana versions. Connectware communicates with these APIs through the [HTTP/REST connector](/2-4-2/connectors/enterprise-connectors/http-rest.md), which sends the token as an `Authorization: Bearer` header with every request.

This guide uses the [Annotations HTTP API](https://grafana.com/docs/grafana/latest/developers/http_api/annotations/) to mark discrete shop floor events on Grafana dashboards. An annotation is a point or region on the time axis with a text and a set of tags, for example a machine stop or a batch change. Annotations put the shop floor context right on top of your charts: a temperature spike is much easier to interpret when the batch change that caused it is marked on the same panel.

The measurement data behind those charts does not go through this API. Grafana visualizes time series data by querying a database, so live shop floor values typically flow from Connectware into a time series database such as InfluxDB, which Grafana then uses as a data source. For that part of the setup, see [InfluxDB Service Integration](/2-4-2/guides/system-connectivity/databases-time-series-storage/influxdb-service-integration.md). This guide covers the two interactions that run directly between Connectware and Grafana: annotations in one direction and alert notifications in the other.

For the alert direction, Connectware hosts an HTTP route using the [HTTP Server](/2-4-2/connectors/servers/http-server.md) resource. A Grafana webhook contact point calls this route whenever an alert fires or resolves, and a mapping publishes the notification to an MQTT topic. From there, any other Connectware service can react to it, for example by switching an alarm light on the affected line.

The MQTT topics in this guide follow an ISA-95-style equipment hierarchy (`<enterprise>/<site>/<area>/<line>/<cell>`). The mappings subscribe with wildcards across all levels, so any machine in the hierarchy is picked up without changing the integration.

### Grafana Connection Properties

The connection to Grafana requires the hostname of your Grafana instance and a service account token. We add them as parameters to the service commissioning file, so you can set them when you install the service.

Do not worry about copying the service commissioning file snippets together into one, the complete example file is available at the end of this guide.

* `grafanaHost`: The hostname of your Grafana instance, without the scheme. For example, `example.grafana.net` for Grafana Cloud.
* `grafanaPort`: The HTTPS port of your Grafana instance. Defaults to `443`.
* `serviceAccountToken`: A token of a Grafana service account with the Editor role. To create one, open **Administration > Users and access > Service accounts** in Grafana, add a service account, and generate a token for it.
* `topicRoot`: The root of the MQTT topic hierarchy. Defaults to `enterprise`.

{% code lineNumbers="true" %}

```yaml
parameters:
  grafanaHost:
    description: Hostname of your Grafana instance, without the scheme
    type: string
    default: example.grafana.net

  grafanaPort:
    description: HTTPS port of your Grafana instance
    type: number
    default: 443

  serviceAccountToken:
    description: Grafana service account token used to authenticate API requests
    type: string

  topicRoot:
    description: Root of the MQTT topic hierarchy
    type: string
    default: enterprise
```

{% endcode %}

### Grafana Connection

To connect to the Grafana HTTP API, we set up a `Cybus::Connection` resource that uses the HTTP/REST connector. The `headers` property adds the `Authorization: Bearer` header to every request, so the endpoints that use this connection do not need any authentication configuration of their own.

The connection probes the `/api/health` path of Grafana, which responds without authentication. This keeps the connection state meaningful even when no annotations are being posted.

{% code lineNumbers="true" %}

```yaml
resources:
  grafanaConnection:
    type: Cybus::Connection
    properties:
      protocol: Http
      connection:
        scheme: https
        host: !ref grafanaHost
        port: !ref grafanaPort
        headers:
          Authorization: !sub 'Bearer ${serviceAccountToken}'
        probePath: /api/health
        probeMethod: GET
```

{% endcode %}

### Posting Annotations for Shop Floor Events

The Annotations API creates one annotation per POST request to the `/api/annotations` path. The request body contains the annotation time as an epoch timestamp in milliseconds, a list of tags, and the annotation text.

We define one write endpoint for the API path and a mapping with one entry per event type. The HTTP/REST connector expects the request body in the `body` property of the message (see [Publishing Data to REST Servers](/2-4-2/connectors/enterprise-connectors/http-rest.md#publishing-data-to-rest-servers)). The `transform` rules build this body from the machine payload: `$millis()` supplies the current time, and the [named wildcards](/2-4-2/data-flows/service-commissioning-files/resources/cybus-mapping.md#wildcards) of the topic make the equipment hierarchy levels available in `$context.vars`, so the machine identity ends up in the tags without the machine having to send it.

{% code lineNumbers="true" %}

```yaml
annotationEndpoint:
  type: Cybus::Endpoint
  properties:
    protocol: Http
    connection: !ref grafanaConnection
    write:
      path: /api/annotations

annotationMapping:
  type: Cybus::Mapping
  properties:
    mappings:
      - subscribe:
          topic: !sub '${topicRoot}/+site/+area/+line/+cell/events/machine-stop'
        publish:
          endpoint: !ref annotationEndpoint
        rules:
          - transform:
              expression: >-
                {
                  "body": {
                    "time": $millis(),
                    "tags": ["machine-stop", $context.vars.line, $context.vars.cell],
                    "text": "Machine stop on " & $context.vars.cell & ": " & reason
                  }
                }
      - subscribe:
          topic: !sub '${topicRoot}/+site/+area/+line/+cell/events/batch-change'
        publish:
          endpoint: !ref annotationEndpoint
        rules:
          - transform:
              expression: >-
                {
                  "body": {
                    "time": $millis(),
                    "tags": ["batch-change", $context.vars.line, $context.vars.cell],
                    "text": "Batch change on " & $context.vars.cell & ": new batch " & batchId
                  }
                }
```

{% endcode %}

Any message published to a matching topic now creates one annotation. For example, a machine stop published to `enterprise/hamburg/assembly/line-1/press-01/events/machine-stop`:

{% code lineNumbers="true" %}

```json
{
  "reason": "Material jam at infeed",
  "operator": "shift-a"
}
```

{% endcode %}

This results in an annotation tagged `machine-stop`, `line-1`, and `press-01` with the text `Machine stop on press-01: Material jam at infeed`. If your machine payload already carries an event timestamp in epoch milliseconds, use that field in the transform instead of `$millis()`. To mark a time range instead of a point, for example the full duration of a stop, add a `timeEnd` property in epoch milliseconds to the body.

### Scoping Annotations to a Dashboard

Without further properties, Grafana creates an organization-wide annotation. To display these annotations, add an annotation query to a dashboard (**Dashboard settings > Annotations**) that uses the built-in Grafana data source and filters by tags, for example `machine-stop`. This is the most flexible option, because one annotation appears on every dashboard that queries its tags.

To pin an annotation to one specific dashboard or panel instead, add the `dashboardUID` property, and optionally the numeric `panelId` property, to the `body` object in the transform. The dashboard UID is part of the dashboard URL.

### Receiving Grafana Alert Notifications

For the return direction, Connectware hosts an HTTP route using the `Cybus::Server::Http` and `Cybus::Node::Http` resources. A mapping publishes every alert notification that arrives on this route to an MQTT topic, where any other Connectware service can pick it up.

{% code lineNumbers="true" %}

```yaml
grafanaAlertServer:
  type: Cybus::Server::Http
  properties:
    basePath: /grafana

grafanaAlertRoute:
  type: Cybus::Node::Http
  properties:
    parent: !ref grafanaAlertServer
    method: POST
    route: /alerts

alertMapping:
  type: Cybus::Mapping
  properties:
    mappings:
      - subscribe:
          endpoint: !ref grafanaAlertRoute
        publish:
          topic: !sub '${topicRoot}/grafana/alerts'
```

{% endcode %}

The route is served under the `/data` prefix of your Connectware instance: `https://<connectware>/data/grafana/alerts`. In Grafana, create a contact point of type **Webhook** (**Alerting > Contact points**) with this URL and assign it to your notification policies.

The calling user must be authenticated and needs write permission on the path `data/grafana/alerts`. The [webhook contact point](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier/) supports HTTP basic authentication and a custom `Authorization` header. The simplest option is basic authentication with the credentials of a Connectware user that has the required write permission. For more information, see [HTTP Server Permissions](/2-4-2/connectors/servers/http-server.md#permissions).

Grafana sends one POST request per notification, containing all alerts of the notification group. The HTTP Server wraps the JSON body in the `value` property of the internal message convention, so the message on the `${topicRoot}/grafana/alerts` topic looks like this (shortened):

{% code lineNumbers="true" %}

```json
{
  "timestamp": 1752652800000,
  "value": {
    "receiver": "connectware",
    "status": "firing",
    "alerts": [
      {
        "status": "firing",
        "labels": { "alertname": "High temperature", "cell": "press-01" },
        "annotations": { "summary": "Temperature above 80 degrees for five minutes" },
        "startsAt": "2026-07-16T09:00:00Z",
        "endsAt": "0001-01-01T00:00:00Z",
        "values": { "B": 84.2 }
      }
    ],
    "commonLabels": { "alertname": "High temperature" },
    "title": "[FIRING:1] High temperature",
    "state": "alerting"
  }
}
```

{% endcode %}

The `status` property is either `firing` or `resolved`, so a consuming service can both raise and clear a shop floor reaction, for example an alarm light. The `labels` of each alert carry the dimensions of the alert rule, which is a good place to encode the affected equipment.

## Verifying the Integration

1. Install the service and set the parameters with the values of your Grafana instance.
2. Check that the connection is in the **Connected** state on the service details page in the Admin UI. The connected state confirms that Grafana is reachable. It does not validate the token, because the health probe does not require authentication. An invalid token surfaces as a `401` error in the next step.
3. Publish a test message to `enterprise/hamburg/assembly/line-1/press-01/events/machine-stop`, for example with an MQTT client or the Admin UI. The result of every HTTP request is published to the `/res` topic of the endpoint. Use the [Data Explorer](/2-4-2/monitoring/data-explorer.md) to inspect it: on success, the result contains `Annotation added` and the annotation ID. If Grafana rejects the request, the message contains an `error` property with the HTTP status.
4. In Grafana, add an annotation query that filters by the `machine-stop` tag to a dashboard and check that the annotation marker appears on the panels.
5. Open your webhook contact point in Grafana and click **Test**. Check that the test notification arrives on the `enterprise/grafana/alerts` topic in the Data Explorer.

## Service Commissioning File Example

{% file src="/files/rebVAkWJcB2ANnYvKnnz" %}

{% code title="grafana-example.yml" lineNumbers="true" expandable="true" %}

```yaml
---
# ----------------------------------------------------------------------------
# Service Commissioning File
# ----------------------------------------------------------------------------
# Copyright: Cybus GmbH
# Contact: support@cybus.io
# ----------------------------------------------------------------------------
# Grafana Integration (Example)
# ----------------------------------------------------------------------------

description: >
  Service commissioning file for the bidirectional integration between
  Connectware and Grafana (Example)

metadata:
  name: Grafana Integration
  provider: cybus
  homepage: https://www.cybus.io
  version: 1.0.0

parameters:
  grafanaHost:
    description: Hostname of your Grafana instance, without the scheme
    type: string
    default: example.grafana.net

  grafanaPort:
    description: HTTPS port of your Grafana instance
    type: number
    default: 443

  serviceAccountToken:
    description: Grafana service account token used to authenticate API requests
    type: string

  topicRoot:
    description: Root of the MQTT topic hierarchy
    type: string
    default: enterprise

resources:
  # Connection to the Grafana HTTP API using a service account token
  grafanaConnection:
    type: Cybus::Connection
    properties:
      protocol: Http
      connection:
        scheme: https
        host: !ref grafanaHost
        port: !ref grafanaPort
        headers:
          Authorization: !sub 'Bearer ${serviceAccountToken}'
        # The health endpoint responds without authentication
        probePath: /api/health
        probeMethod: GET

  # Creates one Grafana annotation per POST request
  annotationEndpoint:
    type: Cybus::Endpoint
    properties:
      protocol: Http
      connection: !ref grafanaConnection
      write:
        path: /api/annotations

  # Turns shop floor events into annotations. The named wildcards provide
  # the topic levels in $context.vars, so the transforms tag each
  # annotation with the affected equipment.
  annotationMapping:
    type: Cybus::Mapping
    properties:
      mappings:
        - subscribe:
            topic: !sub '${topicRoot}/+site/+area/+line/+cell/events/machine-stop'
          publish:
            endpoint: !ref annotationEndpoint
          rules:
            - transform:
                expression: >-
                  {
                    "body": {
                      "time": $millis(),
                      "tags": ["machine-stop", $context.vars.line, $context.vars.cell],
                      "text": "Machine stop on " & $context.vars.cell & ": " & reason
                    }
                  }
        - subscribe:
            topic: !sub '${topicRoot}/+site/+area/+line/+cell/events/batch-change'
          publish:
            endpoint: !ref annotationEndpoint
          rules:
            - transform:
                expression: >-
                  {
                    "body": {
                      "time": $millis(),
                      "tags": ["batch-change", $context.vars.line, $context.vars.cell],
                      "text": "Batch change on " & $context.vars.cell & ": new batch " & batchId
                    }
                  }

  # HTTP route that the Grafana webhook contact point calls.
  # Served at https://<connectware>/data/grafana/alerts
  grafanaAlertServer:
    type: Cybus::Server::Http
    properties:
      basePath: /grafana

  grafanaAlertRoute:
    type: Cybus::Node::Http
    properties:
      parent: !ref grafanaAlertServer
      method: POST
      route: /alerts

  alertMapping:
    type: Cybus::Mapping
    properties:
      mappings:
        - subscribe:
            endpoint: !ref grafanaAlertRoute
          publish:
            topic: !sub '${topicRoot}/grafana/alerts'
```

{% endcode %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.cybus.io/2-4-2/guides/system-connectivity/analytics-monitoring-alerting/grafana-integration.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
