Skip to content
_FLOWS/

Money movement, orchestrated

Compose multi-step payment workflows as durable, event-driven programs — reliable across every provider.

Define
name: payout
stages:
  - send
  - wait_event:
      event: ${event}
  - delay:
      seconds: 7d

Declare the whole journey, stages, waits and delays, as code.

Trigger
SAVED_PAYMENT
Filter✓ match
event.type == "PAY-IN"
▶ run instance
vars → amount · asset

Bind a flow to any event and filter it, matches start a run.

Run
send
wait_event
delay
now

A durable instance advances stage by stage, waiting as long as needed.

Send
€41.99EUR/2 · 4199
worlddeposits:42
ledger · flows-demo-001

Each stage moves value between ledgers, wallets and payments.

define · trigger · run · send, orchestrate money movement
_WORKFLOWS/

Workflows as code

Declare the whole journey, end to end.

Describe splits, fees, holds and payouts as a single declarative program. Version it, review it, and ship money movement like any other code.

workflow
name: collect-and-payout
stages:
  - send:
      source:
        payment: { id: "payment:${paymentID}", psp: "modulr" }
      destination:
        account: { id: "deposits:${depositID}:pending", ledger: "ledger-pending" }
      amount: { amount: 1000, asset: "USD" }
  - wait_event:
      event: "payout.confirmed"
  - send:
      source:
        account: { id: "deposits:${depositID}:pending", ledger: "ledger-pending" }
      destination:
        wallet: { id: "main" }
      amount: { amount: 1000, asset: "USD" }
_SEND/

One send, any rail

Move value between ledgers, wallets and payments.

A single send stage moves value between internal ledgers, wallets and external payments. Source and destination interchange freely — the engine resolves the route, including provider-specific paths like Stripe.

Sendcompatibility
LEDGERWALLETPAYMENTSourceLEDGER
Stripe logo
WALLET
Stripe logo
PAYMENT
supportedStripe logostripe only, provider-routednot supported

one send stage, ledgers, wallets and payments interchange

_DURABILITY/

Durable by default

Every run survives time and failure.

A workflow instance is a durable program: each stage is tracked with its status and timing, and long waits or delays hold their place for days without losing state. The run resumes exactly where it left off.

InstanceRunning
dff3791d·my-workflow
Elapsed5d 02hstage 3 / 4 · delay · resumes in 4d
SENDworld → deposits0.4s
WAIT_EVENTdeposit.confirmed2d
DELAY7d3d / 7d
SEND→ psp: stripequeued
t0+2dnow+9d

a durable instance, each stage tracked with status and timing

_ORCHESTRATION/

Durable orchestration

Long-running flows that never lose state.

Each stage runs as a durable step. Flows can wait for external events for minutes or months, then resume exactly where they left off — no lost work.

TRIGGERCOLLECTFEESPAYOUTSETTLE
_RELIABILITY/

Retries & idempotency

Exactly-once, even when rails fail.

Transient failures are retried automatically with idempotency keys, so a flaky provider never double-charges or double-pays. Every attempt is recorded.

run · collect-and-payout
RUNNING
send · collectpayment → ledger-pending
DONE
wait_eventpayout.confirmed
DONE
send · payoutattempt 2/3 · retry
RUNNING
settleidempotent · exactly-once
QUEUED
_EVENTS/

Event-driven by design

Observe every step in real time.

Flows emit a typed event stream you can subscribe to — drive notifications, analytics, and downstream systems from a single source of truth.

flows · event stream
LIVE
EVT
flow.started collect-and-payout
EVT
stage.ok send → deposits:pending
WAIT
stage.wait payout.confirmed
RETRY
stage.retry payout 2/3
EVT
event.received payout.confirmed
EVT
stage.ok send → wallet:main
DONE
flow.terminated idempotent
_INSTANCES/

Every run, inspectable

Follow a workflow instance, stage by stage.

Each run is a live instance you can inspect end to end — what has executed, what is waiting and why. Durable stages hold their place until the event arrives, so nothing is lost between steps.

WorkflowRunning
my-workflow
Instance · stage02 / 04wait_eventblocked on deposit.confirmed
sendwait_eventdelay
01
SEND100 JPY
world → deposits:${depositID}
02
WAIT_EVENTWAITING
deposit.confirmed
03
DELAY
7d
04
SEND100 JPY
deposits:${depositID} → psp: stripe

send · wait_event · delay, across ledgers, wallets and payments

_TRIGGERS/

Triggered by events

Start a workflow the moment it matters.

Bind a flow to any platform event and filter it with expr-lang. When an incoming payment matches, its data is forwarded straight into the run as typed variables — no glue code, no polling.

TriggeronSAVED_PAYMENT
EVENT · payment
TYPEPAY-IN
PROVIDERSTRIPE
ASSETEUR/2
AMOUNT4,199
STATUSSUCCEEDED
FILTER · expr-lang
event.type == "PAY-IN"&& provider == "stripe"
Match
VARIABLES runs my-payout
amount4,199
assetEUR/2
userID003
merchantID001

a payment event fires a workflow, matched variables forwarded

FAQ

Questions, answered

01 / DURABILITY

Each stage runs as a durable step. A flow can wait for an external event for minutes or months, then resume exactly where it left off.

02 / IDEMPOTENCY

Retries use idempotency keys, so a flaky provider never double-charges or double-pays. Every attempt is recorded.

03 / EVENT-DRIVEN

Flows wait on typed events and emit their own, so notifications, analytics and downstream systems run off one source of truth.

04 / AUTHORING

Workflows are declared as code — versioned, reviewed and shipped like any other program, not built in an opaque visual editor.

05 / OBSERVABILITY

Every stage transition is logged with its inputs and outputs, so a stuck or retrying flow stays inspectable in real time.

_GET STARTED/

Orchestrate money movement that survives failure

Durable, idempotent, event-driven workflows — reliable across every provider.