Skip to main content
A workflow is an automation that responds to an event in Nash and runs a series of actions. Workflows let you encode operational logic — “when an order is created, if it’s high value, apply the white-glove dispatch strategy and notify the team” — so the platform handles decisioning instead of your application. Workflows are how Nash determines which dispatch strategy an order flows into, and how it automates order-level decisions using attributes like order value, product type, destination, SLA, or merchant-defined metadata. Workflows

What workflows are

A workflow is built from nodes connected by edges, plus a trigger that defines when it runs.
  • Nodes are the steps of the workflow. Each node has a type:
    • trigger — the entry point that starts the workflow
    • filter — evaluates conditions and branches the flow
    • action — performs an operation
    • switch — routes execution based on a value
  • Edges connect nodes and define execution order. An edge type of default is used for trigger and action nodes. For filter nodes, success follows when the condition is met and negative follows when it is not.
  • A trigger defines the event that activates the workflow.
A workflow moves through a lifecycle of statuses: draft, active, inactive, archived, and template. New workflows are created in draft by default and must be set to active to run. Updating a workflow’s structure (nodes, edges, and trigger together) creates a new version.

Actions

Action nodes perform operations on the order or delivery, or send notifications. Supported actions include:
  • Apply a dispatch strategy or an optimization strategy
  • Modify order price or tip — set a value outright, or bound one: At least raises anything under a floor, At most caps anything over a ceiling, and both leave a value already inside the bound alone
  • Add a tag or extract a metadata field
  • Flag a delivery, auto-reassign, cancel a delivery, cancel an order, or remove an order from a route
  • Raise, update, resolve, or assign a route flag so a detected condition becomes durable work on the Execute page rather than a notification someone has to catch
  • Add requirements to an order
  • Mark an order invalid from a validation workflow, so a rule of your own parks the order alongside Nash’s built-in checks — see Validate orders with your own rules
  • Send a notification by email, SMS, Slack, or Teams channel message
  • Summarize, run an agent call or browser automation, or emit a custom event
  • Send an HTTP request to a system of your own
  • Hand the step to a Custom Agent and branch on what it reports back
The exact set of actions, their configuration schemas, and the available filter fields and operators are self-describing. Call GET /v1/workflows/schema to retrieve the current triggers, action config schemas, filter fields and operators, and supported node, edge, and status types.

Triggers & conditions

A workflow’s trigger specifies what activates it. Trigger types include event, manual, webhook, and cron. Event triggers listen for a named event in Nash — for example order.created or order.creating. (Currently, the REST API for creating workflows supports the event trigger type; other trigger types exist in the platform but are not all exposed through that endpoint.) A trigger built on a delivery status transition can name several statuses in deliveryStatuses and fires on each of them on its own, so one workflow covers pickup and dropoff without being built twice. Conditions are expressed with filter nodes. Each filter holds one or more conditions made up of a field, an operator, and a value. When the filter evaluates true, execution follows the success edge; otherwise it follows the negative edge — letting you branch a workflow based on order attributes. The available fields and operators are returned by GET /v1/workflows/schema.

Fire against a pickup or dropoff window

Most triggers react to something that has happened. Four react to a time that is coming. order_pickup_start_time, order_pickup_end_time, order_dropoff_start_time, and order_dropoff_end_time fire against the order’s own window timestamps, offset by however long you choose — “30 minutes before the dropoff window ends”. Each takes a direction of before, at, or after and an offset_minutes. This is the trigger for the order that hasn’t moved: a pickup due in twenty minutes with no provider on it, a dropoff window about to close on a store that hasn’t packed. Nothing has happened, which is the problem. If the window moves after the timer is armed, Nash arms a new one against the new time and the superseded timer does nothing when it comes around.

Match an event from your catalog

An event trigger matches on the event’s name. If your organization keeps a catalog of custom event definitions, point the trigger at a definition instead of typing that name: send customEventDefinitionId on the trigger and Nash stamps the definition’s key as the event name it matches on. The two can’t drift apart afterwards, and a definition something is still listening for can’t be archived out from under the workflow — whether the listener is a workflow trigger or a plain notification. The same reference works on the writing side. POST /v1/order/{id}/events and its job equivalent accept customEventDefinitionId in place of name, and so does the emit_custom_event action. Send one or the other; sending both is rejected, as is a definition from another organization or one that has been archived.

Branching on whether a field is there

Two operators take no value at all. Is set and Is missing ask whether a field or a metadata key is present, which is a different question from what it contains. Only null and genuinely absent values count as missing. 0, an empty string, and an empty list are all set — so a condition asking whether someone filled in a tip doesn’t quietly treat a deliberate zero as a blank. A presence check against context the workflow doesn’t have yet, such as a delivery on an order that hasn’t been dispatched, resolves to missing rather than failing the step.

Fields worth branching on

The field catalog spans the order, its packages, the job (delivery), and the delivery attempt, and it is served live by GET /v1/workflows/schema — the Portal’s workflow editor reads it from there, so new fields appear without a Portal release. Alongside the address, value, tag, and requirement fields, it carries the numbers a routing or escalation decision usually turns on: Order fields are available on every trigger that has an order behind it, including delivery, job, and delivery-attempt triggers, so a delivery-status workflow can branch on the order’s value or tags. A condition on an order field in one of those contexts used to resolve to nothing; if you built one earlier and it never took the branch you expected, it does now. In the Portal, the field picker offers only fields whose entity can exist for the trigger you’ve chosen, and marks a field that may be empty for that trigger — a delivery field on an order-created trigger, say — so a condition that can never match isn’t offered as though it could.

Comparing times

Date and time fields carry their own operators rather than being matched as text. A condition can compare a timestamp against a fixed moment, against a relative window — within the last, more than, less than a duration you name — or against another datetime field, which is how you ask whether a dropoff window closes before its pickup window does. Evaluation is time zone aware, and a weekday reads the same regardless of where the person editing the workflow is sitting. A dispatch strategy is a field you can branch on too, so a workflow can ask which strategy an order is already flowing through before it decides to change it.

Branching on whether an event has happened

Is set asks whether a field has a value. A different question is whether something has happened to this order — has the store sent its store_ready event yet? Four filter-only fields answer it: order.custom_event, job.custom_event, task.custom_event, and delivery.custom_event, each paired with the operators Has occurred and Has not occurred and a value that is the exact key of the custom event you mean. Pick it from your catalog, or type a key by hand to match an archived or legacy one. A workflow on a pickup-window trigger can then continue only when the order has no store_ready event. The check is taken at the moment the trigger fires and stored with the run, so a worker that picks the run up late, or a retry, can’t see an event that arrived in between and flip the branch. If the entity the condition refers to can’t be resolved, the step errors rather than treating “not found” as “hasn’t happened”. These fields exist only to be filtered on; they don’t hydrate into a message.

Validate orders with your own rules

Nash checks every order it takes in — an address that won’t geocode, a pickup time in the past — and parks an order that fails as needing attention, with the reasons in validationErrors. Some rules are yours, not Nash’s: an unattended delivery needs dropoff instructions, a reference ID has to match your ERP’s format, a barcode has a fixed shape. A workflow on the Order validation trigger (order.validating) adds those checks to the same pass. The trigger runs synchronously inside every validation of an order — on creation, on every update, and on CSV upload — and the workflow can hold only filters, switches, and the Mark order invalid action. It runs before any order-created workflow, so a rule can’t depend on a field one of those fills in. Mark order invalid (add_validation_error in the API) takes a field and a message. The field is a camelCase key of up to 64 characters: use one of the order’s own fields, such as dropoffInstructions, and the Portal’s order form highlights that field, or use a key of your own. A few keys Nash uses itself are reserved. The message can carry {{order.*}} variables. The error lands in validationErrors next to Nash’s own, the order’s status becomes needs_attention, and the key clears on the next update that satisfies the rule. Every run, including one that marked nothing, appears in the workflow’s Runs tab. Two filter operators arrive with it. Matches pattern and Does not match pattern (matches_regex, not_matches_regex) test a string field against a regular expression, matched anywhere in the value — anchor with ^ and $ to check the whole thing. On a list field, matches pattern needs every item to match, and does not match pattern passes when any item fails. Patterns are compiled when you save, so a broken one is refused there rather than at run time. A validation workflow fails closed. If it can’t run, or one of its conditions can’t be evaluated, the order is marked with a reserved workflowValidation error naming the workflow, rather than slipping through as valid.
Two things this trigger can’t do: it can’t be run by hand, because a validation only makes sense inside an order’s own validation pass, and it can’t carry any action other than Mark order invalid. Nash refuses both when you save or activate the workflow.

Writing text with variables

Any text a workflow sends — an email subject, an SMS body, a Slack message, the channel it goes to — can carry {{variables}} that Nash fills in when the run reaches that step. Every notification action — email, SMS, Slack, Teams, and voice-agent calls — reads the same catalog the notification triggers use, so a message you write in a workflow and a message you write in Notifications have the same vocabulary. The catalog hydrates the destination and the subject as well as the body: a Teams channel or an SMS recipient can be a variable instead of a hard-coded address. A variable that can’t resolve at the node you’re editing is refused when you save, with the nearest match suggested, so a typo surfaces before the workflow runs rather than as a blank in a message. At run time a variable that resolves to nothing hydrates to empty text. On message actions a key that doesn’t exist at all fails the step; the Send HTTP request action is more forgiving, and sends anyway — see Call your own system. A Send email action takes several addresses, comma-separated. Each is checked against the deny list and delivered on its own, so one refused or bouncing address doesn’t take the rest with it. In the Portal, Fill addresses from destination copies the current addresses of one of your notification destinations into the field. It’s a one-time copy: a later change to the destination doesn’t follow. In the Portal’s editor, destination fields suggest only variables that could plausibly address that channel: phone numbers for SMS and voice, addresses for email, channels for Slack. The message body itself stays unrestricted.

Call your own system

Everything above acts on Nash. Sometimes the next step in your process lives somewhere else — mark the order picked in your WMS, open a ticket, tell your ERP the delivery failed. The Send HTTP request action makes that call from inside the workflow, so the handoff happens where the decision was made rather than in a listener you have to run and monitor yourself. It works in two halves, deliberately:
  • An administrator creates a connection. The connection owns the HTTPS origin, the credentials, which methods are allowed, the path prefix requests must sit under, and the names of the request fields a workflow may set. Credentials are write-only: they never appear in the workflow definition, and they are kept out of request logs and error reporting.
  • A workflow author references it. On the action node you set a relative path, query parameters, headers, a JSON body, a timeout, and what the workflow does if the call fails. Templates work here the same as anywhere else, so the body can carry the order’s own values.
The action can pull a value back out of the response with a JSON Pointer, and downstream filters see that value alongside the HTTP status, how long the call took, and any failure detail — so the branch after it can act on what your system actually said. A template variable with no value at run time doesn’t stop the call. It hydrates to empty text, or null for a whole value, and the request goes out, with each miss recorded on the run’s step so a request that left with an empty field is still explainable. When the call does fail, the step says at which stage — building the request, connecting, authenticating, transport, the HTTP status, or reading the response — so a request that was never sent isn’t reported as one that failed.
The action runs in the background and makes one attempt, carrying a stable idempotency key so your endpoint can recognize a repeat. It is not a durable delivery channel: treat it as a signal your system can act on, not as a queue that guarantees arrival. It also can’t hold up a synchronous run.

Running a workflow by hand

Triggers cover the routine cases. Sometimes you need to run one yourself — to apply a workflow to orders that predate it, or to check a change before you turn it on. Start a workflow on demand with the Trigger Workflow endpoint (POST /v1/workflows/{id}/trigger), passing entity references (such as the ID of a job, i.e. a delivery) and optional input data, in synchronous or asynchronous mode. A manual run works on a workflow in either active or inactive status. That’s the useful part of inactive: a workflow you’ve switched off no longer fires on its own, but you can still run it deliberately. Workflows in draft, archived, or template status are rejected. In the Portal, select the orders you want on the Orders page and choose Run Workflow from the action bar. Pick the workflow, add trigger input if it needs any, and Nash starts one run per selected order — each independent, each with its own result you can open. Selecting orders row by row is what’s supported here; a workflow run needs a concrete order to reference, so “select all results” across pages isn’t.

Test a workflow before you activate it

Running a workflow tells you what it does. Testing one tells you what it would do, which is what you want while you’re still writing it. The alternative is activating a workflow against live orders to find out whether a filter matches. Test a saved workflow with POST /v1/workflows/{id}/test, passing the jobId or orderId of the entity to evaluate it against. The response reports per-node status, so you can see which filters passed and which branch execution took.
Even a live test can’t change state. Actions like cancel_order or add_tag are preview-only in both modes, so testing against a real order can’t cancel or modify it. The one thing a live test really does is deliver messages. Use it to check that a Slack or email action is addressed and worded the way you meant.
This is the same dry run behind the Test drawer in the Portal’s workflow editor, so a workflow an agent or a script builds can be checked the same way a person would check it.

Hand a step to an agent

Filters and actions cover the decisions you can write down in advance. Some steps aren’t like that — read the notes on this order and decide whether anyone needs to be called, look at what the provider has done so far and judge whether the delivery is still recoverable. An agent node hands that step to one of your custom agents and lets the rest of the graph branch on what comes back. The agent receives the entity the workflow was triggered on, the trigger’s own metadata, and the output of every node upstream of it. It does the work — pulls the data it needs, calls the tools it’s allowed to use — and returns a written report, plus the structured fields that go with it if you’ve given the agent an output format. In the API it’s the invoke_agent action; in the Portal’s workflow editor it’s Run Custom Agent. Either way you pick the agent, write the task context for this particular step, and choose what the workflow branches on.

Branching on what it reports

The node writes its result into the workflow’s variables in two shapes: The typed per-field variables are the ones worth branching on: a number can be compared, a boolean tested, an enum matched, the same as any other filter field. The full report is the fallback for an agent with no output format — useful to carry into a notification, less useful to filter on.

What an agent node can’t do

  • It can’t take actions. Only report-only agents can be selected. An agent that acts would put its actions behind its own confirmation gate, and the workflow would carry on as though they had happened.
  • It can’t sit on a synchronous run. Workflows that run synchronously while an order is being created reject agent nodes, because the order would wait on the agent. Nash refuses the combination when you save the workflow, and again if a run attempts it.
  • It can’t run unbounded. Each node carries its own timeout and a choice of what the workflow does when the agent fails or runs out of time.

How workflows relate to dispatch strategies

Workflows and dispatch strategies are complementary:
  • A dispatch strategy answers “given this job, which provider should fulfill it?” — it defines the eligible providers, selection rule, and failover behavior.
  • A workflow answers “given this event and these conditions, what should happen?” — including which dispatch strategy to apply.
A common pattern is a workflow triggered on order creation, with filter nodes that branch on order value or destination, and dispatch_strategy action nodes that apply the right strategy to each branch. This is the same role served by Automations, which map account-level business rules to dispatch strategies; workflows generalize that idea to a broader set of triggers, conditions, and actions.

Next steps

Dispatch strategies

How Nash selects a provider for each delivery.

Automations

Map business rules to dispatch strategies.

Notifications

Configure delivery notifications to customers and systems.

How Nash works

See where workflows fit in the delivery lifecycle.