Skip to main content

Notify when a deployment fully shifts traffic

🎯 Goal: Run custom logic the instant a deployment is fully serving production traffic, not on every intermediate update.

Introduction​

Deployment updates fire often during a rollout: progress percentages, health checks, traffic switch increments, and a final status change. Most of the time you don't want to react to all of them. You want one signal that says "this deployment is now the live one."

The pattern: create an entity hook that subscribes to deployment:write events, then attach a notification channel that filters on the request body, so your endpoint only sees the events where traffic actually moved to 100%. The hook itself stays generic; the channel does the filtering. See Conditional hook firing for the underlying mechanism.

When you'd use this hook​

This pattern is useful any time the action you want to take has to happen after users are actually being served by the new revision, not during the rollout, and not on every intermediate progress update. A few concrete examples:

  • Run a post-deploy test suite. Kick off smoke tests, synthetic monitoring, or end-to-end checks the moment 100% of traffic is on the new revision, so you measure the version your users are hitting.
  • Close out the Jira ticket attached to the deployment. Move the issue to Done, post a comment with the deployment URL, or update a custom "Released in" field, automatically, only once the rollout is complete.
  • Trigger customer-facing announcements. Drop a release note in a status page, post to a #changelog Slack channel, or email a customer segment that the feature they were waiting on is now live everywhere.

The common thread: each of these is a one-shot action that should fire exactly once per deployment, when the new version is fully live.

What you'll set up​

By the end of this guide, you'll have:

  • An after-hook on deployment:write that intercepts every update
  • A notification channel with filters that only forwards the events you care about (we cover two options: traffic-100 only, or traffic-100 plus finalized-status fallback)

1. Create the hook action​

The hook subscribes to deployment updates. It carries no filtering of its own: every update will trigger the hook internally; the channel decides what reaches you.

np entity-hook action create \
--body '{
"nrn": "<account nrn or namespace nrn>",
"entity": "deployment",
"action": "deployment:update",
"dimensions": {},
"when": "after",
"type": "hook",
"on": "update"
}'
  • when: "after": the hook runs after nullplatform has already applied the update, which is the right place to react to a settled state.
  • on: "update": only fires for updates, not creates or deletes.

2. Create the notification channel​

The filter lives here, on the notification channel. The framework injects the request body into the notification's context.request_body, and channel filters are evaluated against the context object, so filter paths start with request_body.<field> (no context. prefix).

You have two options, depending on how strict you want the "deployment is done" signal to be:

  • Option A matches only when traffic reaches exactly 100%. Simpler, but skips fast-finalize cases.
  • Option B matches traffic-100 or a finalized status. Broader, but requires an idempotent handler.

Option A: Only on full traffic shift​

np notification channel create \
--body '{
"nrn": "<account nrn or namespace nrn>",
"source": ["entity"],
"type": "http",
"configuration": {
"url": "https://yourdomain.com/deployment-finalized"
},
"filters": {
"request_body.strategy_data.switched_traffic": 100
}
}'

That's it. Every deployment update still triggers the hook, but the channel only delivers the ones where strategy_data.switched_traffic equals exactly 100. Your endpoint will only see "done" events.

Watch out for fast finalizations

Some deployment strategies finalize a deployment without going through a 100% traffic shift. For example, if the rollout is aborted and rolled back, or if a strategy completes by promoting the revision in a single step. In those cases, strategy_data.switched_traffic may never be exactly 100, and the channel above will silently skip the event.

If you need to react to every "this deployment is now done" signal regardless of how it finished, use the broader filter below instead.

Option B: Cover both traffic-100 and finalized statuses​

To catch both the traffic-shift case and the fast-finalize case, combine the conditions with $or on the channel filter:

np notification channel create \
--body '{
"nrn": "<account nrn or namespace nrn>",
"source": ["entity"],
"type": "http",
"configuration": {
"url": "https://yourdomain.com/deployment-finalized"
},
"filters": {
"$or": [
{ "request_body.strategy_data.switched_traffic": 100 },
{ "request_body.status": "finalized" }
]
}
}'

Now the channel forwards when either condition holds. The trade-off: a deployment that goes through a normal rollout will hit this channel twice, once when traffic reaches 100 and once when the status transitions to finalized. Make sure your handler is idempotent (or de-duplicate by notification.entity_id on your side) before relying on this filter in production.

3. Implement the handler​

Once both the hook and the channel are in place, nullplatform starts delivering filtered notifications. The payload your endpoint receives looks like this:

POST https://yourdomain.com/deployment-finalized
{
"id": "1180cd02-1c36-4274-8e7a-4483b87e8f2e",
"source": "entity",
"event": "deployment:write",
"created_at": "2026-05-19T14:20:43.088Z",
"notification": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"entity": "deployment",
"entity_id": "5",
"nrn": "organization=1:account=2:namespace=3:application=4:scope=5",
"callback_url": "https://api.nullplatform.com/entity_hook/550e8400-e29b-41d4-a716-446655440000",
"type": "hook",
"when": "after",
"on": "update",
"request_body": {
"status": "finalized", // present if you're using the status filter (Option B)
"strategy_data": { "switched_traffic": 100 }
}
}
}

For an after-hook, nullplatform expects an acknowledgement. See Implementing hook processing for the full callback format.

Where to go next​