Skip to content

External interaction

orchestra_inbox lets a logged-in operator act on a parked step from an inbox. orchestra_interaction is its customer-facing counterpart: it lets an external party, typically not logged in, act on a parked step through a public link. A booking guest confirms a request, a customer signs a document on a partner site, an applicant fills a form: each is a wait the outside world advances.

Going deeper

This page is the overview. For a code-level, illustrated walkthrough of the URL mechanism, the capability token, and the security model, see External interaction internals.

How it works

A wait node opts in by declaring an External interaction node feature, choosing an interaction plugin and its settings. While the instance is parked on that node, the party visits the dispatcher:

/interaction/{instance}?token=<capability token>

The dispatcher reads the step the instance is parked on, the interaction that step declares, and hands the visitor to the matching plugin. Each step returns to the dispatcher, so the flow chains to the next interaction with no landing screen. When the instance is no longer running, or is parked on a step with no visitor interaction (an operator task, say), the dispatcher shows a neutral message instead.

Shipped interactions

  • Message: show a message while the step is parked (an "awaiting confirmation" notice). The workflow is resumed by something else (an operator, a timer), so this offers no visitor outcome. The body can carry tokens to insert process values, such as a reference number or a process variable.
  • Redirect: send the visitor to an external URL (an off-site form, a partner site, a document signer), optionally appending a return link so the external step can send them back.
  • Webform (in orchestra_interaction_webform): collect input on a Webform and resume the workflow on submission. See below.

A site can add its own interaction plugin; it is a tagged plugin like every other Orchestra extension point (see Extending Orchestra).

An interaction plugin is written once and reached two ways. The public dispatcher above is the bearer doorway: a capability-token link, for a party who is typically not logged in. The same plugin also runs behind the identity doorway, an interaction_task inbox task, for a logged-in assignee who reaches it from the inbox rather than a link. The plugin does not know or care which doorway opened it: it is handed a context that carries a continuation handle (an opaque "how to resume this step"), and resolving that handle is the doorway's job. The bearer doorway resolves it from the capability token; the task doorway resolves it under the inbox's assignment check, so a leaked task handle is inert for anyone but the assignee.

So a Webform can be a public form an outside party fills or a review an operator does in-site, with no change to the form or the plugin, only to which node type carries it: a wait node with an External interaction feature for the link, or an interaction_task node for the task.

Security model

The dispatcher routes are public, so a guessable instance id must not be enough to act on a run. The capability is a token, not the id:

  • It is self-encoding: one opaque string that names its own instance and carries its own expiry, both bound into an HMAC keyed by the site private key and hash salt. It cannot be forged, pointed at another instance, or have its expiry extended.
  • It is time-limited (30 days by default). A link that leaks into a web-server log, a Referer header, or a redirect to an external site stops working once it expires. Every dispatcher link is issued fresh with a new expiry as the flow chains, so a live party never meets an expired one.
  • A node whose declared interaction plugin is no longer installed resolves to "nothing to do" rather than erroring, so an uninstalled provider cannot turn a public link into a server error.

A visitor-initiated outcome (cancel, back) that an interaction allows is resumed through a small confirmation form: the link shows the form on a plain GET, and the run is resumed only on its POST submission. A prefetch, a link preview, or a scanner fetching the link cannot resume the workflow.

When the Redirect interaction passes a return link, that link carries the capability token, so the destination site receives it (in its logs and Referer). The token is time-limited, but enable "pass a return link" only for a destination you trust.

A link comes in two scopes, and the capability token records which. Both are built by InteractionUrlsInterface:

  • Instance-scoped (stepUrl($instance_id)): drives whatever step the instance is parked on, and chains from one step to the next as the run advances. This is the right link for a sequential flow, and for a single wait offered to several people where the first to act wins (email the same link to all of them).
  • Branch-scoped (stepUrlForToken($instance_id, $token_id)): drives one specific parked token. When an instance parks several visitor-interactive branches in parallel, mint one branch-scoped link per branch and send a distinct one to each person, so each drives only their own step and the others are untouched. Once that branch has been acted on, its link shows the neutral "nothing to do" message.

To enumerate the branches awaiting an external party (to build and send a link per recipient), use InteractionResolver::parkedInteractiveTokens($instance), which returns every parked token whose node declares an interaction. The webform integration scopes its binding to the specific parked token automatically, so a form always resumes the exact branch it was opened for, even if other branches are parked or the instance moves on before the form is submitted.

Webform

orchestra_interaction_webform makes any Webform a form step. It ships:

  • a webform interaction plugin chosen on the wait node's External interaction feature;
  • an Orchestra interaction Webform element: one hidden field that binds the form to its instance. The interaction prepopulates it with the self-encoding token, so this single element is the whole binding (no separate instance or token fields to add by hand);
  • an Orchestra: resume on submit Webform handler: on completion it validates the token, binds the submission to the instance as its source entity, resumes the parked step with a configured result (default submitted), and advances the run;
  • an Orchestra: start workflow on submit Webform handler: the mirror image, it starts a workflow when a fresh submission completes, binds that submission to the new instance, and seeds configured variables (the submission id, the submitter's uid) so later steps can reopen the form and assign work back to the original poster.

Setting it up takes no code:

  1. Build a Webform for the input to collect.
  2. Add one Orchestra interaction element to it (its key is free; both halves find it by type).
  3. Add the Orchestra: resume on submit handler and set the resume result (or Orchestra: start workflow on submit to begin a workflow from the submission).
  4. On the wait node, set External interaction to Webform and pick the form.

The submission is bound to the instance as the Webform source entity, so a later step can reopen it (the party's earlier answers are kept) and Views relates the two natively. A re-edit after the run has moved on resumes nothing.

Modes: collect, review, edit

The webform interaction renders in one of three modes, so the same form serves the whole life of a request:

  • Collect input (default): show the empty form to fill. This is the classic interactive wait.
  • Review (read-only): render an existing submission as a read-only summary, for a step that inspects what was submitted (a validator reading a request) without changing it.
  • Review (editable): reopen an existing submission for editing, for a step that revises what was submitted (the poster amending after a request for changes).

The review modes read the submission to show through a configured submission variable, the same variable the start (or a collect step) wrote the submission id into, so the interaction always reopens the right submission. An edit step can also prefill feedback: name a result variable and a target element, and the validator's comment is copied into a read-only element on the form, so the poster sees what to change while they edit. These are the pieces the orchestra_webform_example workflow wires together.

Chaining: who owns what happens after submit

By default a Webform shows its own confirmation page after submit, the same as any standalone form. Both handlers offer a Return to the workflow after submit option that hands that decision to the workflow instead: once the step has resumed (or the run has started), the visitor is sent on to the interaction dispatcher, which renders whatever the run parked on next, with no landing screen. That is what lets one workflow chain several forms, or show a workflow-handled confirmation page (a read-only receipt, a message) rather than the webform's own. It is off by default, so a form keeps its own confirmation unless chaining is turned on.

  • On the resume handler it returns the visitor to the step's own dispatcher doorway: the bearer dispatcher for an anonymous party, the inbox for a task.
  • On the start handler it returns the visitor to the new run's dispatcher, so a standalone form flows straight into the workflow it just started.

The next step is read from the workflow itself, not carried on the form: by the time the form's confirmation runs, the branch has resumed and the run has advanced, so the resume handler resolves the branch's current chain and the token now parked on it and scopes the return link to that token. A workflow that runs parallel branches through chained forms is handled automatically: each branch lands on its own next step rather than drifting onto a sibling, with no extra form configuration and no dependence on the catch-up setting. Nothing about the return is read from the request query or a hidden field, so it does not depend on anything surviving the POST.

The redirect is issued from the form's confirmation step, which webform runs after its own setConfirmation(), so it only takes effect when the form's confirmation type performs a redirect. The inline and modal types re-render the form in place (a rebuild) and cancel the redirect, so return-to-workflow silently does nothing with them: use a redirecting type such as Page (the default). The resume handler's settings form disables the option, and explains why, when the form's confirmation type is inline or modal.