Custom Job Guide

Custom Job Integration Guide
This guide walks you through integrating a custom executable with KEPM as a job task — from building and deploying your binary, to authoring the job JSON, to connecting to MQTT and publishing structured logs. It covers everything a tool that runs on a schedule or at agent startup needs to work correctly in a production deployment.
If you have not read the Integrations Overview page yet, start there. It explains the key terms, how the pieces connect, and when a job task is the right pattern for your use case.
Build and Deploy Your Binary
What to Build
Your binary can be written in any language. The agent does not impose framework or runtime requirements — it starts your executable as a child process, tracks its exit code, and captures its output. The expected pattern is simple: start, do work, exit with a meaningful exit code.
A few things to plan from the start:
One build per OS. If you support Windows, Linux, and macOS, produce a separate binary for each platform. You can use one job JSON per OS or one job with multiple tasks and conditions, but separate builds per platform is simpler and easier to maintain.
Single purpose. Scan, report, or perform a maintenance task — then exit. Avoid designing a job task binary as a long-running daemon; that is what the plugin pattern is for.
Log via MQTT. Plan to publish structured log messages to the
KeeperLoggerMQTT topic rather than relying on stdout. Step 3 covers the full details.
Deploy the Binary on the Endpoint
Install your binary under the agent's application root. The recommended layout allows the agent to resolve your binary by name without an explicit full path:
In the job JSON, set the task's command field to {CommandName} (no path). The job runner checks Jobs/bin/{CommandName}/ before searching PATH.
If you cannot use this layout, set executablePath to a full absolute path. You still need to set command — it is required by the job schema — but executablePath takes precedence for resolution.
Cross-Platform Jobs and osFilter
Use osFilter on the job root so each endpoint only runs the task that matches its operating system. When osFilter excludes the current OS, the validator also skips checking whether that task's binary exists on disk — which means you can deploy a Windows job to a fleet that includes Linux agents without causing validation failures.
For multi-OS support, the simplest approach is a separate job JSON file per platform (for example my-tool-windows.json, my-tool-linux.json), each with its own binary path and osFilter. A single job with multiple tasks and osFilter conditions on each task is also supported but is more complex to maintain.
Execution Context
Set ExecutionType on the task to control which account the agent uses when starting your process.
Service
The default. Runs as the agent service account. Use this for background tasks — security scans, maintenance jobs, compliance reporters — that need machine-wide access but not an interactive user session.
User
Runs in the logged-on user's session. Use only when your tool genuinely needs user profile access interactively. Note that user-session processes are subject to additional trust checks described in the next section.
UserDesktop
Runs in the user's desktop session. Same caveats as User.
For most custom tools, Service is the right choice. If you use User or UserDesktop, read the signing and process trust section carefully — non-admin user-session processes are subject to additional restrictions.
Code Signing and Process Trust
This is one of the most important things to understand before you write a single line of job JSON. How the agent decides to trust your binary affects whether MQTT connections succeed and whether Plugin-tier HTTPS calls return data or 403.
How the Agent Registers Your Process
When the job runner starts your binary as a task, it adds that process to the agent's launched-process registry immediately after starting it. This registration is what grants your process:
Access to the local MQTT broker
Permission to call Plugin-tier HTTPS endpoints like
/api/PluginSettings/...
Because the registration happens before your binary makes its first connection, a normally launched job task does not need to pass a certificate check to connect to MQTT or call Plugin Settings.
When Certificate Checks Run
Certificate checks run when the agent encounters a process that is not already in the launched-process registry. This happens in two cases:
Your binary starts before the job runner's registration completes (a race condition on startup). The agent then falls back to a certificate-based check. The fix is a short retry loop on the first MQTT connection.
Your binary is started manually — from a shell, a script, or Explorer — rather than by a job. In this case the agent must validate the executable's signature against the Keeper Privilege Manager certificate or any thumbprints listed in
Settings:AlternativeSignatures. An unsigned binary fails this path.
Adding Your Certificate Thumbprint
If your binary needs to connect to MQTT or call Plugin Settings when started outside a job (for example during development or testing), add your code-signing certificate thumbprint to Settings:AlternativeSignatures in appsettings.json:
To get the thumbprint on Windows:
Use the thumbprint as a 40-character hex string with no spaces. You can list multiple thumbprints if you have more than one signing certificate.
Restart the Keeper Privilege Manager service after saving appsettings.json. These settings are read at startup.
AllowedNonAdminExecutables for User-Session Processes
If your binary runs in a user session (ExecutionType: User or UserDesktop) and the process owner is not an administrator, one additional check applies after the certificate check succeeds: the executable's base name (no path, no .exe) must appear in Settings:AllowedNonAdminExecutables.
This check does not apply to Service tasks, which run under the agent service account and follow system-session trust rules. If you only run your tool as a Service job task, you do not need to configure this setting.
Recommended Practice
Sign all production builds with your organization's code-signing identity (Windows Authenticode; Apple Developer ID on macOS; GPG-signed packages or IMA signing on Linux where your deployment enforces it).
On Linux, the agent's process trust relies primarily on the job runner launching your binary — ensure the binary is always started via a job task rather than manually to avoid the certificate check path.
Add your thumbprint to
Settings:AlternativeSignatureswhen MQTT or Plugin Settings access must work outside a job-launched context.Keep job binaries under
Jobs/, not underPlugins/. Plugin directory paths are subject to stricter validation rules that do not apply to binaries underJobs/bin/.Always run as
Serviceunless you have a specific reason to use a user-session execution type.
Author the Job JSON
The Job File and its Location
A job is a JSON file stored at:
The id field inside the file must exactly match the filename without the .json extension. For example, secrets-scanner.json must contain "id": "secrets-scanner".
Important: Use hyphens in job IDs, not underscores. The MQTT broker parses the job ID out of the client ID by splitting on underscores. An ID like secrets_scanner will break that parsing and can cause publish permission failures.
Last Known Good and How to Deploy Safely
In most deployments, the agent runs with Last Known Good (ConfigurationLkg) enabled. This means the agent keeps an encrypted reference copy of each job's JSON and watches the Jobs/ directory for drift from that reference. If it detects a file that doesn't match the stored copy — for example because someone edited it by hand — it restores the file from the reference copy, undoing the change.
For this reason, do not deploy jobs by dropping files directly into Jobs/. Use one of the two trusted write paths instead:
Option 1: The local HTTPS API. Post the job JSON to POST /api/Jobs on the local agent. This writes both the file and the Last Known Good reference in a single atomic operation. See Step 4 for the API details.
Option 2: A JobUpdate policy. In managed fleet environments, a JobUpdate policy delivered through the Keeper console is the standard path. The Configuration Policy Processor writes the job file and updates the reference copy as a blessed operation. Contact your Keeper administrator for the current policy schema and console workflow.
Anatomy of a Job JSON
Here is a complete, annotated example for a Windows job:
The key fields:
id — Must match the filename. Use hyphens only; no underscores.
enabled — Set to true. A job with enabled: false will not run, even if triggered manually.
schedule — Defines the timer trigger. intervalMinutes is minutes between runs, not clock-aligned wall time. 120 means every two hours.
events — Defines event triggers. The Startup event fires once when the agent starts, after jobs are loaded. A job can have both a schedule and events — either can trigger execution independently.
osFilter — Controls which operating systems run the job. Set only the target platform to true.
mqttTopics — Declares which MQTT topics your task may publish to and subscribe from. The broker enforces this list. If KeeperLogger is missing here, your process's publish calls will be denied even if the MQTT connection succeeds.
tasks — The array of steps to run. For most custom tools, one task is enough. Tasks run sequentially.
arguments — Supports variable substitution. {KeeperApiBaseUrl} is replaced by the agent with the local HTTPS API base URL (for example https://127.0.0.1:6889). Your binary parses this flag and uses it to call Plugin Settings at runtime. See the code example in the Overview page.
Schedule Options
The job supports four scheduling modes. Only one mode may be active per job.
Interval — Runs every N minutes from when the scheduler starts:
Cron — Standard five-field cron expression (minute, hour, day-of-month, month, day-of-week):
One-time — Runs once at a specific UTC time:
Calendar — Runs at specific times on specific days of the week or month:
Startup vs. Interval Timing
A job with both a Startup event and an intervalMinutes schedule will run at startup (once, when the agent starts) and then again on the interval from that point forward. The interval timer runs independently — do not assume a fixed relationship between the Startup run and the first interval run without testing on your target agent version.
When the agent starts, Startup jobs are triggered after jobs are loaded and after KeeperLogger is ready to receive messages. The interval timer's first tick occurs after the configured number of minutes from when the scheduler initializes — not from when the Startup run completes.
Deploy the Job
Using the Local HTTPS API
The agent exposes a REST API on the loopback interface for managing jobs. The HTTPS listener runs on Settings:KestrelHttpsPort (commonly 6889). All job management endpoints require Admin-tier authorization — your deployment team will provide the authentication method (typically mutual TLS with client certificates provisioned for your environment).
To create or update a job:
The key job management endpoints:
POST
/api/Jobs
Create a job
PUT
/api/Jobs/{jobId}
Replace an existing job
DELETE
/api/Jobs/{jobId}
Remove a job
POST
/api/Jobs/validate
Validate JSON without saving
GET
/api/Jobs
List all jobs
GET
/api/Jobs/{jobId}
Get one job definition
POST
/api/Jobs/{jobId}/trigger
Trigger a run manually
For the full API reference including Plugin Settings endpoints and authorization details, see the HTTP Reference.
Validation Requires Binaries to be Present
POST /api/Jobs and POST /api/Jobs/validate run the job validator, which checks that your task's executable exists on disk at the time of the call. This means you must deploy the binary before registering the job via the API, or validate on a machine that already has the binary at the expected path.
If the job's osFilter excludes the current operating system, binary existence checks are skipped for that validation run. This lets you register a Linux-only job from a Windows host.
Using a JobUpdate Policy
In managed fleet environments, the typical deployment path is a JobUpdate policy delivered through the Keeper console. Your Keeper administrator handles this — you provide the job JSON, and they configure the policy envelope and push it to endpoints. Contact your administrator for the current policy schema and console steps. See also: Create, Modify, or Delete Job
Connect to MQTT and Publish Logs
Once your binary is deployed and the job is registered, you need to wire up MQTT so log messages flow through the agent's logging pipeline.
Step 1: Declare MQTT Permissions on the Job
Add mqttTopics to the job root object. The broker enforces this list — if a topic is missing, publishes to it will be denied even if the MQTT connection itself succeeds.
If your tool also publishes to a custom event topic, add that topic to allowedPublications as well.
allowedSubscriptions is only needed if your tool subscribes to MQTT topics. Most job task binaries do not.
Step 2: Read the Broker Address from Plugin Settings
When the job defines mqttTopics with at least one entry, the agent sets two environment variables before starting your task:
KEEPER_JOB_ID
The id field from your job JSON
KEEPER_JOB_NAME
The name field from your job JSON
Your binary needs KEEPER_JOB_ID to form the MQTT client ID. It needs the broker address from Plugin Settings. Both happen at startup:
A few things to remember here:
This call requires Plugin-tier auth. It only succeeds from a process the agent launched. A manually started copy of your binary will get
403.The agent uses a self-signed TLS certificate on the loopback interface. If your organization provides a CA bundle for the agent's certificate, configure your HTTP client to use it. Otherwise, disable certificate verification for loopback-only connections per your security policy.
Always fall back to defaults on failure, but log a warning. A failed Plugin Settings call in production is a configuration problem operators need to see.
Step 3: Form the MQTT Client ID
The broker validates your client ID against the job it's associated with. For job tasks, the format is:
Where:
{KEEPER_JOB_ID}is the value from the environment variable the agent set{ExecutableToken}is a short, stable name for your binary (no path characters, no spaces, no underscores){ProcessId}is the current OS process ID as a decimal integer
Do not use underscores in the job ID. The broker splits the client ID on _ to extract the job ID segment. If your job ID contains underscores — for example secrets_scanner — the broker will mis-parse it, fail to associate the connection with the right job, and publish permission checks will fail. Use hyphens: secrets-scanner.
Step 4: Connect to the MQTT Broker
Connect using TLS to the host and port you retrieved from Plugin Settings. The broker requires an encrypted connection — plain TCP is not accepted.
Note on timing: If your binary connects to MQTT immediately on startup and the connection is refused, it is likely a race between your connection attempt and the agent's process registration completing. Add a short retry loop — one or two seconds — before treating the connection as failed.
Prefer MQTT v5 when your client library supports it. The agent's internal components use MQTT v5.
Step 5: Publish Log Messages to KeeperLogger
Publish to the topic KeeperLogger with QoS 1 and retain: false. Each publish must be a single JSON object in the following shape — property names are case-sensitive:
Field Reference:
Id
A new random UUID string for each message
Version
1
RespondToTopic
null
MetaData.MessageType
4 (or the string "Log")
MetaData.LogLevel
0 Debug, 1 Info, 2 Warning, 3 Error, 4 Critical, 5 Verbose — or the string equivalents
MetaData.Source
Your tool's name, shown in logs
MetaData.Category
A short label for the phase or component
MetaData.Message
Human-readable text — must be non-empty. Do not include raw secret values; log counts and paths instead
MetaData.CorrelationId
Optional trace ID; use "" if unused
MetaData.Context
Optional extra text; use "" if unused
A complete publish example in Python:
A few things to keep in mind:
Messagemust be non-empty. The logger rejects empty messages.Do not include raw secrets in log messages. Log counts, file paths, and status — never the credential values themselves.
Avoid raw newlines inside
MessageandContext. Replace them with spaces if you are logging scraped file content.
Optional: Job Progress Over MQTT
If you need downstream subscribers to observe job execution events — separate from the structured log messages going to KeeperLogger — you can publish to the job's event topic.
The default event topic pattern is Jobs/{jobId}/events. You can also set a custom topic with the eventTopic field on the job root:
The event topic must also appear in allowedPublications. If it is missing from that list, publish calls to it will be denied even though the connection succeeds. When you add an eventTopic, always verify that both eventTopic and allowedPublications contain the same topic string.
Pre-Launch Checklist
Run through every item on a pilot endpoint before rolling out to your fleet.
1
Binary exists at the resolved command / executablePath on the endpoint before POST /api/Jobs is called
2
Job id matches filename; no underscores in the id
3
enabled is true
4
osFilter matches the target platform
5
mqttTopics.allowedPublications includes KeeperLogger (and the eventTopic if used)
6
Job appears in GET /api/Jobs after deployment
7
POST /api/Jobs/{jobId}/trigger runs the task successfully (Admin auth required)
8
Running task receives KEEPER_JOB_ID and KEEPER_JOB_NAME environment variables
9
GET /api/PluginSettings/KeeperPrivilegeManager returns broker.host and broker.port from inside the running task
10
MQTT connects with TLS and the correct {jobId}_Token_{pid} client ID format
11
Publish to KeeperLogger succeeds and log messages appear in the operator's log view
12
If using eventTopic: publish to that topic succeeds
13
Code signing: production binary is signed; thumbprint is in AlternativeSignatures if needed
Troubleshooting
POST /api/Jobs returns 403
Admin auth — wrong certificate or the calling process is not admin-authorized
POST /api/Jobs returns 400 with validation errors
Binary not found at the path on the validating host; deploy binary first
Job appears in GET /api/Jobs but does not run
Check enabled: true; check osFilter matches the endpoint OS
Hand-edited Jobs/*.json is reverted
Last Known Good is enabled — use the API or JobUpdate policy instead
GET /api/PluginSettings/... returns 403 from inside the task
Process not registered as trusted; ensure it is started by the job runner, not manually
MQTT connection refused
Process not in launched-process registry; add retry logic on startup
MQTT connects but publish is denied
Topic missing from mqttTopics.allowedPublications in the job JSON
Client ID parsing errors / wrong job association
Underscores in job id — switch to hyphens
Log messages not appearing in operator view
KeeperLogger may not be running or subscribed; confirm with your administrator
Last updated

