Skip to main content

Troubleshooting: campaign and journey enrollment errors

Enrollment and graph-validation errors are refused at the door — before any contact enters the journey, and before a single message is sent. This page covers the four campaign-side refusal codes, the four journey-graph validation codes, and the one executor-recovery code on the resume path. For failures that happen inside a running journey (failed execution rows, misbehaving nodes), see Troubleshooting: a flow execution that failed. The four surfaces this page debugs: The first two are the enrollment endpoints; the third refuses AUDIENCE_TOO_LARGE at launch; the fourth hits the re-validation safeguard covered below.

The enrollment validation flow

Every enrollment request runs the same gate sequence. The first gate that fails returns its own code, so the response tells you which step rejected it:
The resume path adds a pre-flight gate on step 4: before the executor runs, the stored journey definition is re-validated against the same graph rules, so a journey cannot re-enter on a graph that drifted after it started.

Campaign-side refusals

These codes come from the campaign record or the launch itself — the graph never gets looked at.

Journey-graph validation

These codes mean the campaign’s step graph could not resolve an entry point. Fix the graph in the campaign editor, then retry enrollment. JOURNEY_DEFINITION_INVALID, JOURNEY_TRIGGER_MISSING, and JOURNEY_TRIGGER_DISCONNECTED fire at enrollment time; JOURNEY_VALIDATION_FAILED fires at resume time. Same validator, different gate.

Executor recovery: RESUME_FAILED and the resume safeguard

When a journey enrollment pauses (a delay node, a send-time schedule), the scheduler later calls the resume endpoint to continue that contact. Two outcomes matter on that path:
  • JOURNEY_VALIDATION_FAILED (400) — the resume safeguard. Before the executor runs a single node, the stored journey definition is re-validated. If someone edited the graph after the campaign started (deleted the trigger, orphaned a node the paused enrollment was parked on), resume refuses and the response names the validation error. This refusal is a safeguard catching a graph drift, not a user error you need to retry through — fix the graph, and the scheduler picks the enrollment up on its next sweep.
  • RESUME_FAILED (500) — the executor itself threw mid-resume (the message field carries the underlying error). The scheduler counts the attempt, backs off exponentially, and retries; after its attempt ceiling it stops retrying the enrollment instead of looping on it. A RESUME_FAILED that keeps recurring on the same enrollment after the upstream cause is fixed (a dead webhook, an upstream 5xx) should be escalated with the campaign id, contact id, and tenant id.
Recovery, then: correctly form the graph, let the scheduler back off, and expect eventual recovery. Nothing in this table asks you to poll or manually re-trigger — enrollment attempts that fail are re-swept, and a 400 refusal stops retrying precisely because retrying cannot change it.

When to escalate

Escalate to support with the campaign id, the raw error response, and your tenant id (Settings → Organization) when:
  • The same contact gets ALREADY_ENROLLED after its earlier enrollment visibly completed or was cancelled.
  • A graph reads valid in the editor but enrollment still returns JOURNEY_DEFINITION_INVALID (a stale definition is usually the culprit; export the campaign as JSON so support can diff).
  • RESUME_FAILED persists across backoff cycles after the upstream cause is fixed, or resume attempts drain without a resolution.

See also