Skip to content

Pipeline isn't triggering from real traffic

Symptom: you POST to /webhook expecting a pipeline to run, and either nothing happens, or the wrong pipeline runs, or you get an error back. In every case, the webhook response itself tells you exactly which of the following it is — read the "status" field before guessing.

{"status": "accepted", "run_id": "..."}

This is the only shape that means a run actually started. Anything else means one of the checks below stopped it before a run was created:

status in the response What it means
"accepted" A run started. If you still don’t see it, that’s a different problem — see Webhook says accepted but the run 404s.
"skipped_testing" The pipeline is stage: testing and this call didn’t pass allow_testing=true. See below.
"deduplicated" A run for this same alert is already running or ran recently — not a failure, see Idempotency & deduplication.
HTTP 400, "Unknown source" The ?source= (or X-Pipeline-Source header) value doesn’t match a registered parser. Registered sources are named in the error itself.
HTTP 422, "No pipeline matched" The request parsed fine, but no pipeline’s trigger.match accepted it. See below.

Every pipeline in this series — and every pipeline by default — starts as stage: testing. A stage: testing pipeline does not fire from real ingestion traffic, on purpose, so a half-finished pipeline can’t accidentally page someone:

Terminal window
POST /webhook?source=alertmanager
# → {"status": "skipped_testing", ...} — testing pipeline, no override
POST /webhook?source=alertmanager&allow_testing=true
# → {"status": "accepted", "run_id": "..."} — deliberately opted in

If you’re developing a pipeline, add &allow_testing=true to every trigger call while you’re working on it (every tutorial in this series does exactly this). If you’re integrating a real system — Alertmanager, whatever’s actually calling this URL — and it’s not passing that query param, the fix isn’t to add it there; it’s to promote the pipeline to stage: production once it’s actually ready, since real traffic shouldn’t need a special flag to work at all. See Testing vs production stages for the full reasoning.

The Run now button in the UI and a manual re-run always bypass this — they’re deliberate manual actions, not real ingestion traffic — so a pipeline that works from Run now but not from a real webhook call is almost always this, not a broken pipeline.

trigger.match conditions are AND, not OR — and case-sensitive

Section titled “trigger.match conditions are AND, not OR — and case-sensitive”
trigger:
match:
severity: critical
environment: prod

Every condition in trigger.match has to pass for this pipeline to be selected — this is an AND, not “match any of these.” A payload with severity: critical but environment: staging matches neither condition partially; it simply doesn’t match this pipeline, and the resolver moves on to the next one. If nothing matches, the whole request 422s with "No pipeline matched for source=... severity=... labels=..." — that detail message names exactly what VectorStep saw, which is the fastest way to spot a typo or a case mismatch (Critical vs critical never match — a plain scalar match is exact-equality, not case-insensitive).

If you need “any of several values,” a plain scalar won’t do it — reach for the in operator instead:

trigger:
match:
environment:
in: [prod, staging] # matches either, not just one

See Webhooks — Pipeline resolution for the full operator table (eq/ne/in/not_in/regex/gt/gte/lt/lte).

Configs are matched in load order, first match wins. If two pipelines could both match the same payload, whichever one is more specific should be ordered first — an accidental catch-all pipeline earlier in pipeline_config_dir will silently claim traffic meant for a more specific one later in the list, with no error at all, since as far as the resolver is concerned the request was handled successfully.

Confirm the field names, not just the values

Section titled “Confirm the field names, not just the values”

trigger.match keys are checked against NormalisedContext’s top-level fields (source, severity, summary) first, then against context.labels — so environment: prod only works if environment is actually a label on the normalised payload, not a field the reader assumes exists. For the Alertmanager source specifically, this means an Alertmanager label, not something invented in the pipeline’s own YAML. Webhooks — Normalisation layer shows exactly what a payload becomes before matching happens — worth checking directly if a match you’re confident should work still isn’t firing.