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/rejectedoutcomes; the comment can be made required, and acomment_variablestores 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).
Two doorways: link or task¶
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()andcheckCompleteAccess()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 askscheckViewAccess(); one that completes a step askscheckCompleteAccess().
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.
Link scope: instance or branch¶
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
webforminteraction 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:
- Build a Webform for the input to collect.
- 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.
- 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.
- 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.