Publish events to Flowcore
Bergur DavidsenUpdated 2026-07-15
Publishing is the point where an accepted application outcome becomes durable Flowcore history. A producer should validate the business action and payload before sending the event, then treat a successful ingestion response as confirmation that Flowcore accepted the fact.
This guide covers single-event and batch ingestion, event envelopes, metadata, temporal fields, payload size, retries, and large-file patterns.
Choose an ingestion interface
Flowcore v2 applications commonly publish through one of three interfaces:
@flowcore/pathwaysfor registered, runtime-validated event contracts@flowcore/sdkfor direct typed ingestion commands- The webhook ingestion API for non-TypeScript services or integrations
Use Pathways when contract registration and Zod validation are part of the application. Use the SDK when you want direct command-level control. Use the HTTP API when another language or system owns the producer.
All three write into the same event hierarchy: tenant, data core, flow type, and event type.
Validate the business action first
Ingestion is not a substitute for application validation.
A producer should normally:
- Authenticate and authorize the caller.
- Validate the command or external input.
- Check business rules and invariants.
- Build the event payload and metadata.
- Publish the event.
- Return the event ID or accepted result.
If a requested action is invalid, do not publish an event that says it happened.
Publish with Pathways
A registered Pathways contract validates the payload before ingestion:
const app = pathways.register({
flowType: "order.0",
eventType: "order.placed.0",
schema: orderPlacedSchema,
writable: true,
})
const eventId = await app.write("order.0/order.placed.0", {
data: {
orderId: crypto.randomUUID(),
customerId: customer.id,
currency: "EUR",
placedAt: new Date().toISOString(),
},
metadata: {
source: "checkout-api",
correlationId,
causationId: commandId,
},
})write() returns the stored event ID. Keep that ID in logs and responses where it helps trace an asynchronous workflow, but do not expose unrelated sensitive metadata.
A successful write means the event was accepted. It does not mean every projection or downstream integration has finished processing it.
Publish directly with the SDK
Use IngestEventCommand when you need the SDK command model:
import {
FlowcoreClient,
IngestEventCommand,
} from "@flowcore/sdk"
const client = new FlowcoreClient({
apiKey: process.env.FLOWCORE_API_KEY!,
})
await client.execute(new IngestEventCommand({
tenantName: "acme-production",
dataCoreId: "dc_replace_me",
flowTypeName: "order.0",
eventTypeName: "order.placed.0",
eventData: {
orderId: "2d7366b0-1f6d-4bd2-baf1-a55d03cecd8d",
customerId: "a4c190c4-477c-4638-b549-1e03fd58f226",
currency: "EUR",
placedAt: "2026-07-15T12:00:00.000Z",
},
metadata: {
source: "checkout-api",
correlationId: "checkout-8c06d21d",
},
eventTime: "2026-07-15T12:00:00.000Z",
validTime: "2026-07-15T12:00:00.000Z",
isEphemeral: false,
}))The ingestion service uses https://webhook.api.flowcore.io on managed Flowcore. Dedicated installations may use a different endpoint supplied by the platform operator.
The SDK handles the ingestion authorization header. Do not manually transform or partially expose the API key.
Understand the event envelope
A stored event contains more than the business payload. The envelope carries the information Flowcore and consumers use for routing, ordering, checkpoints, and traceability.
Common fields include:
eventId: unique event identitytimeBucket: storage partition and replay positiontenant: owning tenantdataCoreId: parent data coreflowType: lifecycle groupeventType: fact typemetadata: producer-supplied operational contextpayload: validated business datavalidTime: business-validity time
Flowcore also records storage context when the event is accepted. Consumers should use the envelope rather than reconstructing routing or identity from payload fields.
Use metadata for traceability, not hidden business state
Metadata is useful for information such as:
- Producer or source system
- Correlation ID
- Causation or command ID
- Import batch ID
- Trace or request ID
- Actor or service identity reference
- Schema or migration annotations
Business facts required to understand the event belong in the payload. Do not hide required domain values in metadata merely because one consumer knows where to look.
Metadata must follow the same security discipline as payloads. Never place API keys, bearer tokens, passwords, or unrestricted personal data there.
Model time intentionally
Distributed systems often need several time concepts.
Event time
eventTime describes when the occurrence happened according to the producer or source system.
Valid time
validTime describes when the fact is effective in the business domain. It may differ from event time for corrections, imported history, or scheduled changes.
Storage time and position
Flowcore records when the event was accepted and places it in a time bucket. That storage position is used for listing, checkpointing, and replay.
For an offline device, the measurement could occur at 10:00, arrive at 10:20, and become valid for a reporting interval that began earlier. Do not collapse these meanings into one timestamp without deciding which one the business requires.
Use UTC ISO-8601 timestamps unless a contract explicitly requires another representation. Preserve source timezone information as domain data only when it matters.
Use TTL and ephemeral behavior deliberately
The ingestion interfaces can express TTL and ephemeral behavior. These settings influence how an event is handled and retained; they should not be used as an informal substitute for a documented retention policy.
Before using a short TTL or ephemeral event, answer:
- Will any consumer need replay after the TTL?
- Is the event required for audit or recovery?
- Can a projection be rebuilt without it?
- Do all downstream systems understand that the history is temporary?
Durable business facts should normally follow the data core's retention and governance policy. Ephemeral behavior is better suited to explicitly temporary signals.
Publish batches efficiently
IngestBatchCommand accepts up to 25 events in one SDK request:
import { IngestBatchCommand } from "@flowcore/sdk"
await client.execute(new IngestBatchCommand({
dataCoreId: "dc_replace_me",
flowTypeName: "order.0",
eventTypeName: "order.imported.0",
events: orders.slice(0, 25).map((order) => ({
eventData: order,
metadata: {
source: "legacy-import",
importBatchId: "batch-2026-07-15-001",
},
})),
}))Batching reduces request overhead, but it does not make downstream processing one database transaction. Consumers still receive individual event identities and must handle partial progress, retry, and ordering according to their design.
For imports larger than 25 events:
- Split input into bounded chunks.
- Give the import and each source record stable identities.
- Record which chunks were accepted.
- Retry only failed or uncertain chunks.
- Reconcile source counts with stored and projected counts.
Do not retry an entire multi-million-record source from the beginning because one late chunk failed.
Design publication idempotency
A timeout can occur after Flowcore stores an event but before the producer receives the response. The producer cannot safely assume that no event was created.
Where duplicate publication would be harmful, include a stable source or command ID and enforce idempotency in the producer's workflow. Correlation metadata helps investigation but does not automatically prevent duplicates.
For imported records, use a source-system identifier that remains stable across retries. For commands, persist the command's publication result so the application can return the original event ID after a repeated request.
Do not generate a new idempotency identity on every retry.
Respect payload limits
The single-event webhook ingestion path has a 64 KB payload limit. Leave room for the event envelope and metadata rather than constructing payloads that sit exactly at the boundary.
If a record is too large:
- Remove fields that do not belong in the durable fact.
- Split a genuinely composite fact into explicitly modeled events only when the domain supports it.
- Store large content in controlled object storage and publish a stable reference event.
- Use a supported file-ingestion pathway for file-oriented workflows.
Do not base64-encode a large file into a normal event to bypass transport design. Encoding increases size and makes retention, querying, and sensitive-data handling harder.
Handle files as file workflows
A file event should identify the file, its ownership, integrity, and processing context. Useful information includes a stable file ID, original name, media type, checksum, source, and storage reference.
For chunked file ingestion, consumers must be able to:
- Identify all parts of the same file
- Validate part order and total part count
- Verify the final checksum
- Handle duplicate parts
- Detect missing parts
- Avoid assembling unbounded data in memory
Keep credentials out of file references. Prefer short-lived access obtained by the authorized consumer rather than embedding a reusable signed URL in durable history.
Retry according to the failure
Retry transient network failures and service unavailability with bounded backoff. Do not repeatedly retry:
- Runtime schema failures
- Unknown event types
- Missing permissions
- Payloads over the limit
- Invalid sensitive-data configuration
- Business-rule rejection
Those failures require a code, contract, policy, or data correction.
Log the event path, source identity, and correlation ID. Avoid logging the API key or complete sensitive payload.
Confirm publication during operations
For critical producers, monitor accepted and rejected writes, latency, retry volume, and duplicate source identities. An application process can be healthy while all its writes fail because a key expired or a resource name changed.
Compare source counts, Flowcore ingestion results, and downstream projection counts for imports. Keep a durable record of rejected source records so they can be corrected without replaying successful input.
Next, read Query and follow event streams to retrieve stored events and observe new activity.