Search the docs or ask a question /
Concept

Event streams

One source app and one event type: the unit a program subscribes to

Get started with event streams

A stream is one source app and one event type, such as one app’s payout events. Source apps are listed under Administration › Developer › Source apps, and every API key speaks for one of them. Chain activity arrives as its own streams, from the built-in Chain source, one per chain. A program subscribes to streams on its Source tab, and each event on a subscribed stream is judged on arrival by that program’s active policies. The console has no page of its own for streams: the Transactions page shows a Streams figure, and the program wizard’s Streams step picks them.

Sending events

hira.track(type, payload) is the whole surface for emitting, with a key that reaches Transaction monitoring. It is typed: the payload shape is inferred from type. The nine types and their fields are in the API reference.

payouts.ts
hira.track("payout", {
  amount: "2900.00",          // amounts are decimal strings, never floats
  asset: "USDC",
  chain: "polygon",
  fromAddress: hotWallet,
  toAddress: beneficiary.wallet,
  corridor: "US->NG",
  customerId: "cust_55",
  txHash: receipt.hash,
});
Amounts are strings. Send "2900.00", not 2900 — decimal strings avoid float rounding on values that must reconcile to the cent.

Timestamps & keys

Both are optional at the call site. If occurredAt is omitted, the SDK stamps the current time; pass a Date or ISO string to record when the event happened. The server refuses a time more than an hour in the future. If idempotencyKey is omitted, the SDK generates a UUID per call so retried sends never double-count. Set an explicit key, for example "payout_" + id, if the same business event could be tracked from more than one place.

To name the party behind each leg, send fromPartyRef and toPartyRef: the same customerRef the directory and the session API take, never an email address, a phone number or a name.

Flushing before exit

Buffered events live in memory. In serverless handlers or on shutdown, drain them first:

handler.ts
try {
  hira.track("payout", { /* … */ });
} finally {
  await hira.flush();      // ship now
}

// on process shutdown
await hira.shutdown();       // flush + stop the background timer

Delivery & retries

Events are batched and posted to {endpoint}/v1/events. Delivery is resilient by default:

  • The buffer flushes every flushIntervalMs (5s) or once it holds maxBatchSize (20) events, whichever comes first.
  • Network errors, 5xx and 429 are retried up to maxRetries times with exponential backoff (1s, 2s, 4s… capped at 8s).
  • 4xx — bad key, malformed batch — is not retried. It fails fast.
  • On permanent failure the batch is dropped and onError(err, droppedEvents) is called. With failOpen: false, an awaited flush() or shutdown() also throws; a background flush never does.
  • The backend deduplicates on idempotencyKey per account and source app, first write wins, so a retried batch lands once.
  • A batch holds at most 1,000 events, and one refused event, such as an unknown chain, refuses the whole batch with 400.
Reconciliation. Send the txHash on every mint. The reconciliation sweep matches each onchain Ethereum mint Hira observes to a recorded mint with the same txHash, and raises reconciliation_gap for one with no match. correlationId pairs a redemption’s requested and settled events.

Security

  • Server-side only. The API key speaks for the account. new Hira() throws on a hira_sk_ key where a DOM or a web worker is present, and no /v1 door answers CORS.
  • Keys in the environment, never in source. Test keys live in the sandbox account and live keys in the live account; rotate and revoke under Administration › Developer › API keys.
  • Send what policies need — addresses, amounts, asset, authorizer. The full payload is retained as evidence, so avoid raw PII where an id will do.
  • Detective, not custodial. The SDK observes and reports. It never holds keys, signs, or moves funds.