# Job: with Event Topic

**Audience:** Integrators who need downstream subscribers to observe job execution progress or outcomes over MQTT — separately from the structured log messages going to KeeperLogger.

Most job tasks only need KeeperLogger for output. This example is for the less common case where another component needs to react to events your task publishes in real time: a dashboard, an alerting bridge, an orchestrator, or any subscriber that cannot wait for log aggregation. The `eventTopic` field declares a dedicated MQTT topic for this purpose, and `mqttTopics.allowedPublications` must list it explicitly or publish calls will be denied.

This example uses Windows paths. For Linux and macOS, apply the path and `osFilter` changes shown in the [Minimal Linux](/keeperpam/endpoint-privilege-manager/integrations/examples/job-minimal-linux.md) and [Minimal macOS](/keeperpam/endpoint-privilege-manager/integrations/examples/job-minimal-macos.md) examples.

## The Job JSON

```json
{
  "id": "my-tool",
  "name": "My Tool",
  "description": "Runs MyTool on an interval and publishes progress events to a dedicated MQTT topic.",
  "enabled": true,

  "schedule": {
    "intervalMinutes": 60
  },

  "eventTopic": "Jobs/my-tool/events",

  "osFilter": {
    "windows": true,
    "linux": false,
    "macOS": false
  },

  "mqttTopics": {
    "allowedPublications": [
      "KeeperLogger",
      "Jobs/my-tool/events"
    ],
    "allowedSubscriptions": []
  },

  "parameters": [],

  "tasks": [
    {
      "id": "run-tool",
      "name": "Run tool",
      "ExecutionType": "Service",
      "command": "MyTool",
      "executablePath": "C:\\Program Files\\KeeperPrivilegeManager\\Jobs\\bin\\MyTool\\MyTool.exe",
      "arguments": "--keeper-api-base={KeeperApiBaseUrl}",
      "timeoutSeconds": 3600,
      "continueOnFailure": false,
      "scriptType": "Auto"
    }
  ]
}
```

## What to Change

<table data-header-hidden="false" data-header-sticky><thead><tr><th width="286">Field</th><th>What to Put Here</th></tr></thead><tbody><tr><td><code>id</code></td><td>A unique identifier for this job. Use hyphens — no underscores. Filename must match: <code>my-tool.json</code> for <code>"id": "my-tool"</code>.</td></tr><tr><td><code>name</code></td><td>A human-readable name shown in logs and the admin view.</td></tr><tr><td><code>eventTopic</code></td><td>The MQTT topic your task publishes events to. Must match exactly — same string — in <code>mqttTopics.allowedPublications</code>.</td></tr><tr><td><code>mqttTopics.allowedPublications</code></td><td>Must include both <code>KeeperLogger</code> (if you publish logs) and the <code>eventTopic</code> string. Any topic your task publishes to must appear here.</td></tr><tr><td><code>tasks[0].command</code></td><td>The name of your binary without a path or extension.</td></tr><tr><td><code>tasks[0].executablePath</code></td><td>Full path to your binary on the endpoint.</td></tr><tr><td><code>tasks[0].arguments</code></td><td>Flags your binary accepts. Keep <code>{KeeperApiBaseUrl}</code>.</td></tr></tbody></table>

## How This Works

`eventTopic` declares the MQTT topic the agent associates with this job's execution events. If you omit it, the default topic pattern is `Jobs/{jobId}/events` — for a job with `"id": "my-tool"`, that is `Jobs/my-tool/events`. Setting it explicitly, as shown here, makes the topic visible in the JSON and prevents surprises if the default pattern ever changes.

The critical rule is that `eventTopic` and `mqttTopics.allowedPublications` must stay in sync. The broker enforces the allowed publications list at the point of publish — if your task publishes to `Jobs/my-tool/events` but that string is not in `allowedPublications`, the MQTT connection will succeed and the publish call will be silently denied. This is the most common misconfiguration with event topics. Whenever you change `eventTopic`, update `allowedPublications` to match.

Your task binary publishes to the event topic the same way it publishes to KeeperLogger — via the MQTT connection it opens after reading broker settings from Plugin Settings. The event topic is just an additional topic the broker permits. The payload format is your choice; it does not need to follow the KeeperLogger `RequestMessage` structure.

Subscribers receive the events in real time as your task publishes them. Confirm with your administrator that the subscribing component has permission to subscribe to the event topic before designing around it.

## Before You Deploy

1. Verify that `eventTopic` and the corresponding entry in `mqttTopics.allowedPublications` are identical strings. A mismatch is not a validation error — it will only surface at runtime as a denied publish.
2. Confirm that any downstream subscriber has MQTT subscribe permission for the event topic.
3. Deploy the binary to `executablePath` before registering the job.
4. Filename must match `id`.

## Deploy

Validate before saving:

```powershell
Invoke-RestMethod -Method Post `
  -Uri "https://127.0.0.1:6889/api/Jobs/validate" `
  -ContentType "application/json" `
  -Certificate $adminClientCert `
  -Body (Get-Content -Raw .\my-tool.json)
```

Create the job:

```powershell
Invoke-RestMethod -Method Post `
  -Uri "https://127.0.0.1:6889/api/Jobs" `
  -ContentType "application/json" `
  -Certificate $adminClientCert `
  -Body (Get-Content -Raw .\my-tool.json)
```

Trigger a manual run and verify that events appear on the expected topic:

```powershell
Invoke-RestMethod -Method Post `
  -Uri "https://127.0.0.1:6889/api/Jobs/my-tool/trigger" `
  -ContentType "application/json" `
  -Certificate $adminClientCert `
  -Body "{}"
```


---

# Agent Instructions: 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:

```
GET https://docs.keeper.io/keeperpam/endpoint-privilege-manager/integrations/examples/job-with-event-topic.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
