Building a workflow (JSON schema)
A TechWatchAlert workflow is an automation graph: a trigger reacts to an event (new CVE, approaching end-of-life, expiring certificate…), conditions filter, and actions notify or act. The graph is described by a JSON object you manipulate through the visual editor, the Vigie assistant, or workflow import/export.
Overall structure
Section titled “Overall structure”The graph (workflow.graph) has exactly two keys:
{ "nodes": [ /* the tiles */ ], "edges": [ /* connections between tiles */ ]}A tile (node)
Section titled “A tile (node)”{ "id": "c1", "type": "condition.cvss", "params": { "op": "gte", "value": 9 }, "position": { "x": 320, "y": 140 }}| Field | Type | Description |
|---|---|---|
id | string | Unique identifier within the graph (free-form: trigger, c1, a1…). |
type | string | Tile type (see the catalog). |
params | object | Tile parameters (depend on its type). {} if none. |
position | object (optional) | { x, y } coordinates on the canvas. Omit it — the editor lays out tiles automatically. |
A connection (edge)
Section titled “A connection (edge)”{ "id": "e1", "source": "trigger", "target": "c1", "sourceHandle": "out" }| Field | Type | Description |
|---|---|---|
source | string | id of the starting tile. |
target | string | id of the destination tile. |
sourceHandle | string | Output used on the source tile: out (triggers and actions) or true / false (conditions). Default: out. |
id | string (optional) | Connection identifier. |
Validation rules
Section titled “Validation rules”A graph is rejected (with an explicit message) if it breaks any of these rules:
- Exactly one trigger per workflow (
trigger.*). - A trigger cannot have an input (no connection points to it).
- Conditions have two outputs:
trueandfalse. A connection leaving a condition must usesourceHandletrueorfalse. - Triggers and actions have a single output:
out. - The graph is acyclic (DAG) — no loops.
- 30 tiles maximum (
MAX_NODES), depth 20 maximum from the trigger. - Tile
ids are unique; every connection references existing tiles. - Tiles not connected to the trigger are ignored at runtime (tolerated on save so a draft isn’t blocked).
- 10 workflows maximum per project.
Tile catalog
Section titled “Tile catalog”The full registry is also served by the internal API (GET /projects/{id}/workflows/node-types) to build the editor palette.
Triggers (trigger.*)
Section titled “Triggers (trigger.*)”A trigger reacts to an event. Its context (CVE, EOL or certificate) determines which downstream conditions make sense. None take parameters.
| Type | Event | Context | Fires when… |
|---|---|---|---|
trigger.cve_new_match | cve.new_match | CVE | a new CVE matches your monitored stack. |
trigger.cve_updated | cve.updated | CVE | a tracked CVE is updated (score, etc.). |
trigger.cve_poc_published | cve.poc_published | CVE | a public PoC appears for a tracked CVE. |
trigger.cve_kev_added | cve.kev_added | CVE | a tracked CVE enters CISA’s KEV catalog. |
trigger.eol_approaching | eol.approaching | EOL | a tracked product’s end-of-life approaches. |
trigger.eol_support_ending | eol.support_ending | EOL | the end of active support approaches. |
trigger.eol_new_release | eol.new_release | EOL | a new release of a tracked product ships. |
trigger.url_cert_expiring | url.cert_expiring | Certificate | a monitored URL’s HTTPS certificate is about to expire. |
Conditions (condition.*)
Section titled “Conditions (condition.*)”Each condition has two outputs (true / false). A condition from a different family than the trigger (e.g. a CVE condition after an EOL trigger) has no useful effect.
| Type | Context | Parameters |
|---|---|---|
condition.severity | CVE | op ∈ gte | eq | in; value (one severity) for gte/eq, values (list) for in. |
condition.cvss | CVE | op ∈ gt | gte | lt | lte; value number 0 → 10. |
condition.epss | CVE | op ∈ gt | gte | lt | lte; value number 0 → 1. |
condition.has_poc | CVE | (none) — true if a public PoC exists. |
condition.is_kev | CVE | (none) — true if the CVE is in the KEV catalog. |
condition.product_name | CVE | op ∈ contains | starts_with | ends_with | equals; value text (≤ 100). |
condition.vendor_name | CVE | same as product_name, on the vendor. |
condition.match_type | CVE | value ∈ vendor | product | keyword (how the CVE matched your stack). |
condition.case_status | CVE | op ∈ eq | ne; value ∈ a case status or NONE. |
condition.has_tag | CVE | tag_id (UUID of an organization tag). |
condition.eol_days_remaining | EOL | op (numeric); value days −3650 → 3650. |
condition.eol_is_past | EOL | (none) — true if the end-of-life date has passed. |
condition.eol_product_name | EOL | op (text); value text (≤ 100). |
condition.cert_days_remaining | Certificate | op (numeric); value days −3650 → 3650. |
condition.cert_is_expired | Certificate | (none) — true if the certificate has expired. |
condition.url_contains | Certificate | op (text); value text (≤ 100). |
Severities: CRITICAL, HIGH, MEDIUM, LOW, NONE, UNKNOWN.
Case statuses: PENDING, ANALYZING, FIXING, ACCEPTED, RESOLVED, NOT_APPLICABLE.
Actions (action.*)
Section titled “Actions (action.*)”Actions run when the flow reaches them. They have an out output and can be chained.
| Type | Parameters | Effect |
|---|---|---|
action.notify_inapp | (none) | In-app notification. |
action.send_email | recipients (optional): list of member IDs; empty = all relevant members. | Email. |
action.fire_webhooks | webhook_id (optional): a specific webhook, empty = all active webhooks; format ∈ json | slack | discord. | Outbound webhook (HMAC). |
action.create_case | status ∈ a case status (required). | Creates an alert case. Required for an alert to appear under “Alerts” and for the email to include the “View alert” button. |
action.ai_enrich | instruction (optional, ≤ 500): guidance for the analysis (e.g. “focus on Debian remediation”). | Generates an AI analysis (summary, risk, remediation) from only the public technical data; the result is made available to the following actions (email, webhook, notification). No personal data is sent to the model. |
action.add_tag | tag_id (UUID, required). | Adds a tag to the CVE in the project (auto-classification). |
Building a workflow, from A to Z
Section titled “Building a workflow, from A to Z”1. The minimum: a trigger alone
Section titled “1. The minimum: a trigger alone”Valid (a workflow must have exactly one trigger), but does nothing.
{ "nodes": [ { "id": "trigger", "type": "trigger.cve_new_match", "params": {} } ], "edges": []}2. Critical CVE → Slack
Section titled “2. Critical CVE → Slack”“When a new CVE hits my stack with CVSS ≥ 9, notify Slack.”
{ "nodes": [ { "id": "trigger", "type": "trigger.cve_new_match", "params": {} }, { "id": "c1", "type": "condition.cvss", "params": { "op": "gte", "value": 9 } }, { "id": "a1", "type": "action.fire_webhooks", "params": { "format": "slack" } } ], "edges": [ { "id": "e1", "source": "trigger", "target": "c1", "sourceHandle": "out" }, { "id": "e2", "source": "c1", "target": "a1", "sourceHandle": "true" } ]}The c1 → a1 connection uses sourceHandle: "true": the action only fires when the condition is true.
3. true / false branch
Section titled “3. true / false branch”“KEV → alert case + email; otherwise → simple in-app notification.”
{ "nodes": [ { "id": "trigger", "type": "trigger.cve_new_match", "params": {} }, { "id": "kev", "type": "condition.is_kev", "params": {} }, { "id": "case", "type": "action.create_case", "params": { "status": "PENDING" } }, { "id": "mail", "type": "action.send_email", "params": {} }, { "id": "inapp", "type": "action.notify_inapp", "params": {} } ], "edges": [ { "id": "e1", "source": "trigger", "target": "kev", "sourceHandle": "out" }, { "id": "e2", "source": "kev", "target": "case", "sourceHandle": "true" }, { "id": "e3", "source": "case", "target": "mail", "sourceHandle": "out" }, { "id": "e4", "source": "kev", "target": "inapp", "sourceHandle": "false" } ]}Actions chain (case → mail via out). The false branch leads to a lightweight notification.
4. AI analysis then enriched email
Section titled “4. AI analysis then enriched email”{ "nodes": [ { "id": "trigger", "type": "trigger.cve_kev_added", "params": {} }, { "id": "ai", "type": "action.ai_enrich", "params": { "instruction": "Prioritize remediation." } }, { "id": "mail", "type": "action.send_email", "params": {} } ], "edges": [ { "id": "e1", "source": "trigger", "target": "ai", "sourceHandle": "out" }, { "id": "e2", "source": "ai", "target": "mail", "sourceHandle": "out" } ]}ai_enrich is placed before the email: its analysis is included in the message.
5. Approaching end-of-life → email
Section titled “5. Approaching end-of-life → email”{ "nodes": [ { "id": "trigger", "type": "trigger.eol_approaching", "params": {} }, { "id": "c1", "type": "condition.eol_days_remaining", "params": { "op": "lte", "value": 90 } }, { "id": "a1", "type": "action.send_email", "params": {} } ], "edges": [ { "id": "e1", "source": "trigger", "target": "c1", "sourceHandle": "out" }, { "id": "e2", "source": "c1", "target": "a1", "sourceHandle": "true" } ]}Common validation errors
Section titled “Common validation errors”| Message | Cause |
|---|---|
Le workflow doit contenir exactement un déclencheur | Zero or several trigger.* tiles. |
Sortie '…' invalide pour … | Inconsistent sourceHandle (e.g. out on a condition, or true on an action). |
Le workflow contient une boucle | The graph is not acyclic. |
Un déclencheur ne peut pas avoir d'entrée | A connection points to the trigger.*. |
Paramètre requis '…' manquant pour … | A required parameter is missing (e.g. value on condition.cvss). |
Valeur '…' invalide pour … | Value outside the allowed list (severity, status, operator…). |
Valeur hors bornes pour … | Number out of range (CVSS 0–10, EPSS 0–1, days −3650–3650). |
Trop de tuiles (max 30) / Workflow trop profond (max 20) | Graph too large. |
Going further
Section titled “Going further”- Easiest: describe your need to Vigie (the AI assistant) on the Workflows page — it generates the graph, which you approve.
- The visual editor produces and reads this same JSON; exporting a workflow yields a re-importable
{ name, description, graph }file. - See also: Workflows (API).