Automation
Overview

Automation

A workflow engine built for commerce. An event comes in — an order is paid, a payment fails, a customer signs up — and Lunixi runs the workflow you designed: branch on conditions, look up the payment or customer, render a template, and send an email or SMS through your own provider.

It is n8n-grade, but payment- and commerce-native: triggers map to canonical commerce.* events, lookups read directly from your payments and subscriptions, and every run is idempotent, tenant-isolated and fully observable.

Trigger, flow and actionArchitecture: an event triggers the flow, the flow runs its conditions and steps, and the result produces an action through your own provider.Triggerorder.paidFlowcondition → lookup → templateActionyour SendGridLEGENDEVENTWHAT YOU BUILDYOUR PROVIDER
The action leaves through your own provider — we do not send the email, it goes out on your account. Delivery reputation and template ownership stay with you.

How a workflow runs

  1. Trigger — a platform event, a signed inbound webhook, a cron schedule, or a manual run starts the flow.
  2. Resolve — the engine loads the published version of the flow (drafts never fire side effects).
  3. Execute — it walks the graph node-by-node: conditions branch, lookups enrich, templates render, actions send.
  4. Persist — every run, every node attempt, and every outbound delivery is recorded as a redacted, queryable event.

Flows are versioned. A draft is what you edit; a publication is an immutable, compiled snapshot. Only published flows run against live traffic — and you can simulate a draft end-to-end before you ever publish it.

Triggers

A workflow starts from one of four sources.

TriggerWhat fires itNotes
Platform eventsNative events from your Lunixi services — payments, invoices, subscriptions, usage, KYC, fraud, organizationMatched to your flow by event key (e.g. payment.captured)
Signed webhooksInbound HTTP from your storefront or apps, HMAC-signedWordPress / WooCommerce events map to canonical commerce.*
ScheduleStandard 5/6-field cron, per-schedule timezonee.g. nightly reconciliation, weekly digests
Manual runA run you trigger from the dashboard or APIOptionally gated by an approval queue

Canonical commerce events

Inbound storefront events are normalised to a canonical taxonomy, so a flow written once works regardless of the source platform:

Source eventCanonical event key
Order createdcommerce.order.created
Order paidcommerce.order.paid
Order cancelledcommerce.order.cancelled
Customer createdcommerce.customer.created
Form submittedcommerce.form.submitted
Product low stockcommerce.product.low_stock

WordPress / WooCommerce ingress is live today. Additional storefront connectors are on the roadmap — the ingress contract (HMAC + canonical mapping) is identical across providers.

Node catalog

A flow is a graph of typed nodes. Every node reads its inputs from upstream output using {{ }} expressions and writes its result to a saveAs variable that later nodes can reference.

Logic & control

NodeWhat it does
start / stopFlow entry point and terminal stop
ifSingle-condition boolean branch (true / default)
switchMulti-case branch — pick an edge by value
ruleNested all / any / none condition tree → success / failure
parallel / mergeFan a run across branches and re-converge
subflowRun another flow as a child (depth-guarded)

Data

NodeWhat it does
set_varResolve expressions and write run variables
render_templateRender a Mustache template into a variable
validate_schemaField-rule validation (required / type / min / max / pattern / enum)
required_inputAssert required inputs exist before any side effect
lookup_customerFetch a redacted customer record by id / external id
lookup_paymentFetch a redacted payment by id
lookup_subscriptionFetch a subscription by id

Actions (side-effecting)

NodeWhat it does
send_emailSend email through your email provider
send_smsSend SMS through your SMS provider
send_invoiceDeliver an invoice over email or SMS
create_invoiceCreate an invoice via the billing seam
http_requestInline outbound HTTP call (SSRF-pinned)
webhookEnqueue a durable outbound webhook

Every side-effecting node honours dry-run. When you simulate a flow, sends, webhooks and HTTP calls are skipped and replaced with synthetic results — so downstream nodes still resolve, but nothing leaves your account.

Referencing data between nodes

Any node config field can interpolate values resolved at runtime:

{{ input.payload.order.total }}        // the triggering event
{{ vars.customer.email }}              // output a prior node saved
{{ nodes.lookup_pay.output.status }}   // a specific node's result

A bare {{ vars.customer }} returns the raw object; embedded {{ }} inside a string interpolates.

Bring your own providers

send_email, send_sms and send_invoice deliver through your provider account with your API key — Lunixi never sits in the middle as a sending service. You connect a provider once, store the credential in the vault, and reference it from any node.

ChannelProviders
EmailSendGrid, Mailgun, or a generic HTTP provider
SMSTwilio, Netgsm, or a generic HTTP provider

The generic HTTP provider is an escape hatch: define the full request (URL, method, headers, body) for any provider not in the list.

⚠️

API keys are never stored in a node's configuration. A node holds only a secretRef pointing at a vault-managed secret, resolved per-merchant at runtime. Inlining a raw key into a flow is a publish-blocking error.

{
  "type": "send_email",
  "config": {
    "provider": "sendgrid",
    "apiKeySecretRef": "vault:sg_prod",
    "to": "{{ vars.customer.email }}",
    "templateId": "welcome_v2",
    "variables": { "name": "{{ vars.customer.firstName }}" },
    "saveAs": "emailResult"
  }
}

Flow engine

CapabilityBehaviour
BranchingTyped edges — success, failure, branch, default. A node routes the run down the matching edge.
Parallel branchesA parallel node fans the run across branches that re-converge at a merge. Branches share context and accumulate deterministically.
Error handlingExecutor errors are normalised to a stable code and routed to a failure edge; continueOnError is configurable per node.
IdempotencyTriggers and runs are claimed before any side effect. A replayed event or a reused idempotency key returns the original result instead of re-firing.
Draft / publish / versioningEach publish creates an immutable, compiled version. A checksum gate forces re-validate → re-simulate before a changed draft can be republished.
Simulation (dry-run)Run a draft end-to-end with zero side effects and inspect the redacted, node-by-node result.
Approval queueFlows whose manual-run policy is approval enqueue a pending request; a reviewer with the right scope approves it, which then runs the flow.
Run historyEvery run and node attempt is persisted with status, latency, the path taken, and redacted inputs/outputs.

Parallel branches run as logical fan-out within a single run — they share state and execute deterministically, not as independent OS threads. For high-volume outbound delivery, a durable retry-with-backoff worker is available in beta.

Quickstart

Build, simulate and publish a flow with the management API. All management calls use a bearer token (at_live_…); merchantId is resolved from the key, never sent in the body.

# 1. Create a draft
curl https://api-gateway.lunixi.com/api/v1/automation/flows/draft \
  -H "Authorization: Bearer at_live_5f3e2b1a..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Welcome email on signup",
    "nodes": [ /* start → lookup_customer → send_email → stop */ ]
  }'
 
# 2. Validate, then dry-run simulate (no side effects)
curl https://api-gateway.lunixi.com/api/v1/automation/flows/{id}/simulate \
  -H "Authorization: Bearer at_live_5f3e2b1a..." \
  -d '{ "input": { "payload": { "customerId": "cus_123" } } }'
 
# 3. Publish (immutable version)
curl https://api-gateway.lunixi.com/api/v1/automation/flows/{id}/publish \
  -H "Authorization: Bearer at_live_5f3e2b1a..." -d '{}'
 
# 4. Manual run
curl https://api-gateway.lunixi.com/api/v1/automation/flows/{id}/run \
  -H "Authorization: Bearer at_live_5f3e2b1a..." \
  -d '{ "input": { "payload": { "customerId": "cus_123" } } }'

Prefer not to write JSON by hand? The dashboard ships a visual flow builder — drag nodes onto a canvas, wire edges, configure each node in a typed inspector, then simulate and publish from the same screen.

Inbound webhooks

To drive flows from your storefront, register an integration to get a signed ingress URL and an HMAC secret (shown once). Events are posted to:

POST https://api-gateway.lunixi.com/api/v1/automation/integrations/{provider}/{integrationId}/events

Each request is authenticated by signature — no bearer token:

HeaderValue
X-Automation-Signaturesha256=<hex> — HMAC-SHA256 of {timestamp}.{rawBody}
X-Automation-TimestampUnix seconds; must be within ±300s
X-Automation-NonceOptional; replayed nonces are rejected
SIG=$(printf '%s.%s' "$TS" "$BODY" \
  | openssl dgst -sha256 -hmac "$SIGNING_SECRET" -hex | sed 's/^.* //')
 
curl https://api-gateway.lunixi.com/api/v1/automation/integrations/wordpress/intg_123/events \
  -H "X-Automation-Signature: sha256=$SIG" \
  -H "X-Automation-Timestamp: $TS" \
  -d "$BODY"

The response status reflects the outcome so your producer can retry correctly: 202 accepted, 200 duplicate, 401 bad signature, 404 no matching flow, 409 replayed nonce, 413 body too large, 429 rate-limited.

Security & multi-tenancy

ControlHow it works
Tenant isolationEvery flow, integration, secret and run is scoped to your merchant. The authoritative merchant is resolved from your token or your integration registration — never from the request body.
Integration ownershipA (provider, externalId) pair is owned by exactly one tenant; a cross-tenant takeover attempt is rejected and ownership is never reassigned.
Signed ingressHMAC-SHA256 over {timestamp}.{body}, a ±300s freshness window, and optional nonce replay protection.
SSRF-safe egressOutbound calls are blocked from reaching private, loopback, link-local and metadata addresses. DNS is pinned at connect to defeat rebinding, and every redirect hop is re-checked.
Response hygieneOutbound responses are size-capped and have sensitive headers (authorization, set-cookie, …) stripped before they can land in flow variables.
Secret handlingCredentials live in a vault and are resolved with a short-lived, scoped token at runtime. Secret values are never logged or persisted in flow definitions.
Rate limitingPer-merchant sliding-window limits apply to both inbound triggers and outbound destinations.
⚠️

Manual runs can be governed per-merchant: set a default policy of open, operator, approval (route through the approval queue) or admin (require an elevated scope), and override it per flow.

Observability

Every surface is queryable, redacted and merchant-scoped:

  • Overview — run volume, success rate, latency and recent failures over a rolling window.
  • Run history — per-flow runs, drillable to a node-by-node trace.
  • Trigger events — the inbound ingest log.
  • Connector deliveries — the outbound HTTP egress log.
  • Audit events — an append-only trail of flow changes.
{
  "runId": "run_01HV3K9X...",
  "flowId": "flow_8f2a9c",
  "status": "success",
  "latencyMs": 412,
  "path": ["start", "if", "lookup_customer", "send_email", "stop"],
  "nodes": [
    { "id": "lookup_customer", "status": "success" },
    { "id": "send_email", "status": "success", "output": { "delivered": true } }
  ]
}

Related