Skip to main content

Set up external parameters

The external-parameters backend doesn't store anything itself. It marks a {NRN, dimensions} tuple as values for this tuple are handled outside nullplatform and forwards every lifecycle event to a notification channel you configure. Your handler decides where the value lives, how it's read, and when it's deleted.

This is the right backend when no off-the-shelf integration matches your storage system, or you want to layer custom logic (audit, replication, multi-region routing) on top of parameter operations.

How it works

Three pieces work together:

  1. The provider, which you create in nullplatform with the slug external-parameters. It carries no attributes: its presence on a {NRN, dimensions} tuple is the signal.
  2. A notification channel subscribed to the parameter source. It tells nullplatform where to send lifecycle events.
  3. Your handler, reachable through the agent or as an HTTP endpoint depending on the channel type. It receives each event, runs your logic, and returns a result to nullplatform.

The handler contract is action-based. Each lifecycle event triggers one of four actions: parameter:store, parameter:retrieve, parameter:delete, parameter:notify. Your handler responds with a small JSON payload that nullplatform validates and persists.

Prerequisites

Before starting, make sure you have:

  • A reachable agent running inside your infrastructure. See Install the agent.
  • An API key with the Agent role attached. See Authenticate the agent.
  • The backend you'll write values to (cloud secret manager, internal vault, key-value store, etc.).

Step 1: Create the external-parameters provider

Create the marker provider on the NRN you want to route externally. It has no configurable attributes.

  1. Go to Platform settings > Security & Secrets > External parameters.
  2. Click + New provider, pick the resource (NRN) and any dimensions.
  3. There are no fields to fill in.
  4. Click Create provider.

The provider on its own doesn't do anything. The next step wires it up to your agent.

Step 2: Create the notification channel

Create an agent channel that listens for parameter lifecycle events and runs your handler scripts. The same channel handles all four actions: nullplatform passes the action name in the notification context, and your handler dispatches accordingly.

  1. Go to Platform settings > Notifications > Channels, and click + New channel.
create a channel for external parameters

A few things to note:

  • source: ["parameter"] is what makes this channel pick up parameter events. Don't add unrelated sources here; mixing them makes the handler harder to reason about.
  • selector matches a tag on the agent. Make sure at least one of your agents declares the same tag, otherwise the event has nowhere to go.
  • filters can narrow events further (for example, only events where parameter.secret = true). Leave it empty until you have a concrete reason to narrow it.

For the full channel reference, see Set up an agent notification channel.

Step 3: Implement the handler

The agent runs your script for each lifecycle event. It passes:

  • NP_ACTION (or whatever name you wired in command.data.environment): the action being processed (parameter:store, parameter:retrieve, parameter:delete, parameter:notify).
  • NP_ACTION_CONTEXT: a JSON blob with the full notification context (parameter name, value, NRN, dimensions, scope, secret flag, etc.).
  • EXTERNAL_ID: for retrieve and delete, the identifier returned by the previous store for the same value.
note

A working reference implementation lives in nullplatform/scopes, under parameters/keyvault. It targets Azure Key Vault but the contract is the same regardless of where you write to. Use it as a starting point.

What each action expects

parameter:store

Inputs (from NP_ACTION_CONTEXT):

{
"notification": {
"parameter_id": 12345,
"parameter_name": "DB_PASSWORD",
"value": "the-actual-secret",
"secret": true,
"entities": { "application": "advertising-api", "scope": "prod-us" },
"value_entities": { "scope": "prod-us" },
"dimensions": { "environment": "production" }
}
}

Output:

{
"external_id": "<id-you-pick-to-look-this-value-up-later>",
"metadata": {
"any": "extra info you want to keep alongside the reference"
}
}

The external_id is the only mandatory field. Nullplatform persists it as the value's reference and passes it back as EXTERNAL_ID for future retrieve and delete calls. Pick a deterministic identifier so the same {parameter, scope, dimensions} always resolves to the same record on your side.

parameter:retrieve

Inputs: EXTERNAL_ID env var set from the value's reference. The full context is also available in NP_ACTION_CONTEXT.

Output:

{
"value": "the-actual-secret"
}

If the value is missing from your backend, return { "value": "value not found" } and exit successfully. Nullplatform treats this as "deleted upstream" rather than as an error.

parameter:delete

Inputs: EXTERNAL_ID.

Output:

{
"success": true
}

Treat a missing record as success: the value is already gone. This keeps deletes idempotent under retries.

parameter:notify

Fires after a value is stored. Useful for audit trails or downstream replication. If you don't need anything here, return { "success": true } and move on. The Key Vault example is a no-op.

Putting it together

A minimal entrypoint script looks like this:

#!/bin/bash
set -euo pipefail

case "$NP_ACTION" in
parameter:store)
"$SERVICE_PATH/store"
;;
parameter:retrieve)
"$SERVICE_PATH/retrieve"
;;
parameter:delete)
"$SERVICE_PATH/delete"
;;
parameter:notify)
"$SERVICE_PATH/notify"
;;
*)
echo "ERROR: unknown action $NP_ACTION" >&2
exit 1
;;
esac

The four sibling scripts each produce the JSON described above on stdout. Anything written to stderr is captured by the agent and surfaced in nullplatform's notification logs.

Step 4: Verify the flow

Create a parameter value under the NRN you configured:

np parameter-value create \
--parameter <parameter-id> \
--nrn organization=1:account=2:namespace=3:application=4 \
--value "test-value"

Then check, in this order:

  1. The notification log. Each action should show up as a delivered notification. Failures appear with the stderr captured from your script.
  2. Your backend. The value should be present, keyed by the external_id your store script returned.
  3. Read it back. np parameter-value read <id> returns the value as if it lived in nullplatform. The retrieve action runs in the background to fetch it from your store.

If any step fails, see Troubleshooting below.

Coexistence with other backends

Only one backend per {NRN, dimensions} tuple

External parameters and HashiCorp Vault can't both apply to the same {NRN, dimensions} tuple. Configuring both creates ambiguity about who owns the value, and nullplatform rejects the second provider at creation time.

You can still mix backends across different NRNs or different dimensions. For example: dimensions: { environment: "production" } routes to Vault, dimensions: { environment: "staging" } routes to your handler. Each tuple resolves independently.

Troubleshooting

The notification never reaches my agent

  • Check the channel selector matches a tag on at least one agent. A channel without a matching agent silently swallows events.
  • Check source: ["parameter"] is set. A channel subscribed only to service or telemetry won't see parameter events.
  • Inspect the channel and the agent in Platform settings > Notifications > Channels.

parameter:store succeeds but reads return null

The external_id your store script returned was empty or null. Nullplatform stored an empty reference, so the subsequent retrieve has nothing to look up. Make sure store always emits {"external_id": "..."} with a non-empty string.

Reads return values from the previous backend

If you migrated from internal storage or HashiCorp Vault, existing parameter values still point to the old location. Only values created after the external-parameters provider exists go through your handler. Re-create the values you need in the new backend, or build a one-off migration that calls parameter-value create again.

The handler script can't find EXTERNAL_ID

EXTERNAL_ID is only set for retrieve and delete. For store and notify, read everything from NP_ACTION_CONTEXT instead.

Next steps