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 the visitor has nothing to do on, it shows that step's chain of messages instead, and a neutral message when the chain carries none.

A step is not the visitor's to act on in two cases: it declares no visitor interaction at all, or it is assigned to an operator. An assigned step is completed through the task route under its own authorization check, so the dispatcher never hands the visitor its surface even when it declares an interactive interaction, and the bearer doorway refuses to resume one.

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.
  • Comment: collect a comment and an outcome on a small page (a comment field and one button per the node's outcomes), recording the note and resuming the step. A review is only this with approved / rejected outcomes; the comment can be made required, and a comment_variable stores the note in a named process variable a later step can use (the outcome stays the scalar the routing reads). It is the interaction counterpart of the inbox comment task, reusable on any interaction node.
  • 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 step assigned to an operator (a node declaring assignments) is completed only through the identity doorway (the task route, under the inbox's checkActionAccess() check), never a bearer link. The bearer doorway may display such a step's status (an "awaiting confirmation" message) but refuses to complete it, so a party holding a capability link cannot approve an operator's task. This holds regardless of link scope, so an instance-scoped link chaining onto a later operator step is safe: it shows the status and stops there.
  • Being shown a step and being able to complete it are separate permissions, and the doorways disagree about them, which is why a gateway answers checkViewAccess() and checkCompleteAccess() rather than one question. On the bearer doorway possession authorizes viewing, so an assigned step is displayed (the bullet above); on an identity doorway it does not, because what a step renders is the workflow's own context, which can carry an earlier reviewer's note. A surface that renders a step asks checkViewAccess(); one that completes a step asks checkCompleteAccess().

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 (getStepUrl($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 (getStepUrlForToken($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::findParkedInteractiveTokens($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: start or resume on submit Webform handler: one handler that, keyed on the binding element, either starts a workflow when a fresh submission completes (binding that submission to the new instance and seeding configured variables like the submission id and the submitter's uid) or resumes the bound instance's parked step with a configured outcome (default submitted) when the form is reopened as a step, then advances the run.

Setting it up takes no code:

  1. Build a Webform for the input to collect.
  2. If the workflow will reopen the form as a step, add one Orchestra interaction element to it (its key is free; the handler and interaction find it by type). A form that only starts a workflow needs none.
  3. Add the Orchestra: start or resume on submit handler: set a Start workflow to begin a run from the submission, a Resume outcome for a step, or both for a form that does both.
  4. On the wait node, set External interaction to Webform and pick the form.

See Webform integration for the full configuration reference: every handler setting, the interaction modes, chaining, and the gotchas.

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. Reopening is Webform's own secure-token reopen, so it needs the form's Allow users to update a submission using a secure token setting on; without it the party gets a blank form and a second submission binds alongside the first.

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. A step can also display context: each context_display entry names a process variable (a dotted path reaches into a structured value, e.g. validation.comment), a label and an optional severity, and its current value is posted as a Drupal message wherever the step surfaces, on every page of a wizard included, always read live from the instance and never stored into the submission. These are the pieces the orchestra_interaction_webform_examples 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 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 handler's settings form disables the option, and explains why, when the form's confirmation type is inline or modal.