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.
How a workflow runs
- Trigger — a platform event, a signed inbound webhook, a cron schedule, or a manual run starts the flow.
- Resolve — the engine loads the published version of the flow (drafts never fire side effects).
- Execute — it walks the graph node-by-node: conditions branch, lookups enrich, templates render, actions send.
- 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.
| Trigger | What fires it | Notes |
|---|---|---|
| Platform events | Native events from your Lunixi services — payments, invoices, subscriptions, usage, KYC, fraud, organization | Matched to your flow by event key (e.g. payment.captured) |
| Signed webhooks | Inbound HTTP from your storefront or apps, HMAC-signed | WordPress / WooCommerce events map to canonical commerce.* |
| Schedule | Standard 5/6-field cron, per-schedule timezone | e.g. nightly reconciliation, weekly digests |
| Manual run | A run you trigger from the dashboard or API | Optionally 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 event | Canonical event key |
|---|---|
| Order created | commerce.order.created |
| Order paid | commerce.order.paid |
| Order cancelled | commerce.order.cancelled |
| Customer created | commerce.customer.created |
| Form submitted | commerce.form.submitted |
| Product low stock | commerce.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
| Node | What it does |
|---|---|
start / stop | Flow entry point and terminal stop |
if | Single-condition boolean branch (true / default) |
switch | Multi-case branch — pick an edge by value |
rule | Nested all / any / none condition tree → success / failure |
parallel / merge | Fan a run across branches and re-converge |
subflow | Run another flow as a child (depth-guarded) |
Data
| Node | What it does |
|---|---|
set_var | Resolve expressions and write run variables |
render_template | Render a Mustache template into a variable |
validate_schema | Field-rule validation (required / type / min / max / pattern / enum) |
required_input | Assert required inputs exist before any side effect |
lookup_customer | Fetch a redacted customer record by id / external id |
lookup_payment | Fetch a redacted payment by id |
lookup_subscription | Fetch a subscription by id |
Actions (side-effecting)
| Node | What it does |
|---|---|
send_email | Send email through your email provider |
send_sms | Send SMS through your SMS provider |
send_invoice | Deliver an invoice over email or SMS |
create_invoice | Create an invoice via the billing seam |
http_request | Inline outbound HTTP call (SSRF-pinned) |
webhook | Enqueue 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 resultA 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.
| Channel | Providers |
|---|---|
| SendGrid, Mailgun, or a generic HTTP provider | |
| SMS | Twilio, 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
| Capability | Behaviour |
|---|---|
| Branching | Typed edges — success, failure, branch, default. A node routes the run down the matching edge. |
| Parallel branches | A parallel node fans the run across branches that re-converge at a merge. Branches share context and accumulate deterministically. |
| Error handling | Executor errors are normalised to a stable code and routed to a failure edge; continueOnError is configurable per node. |
| Idempotency | Triggers 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 / versioning | Each 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 queue | Flows whose manual-run policy is approval enqueue a pending request; a reviewer with the right scope approves it, which then runs the flow. |
| Run history | Every 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}/eventsEach request is authenticated by signature — no bearer token:
| Header | Value |
|---|---|
X-Automation-Signature | sha256=<hex> — HMAC-SHA256 of {timestamp}.{rawBody} |
X-Automation-Timestamp | Unix seconds; must be within ±300s |
X-Automation-Nonce | Optional; 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
| Control | How it works |
|---|---|
| Tenant isolation | Every 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 ownership | A (provider, externalId) pair is owned by exactly one tenant; a cross-tenant takeover attempt is rejected and ownership is never reassigned. |
| Signed ingress | HMAC-SHA256 over {timestamp}.{body}, a ±300s freshness window, and optional nonce replay protection. |
| SSRF-safe egress | Outbound 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 hygiene | Outbound responses are size-capped and have sensitive headers (authorization, set-cookie, …) stripped before they can land in flow variables. |
| Secret handling | Credentials 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 limiting | Per-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
- Automation Workflows — worked, end-to-end flow examples
- Payment Gateway — the payment events that trigger flows
- Fraud Detection — gate sends and actions on risk signals