Workflow Manifest Reference
A workflow is defined by a declarative manifest (JSON). It lists the steps to run and maps named outcomes to actions. This page describes its shape.
Top-level structure
| Field | Description |
|---|---|
version | Manifest version number. Required. |
workflowName | Unique name within the tenant. Required. |
meta | Display settings: label, the human-readable name. |
steps[] | Ordered list of steps to execute. Must contain at least one decision step. |
outcomes | Map of named outcome → list of actions to run. Outcome names are snake_case. |
No other keys are allowed. In particular, a manifest must not contain a $schema key: the platform validates manifests itself, and a manifest with $schema is refused with an error naming the key.
Step object
Every step — capture or evaluation — has a uniform shape:
| Field | Description |
|---|---|
order | Numeric position; drives execution sequence. Each value may be used once. |
name | Human-readable label. |
action | The building block to run (e.g. a capture component or an evaluation step). |
params | Action-specific settings (allowed documents, Trust Factor configuration, decision rules, event payload). |
id | Optional key of the step (letters, digits and underscores). Without it, the step is keyed by its action. Give two steps of the same action different ids when an emit_event payload must reference one of them. |
A decision step carries a rule table in its params — see Decision rules.
Illustrative example
A trimmed onboarding manifest — capture a document and a selfie, evaluate trust and uniqueness, decide, then notify the Company's backend:
{
"version": 1,
"workflowName": "onboarding_standard",
"meta": { "label": "Standard Onboarding" },
"steps": [
{ "order": 1, "name": "Document", "action": "document_capture",
"params": {
"allowedDocuments": [{ "type": "identity-card", "countries": ["SVK", "CZE"] }],
"captureBackPage": true
} },
{ "order": 2, "name": "Face capture", "action": "face_capture" },
{ "order": 3, "name": "Trust factor eval", "action": "tf_eval",
"params": { "trustFactorConfig": "onboarding_strict" } },
{ "order": 4, "name": "Face duplicity check", "action": "dedup_face",
"params": { "trustFactorConfig": "onboarding_strict" } },
{ "order": 5, "name": "Document duplicity check", "action": "dedup_document" },
{ "order": 6, "name": "Combine duplicates", "action": "best_duplicate" },
{ "order": 7, "name": "Decide", "action": "decision",
"params": {
"rules": [
{ "when": "tf_eval.result == 'reject' || document_capture.result == 'ocr_quality_failed'",
"outcome": "failed", "terminate": "reject", "reason": "trust_reject" },
{ "when": "tf_eval.result == 'review'", "outcome": "needs_review", "terminate": "review" },
{ "when": "best_duplicate.type == 'blocklist'", "outcome": "blocked", "terminate": "reject" },
{ "when": "best_duplicate.outcome == 'full_hit' && best_duplicate.type == 'customer'",
"outcome": "returning", "terminate": "accept" },
{ "when": "best_duplicate.outcome == 'no_match'", "outcome": "new_unique", "terminate": "accept" }
],
"default": { "outcome": "needs_review", "terminate": "review" }
} },
{ "order": 8, "name": "Notify backend", "action": "emit_event",
"params": {
"event": "onboarding.decided",
"payload": { "outcome": "${decision.outcome}", "trust": "${tf_eval.result}", "channel": "web" }
} }
],
"outcomes": {
"new_unique": { "actions": [
{ "action": "create_new_customer",
"params": { "external_id_template": "${document.personal_number}_${uuid}" } },
{ "action": "repoint_primary_DI" } ] },
"returning": { "actions": [ { "action": "add_new_DI_to_customer" } ] },
"blocked": { "actions": [ { "action": "1N_add_current_to_blocklist" } ] },
"needs_review": { "actions": [ { "action": "1N_move_to_review" } ] },
"failed": { "actions": [] }
}
}
Building blocks
Capture steps
Run on the device of the User, through the hosted app, the web components or the mobile SDK:
| Action | Captures |
|---|---|
mobile_redirect | Hands a desktop session over to a phone (skippable). Must be the first step when present. |
document_capture | Identity document pages; allowedDocuments and captureBackPage in params. |
nfc_capture | The chip of an electronic document. Requires a document_capture step before it. |
face_capture | Selfie with passive liveness. |
smile_capture, multirange_capture | Selfie with active liveness (smile, multi-range). |
both_palms_capture | Left and right palm for enrolment — palm verification, tenants with palm enabled only. |
single_palm_capture | One palm for authentication — palm tenants only. |
external_capture | Data supplied by the integrator instead of a capture component. |
Evaluation steps
Run server-side:
| Action | Evaluates | Result signal |
|---|---|---|
tf_eval | The Trust Factor configuration named in params.trustFactorConfig. | tf_eval.result |
dedup_face | Face duplicity check (1:N); takes params.trustFactorConfig. | dedup_face.result |
dedup_document | Document duplicity check (1:N). | dedup_document.result |
best_duplicate | Combines the two duplicity checks into one outcome. Takes no parameters. | best_duplicate.outcome, .type, .hit |
matching | 1:1 face comparison against a known Customer; takes params.trustFactorConfig. | matching.result |
palm_matching | 1:1 palm comparison against a known Customer — palm tenants only. | palm_matching.result |
decision | The rule table; ends the workflow or lets it continue. | decision.outcome (for emit_event) |
emit_event | Sends a workflow event webhook built from the results of earlier steps. | — |
Decision signals
Values a when condition can test:
| Signal | Values |
|---|---|
tf_eval.result | accept, review, reject |
dedup_face.result, dedup_document.result | no_match, customer, blocklist, concurrent, review |
best_duplicate.outcome | no_match, full_hit, similar_face, similar_name, face_missmatch, doc_missmatch, multiple_full_hits |
best_duplicate.type | customer, blocklist, concurrent, review |
matching.result, palm_matching.result | match, no_match |
document_capture.result | ok, or ocr_quality_failed when the OCR quality retake budget was exhausted |
Decision rules
A decision step has params.rules (at least one rule) and an optional params.default.
| Field | Description |
|---|---|
when | The condition — see Condition syntax. |
outcome | Name of an outcome defined in outcomes. |
terminate | The terminal state the workflow ends in: accept, review, reject or incomplete. Required on every rule. |
reason | Optional reason code, returned with the result. |
Rules are evaluated in order and the first matching rule wins: the workflow ends in that rule's terminal state and runs the actions of its outcome. This applies wherever the decision sits — a decision in the middle of the workflow that matches ends the workflow there, and the steps after it do not run.
The default (outcome + terminate) applies when no rule matches. Set its terminate to an empty string "" to continue to the next step instead of ending. Without a default, a decision with no matching rule also continues to the next step.
Condition syntax
Conditions use a subset of CEL:
- Comparisons —
<step>.result == '<value>'for the signals above,best_duplicate.outcome == '<value>',best_duplicate.type == '<value>', anddedup_document.hit == dedup_face.hit(or!=). Values must be from the signal table;!=is accepted only in thehitcomparison. &&(and),||(or) and parentheses combine comparisons. As in CEL,&&binds tighter than||:a && b || cmeans(a && b) || c. Use parentheses to group differently, e.g.(a || b) && c.- Other operators and functions (
!,in,<, …) are not supported. The validation error names the rule index and the offending token. - A condition can test only steps that run after the previous decision step (or from the start of the workflow, for the first decision), and each step it tests must appear only once in that span — otherwise the reference is ambiguous and the manifest is refused.
{ "when": "(dedup_face.result == 'concurrent' || dedup_face.result == 'review') && tf_eval.result == 'accept'",
"outcome": "needs_review", "terminate": "review" }
The visual decision builder in the dashboard edits conditions joined with && only. A rule that uses || or parentheses is shown as an unsupported format there and is edited on the JSON tab.
Emit event step
An emit_event step sends one signed workflow event webhook each time the run reaches it — mid-flow or after the final decision.
{ "order": 8, "name": "Notify backend", "action": "emit_event",
"params": {
"event": "onboarding.decided",
"payload": { "outcome": "${decision.outcome}", "trust": "${tf_eval.result}", "channel": "web" }
} }
| Parameter | Description |
|---|---|
event | The event name the Company receives. Required, non-empty. |
payload | Optional object. Each value is either a literal (text, number, boolean, null, object or array), sent unchanged, or a reference ${<source>.<field>} that is replaced with the result of an earlier step. |
References — the <source> is the step's id, or its action when it has no id:
| Source action | Fields |
|---|---|
tf_eval | result |
dedup_face, dedup_document | result, hit |
best_duplicate | outcome, type, hit |
matching | result, score |
palm_matching | result |
document_capture | result |
decision | outcome (the matched outcome name), status (the terminal state of the matched rule, or null when the decision continued). Always the latest earlier decision. |
Rules:
- A reference must be the whole value of a top-level payload key. A reference embedded in text or nested inside an object is refused.
- The referenced step must come before the
emit_eventstep, and the source must be unambiguous: when two earlier steps share the same key, give them differentids. - Only the results above can be referenced. Personal data, images, document data, IP addresses, device identifiers and session tokens cannot be placed in an event.
- A reference to a step that did not run in this session is sent as
null. - An
emit_eventstep cannot come before the first capture step.
Behaviour:
- The event is sent when the step runs, not at the end of the session, on every data tier. It is sent only when the Company has at least one webhook endpoint configured; otherwise the step does nothing and the run continues.
- The step has no condition — it fires whenever the run reaches it. For per-outcome behaviour, place it after the
decisionstep and put${decision.outcome}into the payload. Anemit_eventstep placed after a decision that ends the workflow still runs. - Delivery runs in the background: it never delays the session result, and retries follow the webhook delivery guarantees. Each event has its own
eventId, kept across retries, and identifies the step that sent it. - An event is not transactional with the outcome actions: it can arrive before the outcome's records (for example a new Customer) exist. Use the workflow result webhook or the Customer API for the final state.
Outcome actions
Run when the workflow ends in the named outcome:
| Action | Effect |
|---|---|
create_new_customer | Creates a Customer from the identity and adds the person to the Customer watchlist. Accepts params.external_id_template. |
create_blocked_customer | Creates the Customer directly in blocked state. Accepts params.external_id_template. |
update_identified_customer | Updates the data of the Customer the session was started for (personal data update, authentication). |
add_new_DI_to_customer | Attaches the identity to the matched Customer (merge). |
repoint_primary_DI | Makes this identity the primary identity of the Customer. |
1N_add_current_to_watchlist, 1N_add_current_to_blocklist, 1N_move_to_review | Places the person in the Customer, Blocklist or Review watchlist. |
reject_concurrents, merge_rejected_concurrents | Resolves other in-flight verifications of the same person that were held for review — rejects them, or rejects and merges them into the new Customer. |
Deprecated: emit_event outcome action
The emit_event outcome action, with its outcome-level events[] list, is replaced by the emit_event step. It can no longer be added when authoring a workflow. Manifests that still use it keep working, and the dashboard marks the affected outcomes as deprecated; a future release will refuse them. To migrate, remove the action and the events[] list from the outcome and add an emit_event step after the last decision step with the same event name and payload, plus "outcome": "${decision.outcome}" if your backend needs to know which outcome fired.
External identifier template
create_new_customer and create_blocked_customer accept an optional external_id_template that composes the external_id of the new Customer — the identifier your systems use to refer to the person (see Customer API → External identifier). Without a template the external_id equals the Customer identifier, as before.
{ "action": "create_new_customer",
"params": { "external_id_template": "${document.personal_number}_${uuid}" } }
| Token | Value |
|---|---|
${document.personal_number} | The personal number read from the document, exactly as printed (including separators). |
${document.document_number} | The document number. |
${uuid} | A freshly generated UUID. |
Rules:
- Only the tokens above are allowed. Fields that are not unique per person (names, date of birth, nationality) are deliberately unavailable — a template built from them could give two different people the same identifier.
- The rendered value must match
^[a-zA-Z0-9._-]{1,64}$and be unique within the tenant; otherwise the action fails. - If a token cannot be resolved at run time (for example the document carries no personal number), the workflow ends incomplete with the reason
external_id_unresolved. There is no silent fallback to a UUID, because a record your systems cannot match is worse than a failed run. - Templates are validated when the manifest is saved.
A personal number embedded in an identifier is personal data: it is wiped on anonymization together with the rest of the record.
Validation
On save, a manifest is checked for:
- Structure — required fields present, no unknown keys (including
$schema), uniqueordervalues, at least onedecisionstep. - Steps — every
actionis from the catalog and itsparamsare valid; every referenced Trust Factor configuration exists;mobile_redirectis first andnfc_capturefollows adocument_capture; palm steps are used only by tenants with palm verification enabled. - Decisions — every condition follows the condition syntax, tests only known signals of steps in its scope, and every outcome a rule or default names is defined in
outcomes. - Events — every
emit_eventstep has an event name and only valid, unambiguous references to earlier steps.
All problems are reported at once, each with the path of the offending field. The dashboard runs these checks continuously while you edit.