Rules

Declarative filters, transforms, and routing applied before events reach your agent.

A rule is a small declarative expression that runs against every event flowing through your project. Rules decide what to do before anything reaches your agent or your endpoints:

  • Drop noisy events (bot PRs, test-mode webhooks, health-check pings).
  • Transform fields in the event’s data block — redact secrets, lowercase emails, trim whitespace.
  • Route matching events to specific endpoints, overriding the default fan-out.
  • Tag events for downstream search / filtering.

Rules live in the dashboard at Projects → your project → Rules. They’re declarative — no code, no redeploys — and evaluate fast enough to be invisible in your latency.

Where rules run

Rules fire after Nevo persists the event, before fan-out to SDK clients and HTTP endpoints. That means:

  • The stored event keeps the verbatim source envelope in raw_payload — transforms don’t rewrite audit history.
  • event.data that your SDK receives reflects whatever transforms matched.
  • Dropped events are persisted (with status=dropped) so the dashboard shows exactly which rule killed which event.

Ingest-stage rules (fire before persistence) are planned but not yet enabled in the dashboard.

Match tree

Match against any field in the Nevo event using dotted JSON paths:

{
  "all": [
    { "path": "channel.type",              "op": "eq",         "value": "webhook" },
    { "path": "data.body.action",          "op": "in",         "value": ["opened", "reopened"] },
    { "any": [
        { "path": "data.body.user.login",  "op": "ends_with",  "value": "[bot]" },
        { "path": "data.body.pr.title",    "op": "matches",    "value": "^WIP:" }
    ]}
  ]
}

Paths are free-form — anything under data.body for webhooks works even though webhook payloads have arbitrary shapes. No registered schema required.

Operators (14)

OperatorSemantics
eq / neqStrict equality. Numbers coerce, strings compare byte-wise.
containsSubstring (strings) or set membership (arrays).
not_containsInverse.
starts_withString prefix.
ends_withString suffix.
in / not_inMembership in a JSON array value.
matchesRE2 regex match (max 200 chars to block catastrophic backtracking).
not_matchesInverse.
gt / gte / lt / lteNumeric compare; string fallback for timestamps / sortable IDs.
exists / missingPresence test; does not care about the value.

Group logic

  • { "all": [...] } — AND; every child must match.
  • { "any": [...] } — OR; at least one child must match.
  • A bare leaf ({ path, op, value }) is an implicit single-condition match.
  • Groups nest arbitrarily. The dashboard’s visual builder covers flat single-group rules; deeper trees fall back to the JSON editor.

Missing paths fail closed. A rule that references data.body.typo when the field doesn’t exist evaluates to false for everything except missing. Typos don’t accidentally match every event.

Actions

A rule has exactly one action. Multiple rules matching the same event compose — transforms, routes, and tags accumulate; the first drop short-circuits the rest.

drop

{ "type": "drop" }

Event is persisted with status=dropped, skipped from realtime fan-out, and no endpoint POSTs fire. The matching rule name is visible in the event detail panel.

transform

{
  "type": "transform",
  "ops": [
    { "op": "set",       "path": "data.priority", "value": "low" },
    { "op": "redact",    "path": "data.text",     "regex": "sk-[A-Za-z0-9]{10,}", "replace": "[REDACTED]" },
    { "op": "uppercase", "path": "data.subject" },
    { "op": "lowercase", "path": "data.email" },
    { "op": "trim",      "path": "data.note" }
  ]
}

Transforms mutate only the projected Nevo event — the one SDKs and endpoints see. raw_payload stays verbatim. Ops apply in order, so uppercase-then-trim works as expected.

Missing paths are silently skipped (user config shouldn’t crash unrelated events).

route_only

{ "type": "route_only", "endpoint_ids": ["<uuid>", "<uuid>"] }

Overrides the default fan-out — only the listed endpoints receive the event. Useful for triage: send P0 events to the oncall endpoint and normal events everywhere else.

tag

{ "type": "tag", "tags": ["urgent", "p0"] }

Attaches labels to the event. Tags are surfaced on the event record for search and downstream filtering.

Priority + evaluation order

Each rule has a priority (int, lower = higher priority). Rules are evaluated priority-first, ties broken by rule ID for determinism.

The first matching drop short-circuits the walk — later rules don’t run. Non-drop actions accumulate:

rule A (priority 10): transform → data.title uppercase
rule B (priority 20): tag ["processed"]
rule C (priority 30): drop  ← never reached if A + B already matched drop-shaped conditions

Dry-run

Before enabling a rule, run it against historical events. The dashboard gives you 1h / 24h / 7d windows (7d max):

POST /projects/{id}/rules/dry-run
{ "stage": "deliver", "match": {...}, "action": {...}, "windowHours": 24 }

Returns:

  • matched / scanned counts
  • byAction breakdown (drop: 12, transform: 3, …)
  • Up to 50 sampled events with:
    • the event’s data as the rule saw it
    • for transforms: after diff
    • for route_only: the endpoint IDs that would receive it
    • for tag: the tags that would be added

Read-only — zero writes, safe to run repeatedly.

Import / export

Rules are portable:

  • Export — dashboard → Rules → export downloads all project rules as JSON (name, description, stage, match, action, priority). Server-side fields (id, counts) are stripped.
  • Import — upload a JSON file. Entries can be a single object or an array; each is validated individually, malformed entries are skipped with a summary toast.

Use this to copy rules between projects, back them up, or bootstrap a new project from a known-good set.

Templates

The dashboard ships with a curated library of starter rules:

  • Drop GitHub bot PRs — silence dependabot / renovate / github-actions noise
  • Drop Stripe test events — skip all livemode=false webhooks
  • Redact API keys in webhook bodies — mask sk-… and sk_live_… strings
  • Tag urgent Slack mentions — attach urgent to messages containing <!here> or <!channel>

Click templates on the rules page to preview + one-click create. The template is just a pre-filled form; tweak before saving.

Plan limits

TierRules per project
Hobby1
Hacker5
Pro25
Scaleunlimited

Hitting the limit surfaces a 402 in the API with code: quota_exceeded; the dashboard shows an upgrade prompt.

Propagation

Rule edits in the dashboard apply to incoming events within about a minute. Regex patterns have a 200-character limit — longer patterns are rejected when you save.

  • Events — the event shape rules match against.
  • Endpoints — the HTTP destinations rules can route to.
  • Dashboard → Rules for the editor and dry-run.