Your own systems hear about your money the moment it moves
Point an address of yours at a space. Orla calls it with a signed JSON body when money lands or leaves, an invoice is paid, a payout goes out, a payment waits for a signature or is stopped.
Orla sendsX-Orla-Event: money.in
POST /your/address HTTP/1.1 Content-Type: application/json X-Orla-Event: money.in X-Orla-Event-Id: 11b8c41e-47b5-407b-a19d-bb12d90a82b5 X-Orla-Delivery-Attempt: 1 X-Orla-Timestamp: 1789748060 X-Orla-Signature: t=1789748060,v1=5c8456449f0e... {"event":"money.in","version":1, "transaction_id":"5c5cbbda-f6a7-4907-9c10-e167a238b6e2", "account_id":"3a7b76ee-7acd-414e-9066-af408c2b74b7", "direction":"in","amount":"1.00000000","currency":"USD", "kind":"income","origin":"manual","occurred_on":"2026-09-18", "at":"2026-09-18T13:14:20.312110+00:00"}You answerAny 2xx within ten seconds
HTTP/1.1 200 OK
In the app: Connections, the card Event subscriptions. An owner or an admin adds the address and picks the events.
Six events and a ping
One name on the wire,
the same name on the screen
Pick the ones you want, or pick none and hear about all of them, including events added later. A move between your own accounts arrives with origin internal, so an automation that ships goods on payment is not fired by your own sweep. What the autopilot pays reaches you as payout.executed. What it holds for a person reaches you as payment.awaiting_approval.

- money.in13:14 · attempt 1200
- payout.executed13:02 · attempt 1200
- payment.awaiting_approval12:47 · attempt 1200
- invoice.paid12:31 · attempt 2 · next at 12:36503
- ping09:00 · attempt 1200
- money.in
- A row landed in the book that increases an account.transaction_id, account_id, direction (in), amount, currency, kind (income, expense, transfer), origin (rail, manual, import, internal), occurred_on
- money.out
- A row landed in the book that decreases an account.The same as money.in, with direction out
- invoice.paid
- An invoice the space issued is settled in full. Fires once per invoice.invoice_id, number (your document number), amount, currency
- payout.executed
- A payment left on a rail and the rail confirmed it.payment_id, amount, currency, rail (a chain name; bank, for any payout provider; or book for a payment marked paid by hand), transaction_id
- payment.awaiting_approval
- A payment met an approval rule and waits for signatures.payment_id, amount, currency, required_approvals
- payment.blocked
- An automatic run stopped a payment before it left, because a rule it matched was switched off.payment_id, amount, currency, reason (a stable error code, not a sentence)
- ping
- Once, when you save the address and when you rotate the secret. Not in the picker: every subscription gets it, and the address is only saved if it answers 2xx.None beyond the common ones
The body
Amounts and identifiers.
Never names
Every body carries event, version and at, then its own fields. Amounts are unsigned decimal strings, and no event carries a name, a note or an address.
Common fields
- eventThe event name, the same string as the X-Orla-Event header.
- versionThe body version, 1 today. Within a version a field is only ever added, never renamed or removed.
- atWhen the event was written, ISO 8601 with an offset.
- amountA decimal string with eight places, never a float, never signed.
The signature
Compute it on the bytes you received,
before you parse them
X-Orla-Signature carries t=<unix seconds>,v1=<hex>: the HMAC-SHA256, under the secret shown once, of the timestamp, a dot and the raw body. Refuse a timestamp older than five minutes, compare in constant time, and drop a repeat by X-Orla-Event-Id.
import hashlib, hmac, time
def verify(secret: str, body: bytes, header: str) -> bool:
parts = dict(p. split("=", 1) for p in header. split(","))
stamp, given = parts["t"], parts["v1"]
if abs(time. time() - int(stamp)) > 300:
return False
signed = f"{stamp}. ". encode() + body
want = hmac. new(secret. encode(), signed, hashlib. sha256). hexdigest()
return hmac. compare_ digest(given, want)import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret, body, header) {
const parts = Object. fromEntries(header. split(","). map((p) => p. split("=")));
const stamp = Number(parts. t);
if (Math. abs(Date. now() / 1000 - stamp) > 300) return false;
const signed = Buffer. concat([Buffer. from(`${stamp}. `), body]);
const want = createHmac("sha256", secret). update(signed). digest("hex");
if (parts. v1. length !== want. length) return false;
return timingSafeEqual(Buffer. from(parts. v1), Buffer. from(want));
}body is the raw bytes of the request, not an object you serialised again: a JSON library that reorders keys or changes spacing changes the digest. The window is yours to choose; five minutes covers clock drift without covering a replay.
Delivery
At least once,
within a minute, for about a day
What to answer
- Any 2xx within ten secondsThat counts as delivered. Do the work after you answer, not before.
- Anything elseA retry: after 1 minute, 5, 15, then 1 hour, 3, 6 and 12. Eight attempts over about a day, then the delivery is marked as given up in the journal.
- A day of nothing but failuresThe subscription is paused and the owner of the space is told. Fix the address and press Resume in the same place.
What you can see
- The journalEvery delivery for thirty days: event, status, attempt, response code, and a Retry button on one row.
- TwinsA receiver that answers slowly may be sent the same event twice. Drop the second by X-Orla-Event-Id.
Setting it up
An address,
a choice of events, a secret you copy once
The address
Public https on port 443
Resolved again at every send. A private range, a loopback or a metadata address is refused, whatever the hostname says that day.
Redirects are not followed
Answer at the address you gave.
A ping before it is saved
Saving sends a signed ping event. An address that does not answer 2xx within ten seconds is not saved, and the form says so; the same happens when you rotate the secret, so the old one keeps working until the receiver has proven it holds the new one.
Who and how many
- Owner or adminA subscription carries everything that happens in the space and does not filter by role, so the people who may add one are the people who already see it all.
- PlansPro carries one subscription across the spaces of the plan, Scale five, Enterprise twenty five, and a pack adds five more. Free and Solo do not include it.
- The secretShown once, at creation and at rotation. Orla keeps only an encrypted copy, so New secret is the only answer to a lost one.
Write your first mandate
on a thirty minute call
On your own books: one position, one rule, and the first bill waiting for your button.