Emitting events

this.emit() publishes an event to one of your workspace's event sources. Every deployment subscribed to that source runs with the payload you published.

async chargeCard() {
  const result = await charge(this.payload);
  await this.emit("order.paid", { orderId: result.id, amount: result.amount });
  return result;
}

The emitting flow does not wait for the subscribers. Fan-out happens on the platform's dispatch queue after the event is recorded, so emit resolving means the event exists, not the subscribers finished.

This is how you decompose work: one flow does the thing, several unrelated flows react to it, and none of them knows about the others.

What it returns

const res = await this.emit("order.paid", payload);
// { eventId, subscribers, deduped, inactive }

A switched-off source does not throw

Switching a source off must not break every flow that mentions it, so an emit to an inactive source returns { eventId: null, inactive: true } and your flow carries on. Branch on it if it matters:

const res = await this.emit("order.paid", payload);
if (res.inactive) this.log("WARN", "order.paid is switched off; nothing was recorded");

Real failures are thrown: an unknown key, a payload the source's schema refuses, and an emit too deep inside an event cascade.

Making an emit safe to retry

A step can be retried, and a naive emit inside one publishes twice. Pass an idempotency key:

await this.emit("order.paid", payload, { idempotencyKey: `order-${order.id}` });

A second emit with the same key inside the platform's dedupe window returns the first event and dispatches nothing. deduped: true tells you that happened.

Publishing bytes

An event's payload can be raw bytes instead of JSON:

import { Workflow, binary } from "konnecthub/flow";

const res = await fetch(url);
await this.emit("file.received", binary(res.body!, "application/pdf"), {
  filename: "invoice.pdf",
  meta: { url },
});

Bytes are streamed to the platform chunk by chunk, and each chunk is acknowledged only once stored — so a slow store throttles the producer instead of filling the worker's memory. A stream interrupted by a throwing step or a dead worker records no event at all, rather than a truncated one.

meta is a JSON sidecar stored alongside a binary payload. It is how bytes carry structured context, since the bytes themselves describe nothing.

Receiving an event

On the other side, a deployment subscribed to the source runs with the published payload as this.payload, and this.event describes the delivery:

handle() {
  const ctx = this.event;   // undefined for a webhook, schedule or manual run
  if (!ctx) return;

  this.log("INFO", `from ${ctx.source} at depth ${ctx.depth}`, { eventId: ctx.eventId });
}

depth is the cascade depth: 0 for a webhook or manual origin, and one more for each emitting subscriber in the chain. It is the guard against an emit loop — a flow that emits to the source that triggered it will eventually be refused rather than running forever.

Limits worth knowing

Next