An order is placed in your application. Analytics needs to count it, a fraud service needs to inspect it, and a customer-notification service may need to react. If the order API calls all three systems synchronously, one slow or unavailable consumer can make checkout fragile. If it fires off requests and forgets them, events can be lost.
Cloudflare K2 Streams addresses the space between those extremes. Announced on October 1, 2026, K2 is a serverless, durable event log built on R2. A producer appends records; independent subscriptions let different applications read the same retained data at their own pace. It is in public beta, not a drop-in replacement for every queue or Kafka deployment. Cloudflare’s announcement and K2 documentation describe the initial release.
This guide explains how K2 works, where it fits, how to design a first event flow, and which beta limitations should change your architecture. The aim is to help you decide whether to adopt it—not to rename every background job a “stream.”
The short answer: what is K2?
K2 is an append-only log of records with configurable retention. Producers write records in batches through an HTTP endpoint or a Workers binding. Consumers create subscriptions, request batches, process them and acknowledge them. Multiple subscriptions read independently, while several consumers sharing one subscription can divide its work. Cloudflare stores the log on R2 and manages the streaming infrastructure. K2 concepts
Think of a stream as the durable event history available for a limited time, and a subscription as a reader’s position in that history. An orders stream could have one subscription for analytics and another for fraud checks. Each would see the retained order events; two analytics workers sharing the analytics subscription would split its batches rather than each receiving every event.
Order API → K2 orders stream ┬→ analytics subscription → analytics workers
└→ fraud subscription → fraud workers
This is useful when several systems need the same event, or when a producer must keep accepting data while downstream readers catch up. It is less suitable when your real requirement is simply “run this one expensive job and retry it individually.”
How the write and read paths actually work
A producer appends a batch
Each K2 record has a byte payload and optional string headers. The payload might be JSON, but K2 does not require JSON. A batch is atomic: K2 records all of its items or none of them. Cloudflare assigns a timestamp when the batch arrives. The Workers binding accepts Uint8Array or ArrayBuffer content; HTTP producers send base64-encoded content in JSON. Produce records
An accepted write means K2 stored the batch. It does not mean every downstream system has processed it, nor does it make an earlier database transaction atomic with the K2 write. That distinction matters for order, billing and entitlement events.
The announcement reports approximately one second of p99 produce latency for the initial release, partly because K2 batches writes before persisting segments to R2. Treat that as Cloudflare’s launch measurement, not a latency guarantee for your workload. A synchronous customer-facing endpoint should not assume K2 has queue-like or in-memory-broker response time. Launch architecture and latency discussion
A subscription leases a batch to a reader
A subscription begins at earliest—the oldest records still retained—or latest—records produced after the subscription is created. Those choices are significant: earliest cannot restore records that retention has already removed, and latest intentionally skips the current backlog. Subscriptions are independent and their initial settings are immutable. Consume records
On a consume request, K2 leases a batch to a worker_id for five minutes. The reader can acknowledge it after processing, negatively acknowledge it to release it for redelivery, or extend the lease if processing needs longer. If the lease expires first, the records can be delivered again. The service therefore offers at-least-once delivery, not exactly-once side effects. One worker ID may hold one lease at a time; distinct concurrent workers need distinct IDs. Lease and acknowledgement behavior
K2’s log is ordered, but Cloudflare explicitly says processing order is not guaranteed across workers sharing a subscription. If one customer’s events must be handled strictly in sequence, do not assume a large parallel consumer pool preserves that rule. Design serialization in your application, or wait for a product capability that explicitly guarantees the ordering you require. Key-based ordering is on Cloudflare’s roadmap, not a launch feature. Consumer ordering and K2 roadmap
K2, Queues, or Basin Pipelines?
The names overlap, but the work they are designed to do differs.
| Need | Better starting point | Why |
|---|---|---|
| Run a discrete task, such as image processing, with item-level retries and a dead-letter path | Cloudflare Queues | The work item itself is the unit of retry and completion. |
| Keep an event log for multiple independent readers, replay within retention, or high-volume data movement | K2 Streams | Subscriptions can fan out the same retained data or split batches across a reader pool. |
| Ingest events that should ultimately be transformed and written to R2 or Iceberg | Basin Pipelines | It is the managed ingestion-to-storage path rather than a custom event consumer. |
This is a product-fit comparison, not a claim that one is universally cheaper or more reliable. Cloudflare describes Queues as suited to individual asynchronous jobs and K2 as suited to batch-oriented movement, retention and fan-out. K2’s batch acknowledgement also means it does not substitute directly for Queues’ per-message retry controls. Cloudflare recommends Pipelines when the desired destination is object storage or Iceberg. Cloudflare’s comparison and Queues batching behavior
For a worked queue-based SaaS design, see StadiaSoft’s Workers, D1 and Queues API guide. It addresses a different problem: making specific business jobs recoverable after an API request.
Design a first K2 flow around a business event
Suppose a SaaS product emits order.created after an order is committed. Start with the event contract, not the CLI command. A durable event should identify what happened, which version of the schema it uses, and how a consumer can deduplicate it:
{
"event_id": "8a48c53d-0a6d-46a5-b30e-a2bc3ac6ca77",
"type": "order.created",
"schema_version": 1,
"tenant_id": "tenant_42",
"order_id": "order_1001",
"occurred_at": "2026-10-01T10:15:00Z"
}
The timestamp above is the application’s event time; K2 also supplies its own receive timestamp. They answer different questions. A checkout completed at 10:15 but arrived in K2 at 10:16 should retain both facts. Keep payment details and personal data out of the stream unless each consumer truly needs them and your access, retention and legal requirements permit it. An order ID is often enough for an authorized consumer to fetch its own approved view of the order.
The event_id is not decoration. A consumer should store it alongside its own side effect—for example, (subscription_name, event_id) with a uniqueness constraint—so a redelivered record cannot send the same notification or post the same ledger entry twice.
Avoid the database-to-stream gap
This sequence is unsafe for a critical event:
- Commit the order in a database.
- Try to publish to K2.
- Return success even if the K2 call fails.
The order exists but its event may never reach analytics or fraud processing. Reversing the steps creates the opposite problem: an event can describe an order that never committed. K2 does not make those two systems one transaction.
For important business events, use a transactional outbox: save the order and an outbox row in the same database transaction; have a relay publish the row to K2; then mark it dispatched after a confirmed write. The relay must be safe to retry, because a network failure can leave the write outcome uncertain. Cloudflare documents a K2 error that may mean a batch was or was not stored. K2 does not deduplicate a retry, so consumers must deduplicate by event_id. Produce error semantics
An outbox is not required for a disposable page-view signal. It becomes much more important when losing an event would create an incorrect financial, security or customer state. This is the kind of boundary StadiaSoft maps in its API integration service: which system owns the fact, which downstream effects matter, and how discrepancies are reconciled.
A minimal implementation path
The following steps are a design walkthrough, not a claim that this example was deployed to a Cloudflare account. Cloudflare’s APIs and beta limits may change; confirm the current getting-started guide before implementation.
1. Create a stream with deliberate inputs and retention
K2 currently requires a Workers Paid plan. Create a stream through the dashboard, Cloudflare’s cf CLI, Wrangler or the API. Choose retention based on the longest outage and replay window you genuinely need; the default is seven days. If you use only a Worker producer, you can avoid exposing an HTTP producer endpoint. If HTTP production is enabled, set http.authentication to true unless you intentionally want a public write endpoint. By default, omitting that authentication setting leaves the HTTP endpoint open to anyone who knows the stream ID. CORS does not turn a public endpoint into an authenticated one. Input and retention configuration
2. Bind the producer Worker
Cloudflare’s current Wrangler format uses a k2 binding with the stream’s ID. Replace the placeholder with the ID created in your own account:
{
"k2": [
{ "binding": "ORDERS", "stream": "<YOUR_32_CHARACTER_STREAM_ID>" }
]
}
The binding allows the Worker to produce without placing a K2 API token in its source code. It does not authenticate the caller of your Worker; that remains your application’s responsibility. Workers binding setup
After the order and its outbox event are durably recorded, a trusted relay can send the serialized event:
const record = {
content: new TextEncoder().encode(JSON.stringify(event)),
headers: {
"content-type": "application/json",
"event-type": event.type
}
};
const result = await env.ORDERS.send([record]);
if (!result.success) {
// Keep the outbox row pending; classify the error before retrying.
throw new Error(`K2 produce failed: ${result.error.message}`);
}
// Only now may the relay mark this outbox attempt as dispatched.
Here, event is assumed to come from the trusted outbox, not unchecked browser JSON. Handle Cloudflare’s retryable flag and unknown-outcome errors according to its documented rules. The comment about marking the row describes application work you must implement; the snippet is not a complete relay. Workers send() and errors
3. Create separate subscriptions for separate purposes
Make one subscription for analytics and one for fraud. If either must process records already in the stream, start at earliest; choose latest only when skipping the existing backlog is intentional. A token used for consuming needs the K2 Consume permission. Keep it server-side, not in browser code. Subscription API
curl "$K2_ENDPOINT/subscriptions" \
--request POST \
--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
--header "Content-Type: application/json" \
--data '{"name":"analytics","start_at":{"type":"earliest"}}'
Create a second subscription named fraud using the same pattern. Two analytics workers should share the analytics subscription ID but use different worker_id values; the fraud service uses its own subscription ID. This is how you combine work sharing and fan-out without assuming every worker receives every record.
4. Process and acknowledge a leased batch
Poll /subscriptions/<ID>/consume with a stable worker identity and a sensible max_records limit. The response includes a batch_id, leased_until_ms and base64-encoded record contents. Decode each record, validate its schema version and tenant context, apply an idempotent side effect, and acknowledge the batch only after the relevant work is durable. If processing will cross the five-minute lease boundary, extend the lease before expiry. If you cannot process the batch, nack it so K2 can redeliver it. Consume and acknowledge guide
Batch-level acknowledgement changes error handling. One malformed record should not cause endless redelivery of the same batch. A practical policy is to quarantine a permanently invalid record with its payload reference and error, process the valid records idempotently, and acknowledge only when every record has either completed or been durably quarantined. A transient downstream outage should leave the batch unacknowledged or negatively acknowledged for a later attempt. Your quarantine store, alerting and replay procedure are application responsibilities, not a built-in per-record K2 dead-letter queue.
Five production risks to resolve before rollout
- Duplicates: K2’s consumer guarantee is at least once, and some producer failures have uncertain outcomes. Deduplicate by a stable event ID at the destination. Test a crash after the side effect but before batch acknowledgement.
- Ordering: A stream is ordered, but parallel workers sharing one subscription can finish out of order. If strict order matters per entity, prove the design preserves it or reduce parallelism until you have an explicit serialization mechanism.
- Retention and replay:
earliestmeans the oldest retained record, not all events ever produced. Set retention to cover realistic recovery time, monitor lag, and keep an authoritative system of record for longer-term recovery. Cloudflare notes that expired records can remain readable for a time, so retention is not an exact deletion clock. Retention configuration - Security: Never put K2 API tokens in client code. Use least-privilege tokens for management and consumption, authenticate any HTTP producer, authorize your Worker endpoints, and limit sensitive fields. Treat event headers and bodies as untrusted at the consumer boundary.
- Observability: Measure accepted versus failed writes, oldest unprocessed event age, subscription lag, lease expiry, redelivery, quarantine volume and downstream error rates. Define who responds when one subscription falls behind while the others appear healthy.
For a SaaS team, these checks should be part of the release plan rather than an afterthought. StadiaSoft’s SaaS development service covers the product and operational system around an event flow: permissions, integrations, monitoring and recovery.
Beta limits and pricing: what to plan around
As of October 1, 2026, K2 is available in public beta to Workers Paid accounts. Cloudflare documents a 10 GB account-wide storage cap, 20 streams per account, one-hour to 30-day retention range with a seven-day default, 5 MB maximum produce request, roughly 1 MB maximum record, and 100 subscriptions per stream. A subscription can have up to 128 active leases. The announcement states an initial 30 MB/s produce limit per stream. These are beta-era values, not permanent design constants. K2 limits and launch announcement
Cloudflare says K2 usage is not billed during the beta. It has published anticipated, not final, post-beta rates: $0.04 per GB produced, $0.04 per GB consumed and $0.02 per GB-month retained. Do not turn those figures into a firm customer quote. A fan-out design with three independent subscriptions can consume the same event data three times, so model produced bytes, bytes read by each subscription, average retained data and the costs of Workers and downstream systems separately. Cloudflare’s pricing statement
If your expected retained data exceeds 10 GB or your use case requires more than 30 days of replay, request a limit increase or use a different long-term archive. Do not assume the beta service can silently absorb production growth.
When should you choose K2 today?
Choose K2 for a bounded pilot when you need independent readers of the same event history, can work within the beta limits, can tolerate batch-oriented latency, and are prepared to build idempotent consumers. Product analytics, telemetry, integration fan-out and change-event distribution are reasonable candidates.
Choose Queues when the main unit of work is an individual task with per-item retries, delays or a dead-letter flow. Choose Basin Pipelines when your purpose is managed ingestion into R2 or Iceberg. Keep a synchronous API call where the user must know the result before the operation completes. Avoid assuming K2 already supports push-based Worker consumers, message-key ordering or Kafka-client compatibility; Cloudflare lists those as future work. Product comparison and roadmap
The useful question is not “Can we stream every event?” It is: which consumers must see which facts, how soon, for how long, and what happens when one of them fails? Those answers determine the event contract, retention, subscription layout and recovery plan.
If you are designing a multi-system event flow, talk to StadiaSoft about the integration boundary. We can help identify the events worth publishing, the consumers that need them, and the reliability controls that make the system operable.
Technical review: October 1, 2026. K2 was in public beta at review time. Check Cloudflare’s current documentation, limits and pricing before implementation.
Frequently asked questions
Is Cloudflare K2 the same as Cloudflare Stream?
No. K2 is an event-streaming log for application records. Cloudflare Stream is a separate product for video storage, encoding and delivery. Similar naming does not mean the products serve the same workload.
Does K2 provide exactly-once processing?
No. K2 documents at-least-once delivery. A record can be redelivered after a lease expires or processing fails; producer retries can also duplicate a batch when the write outcome is uncertain. Use stable event IDs and idempotent consumer effects.
Can a Cloudflare Worker consume K2 events automatically?
The launch Workers binding is for producing records. Current consumption uses pull-based subscriptions over HTTP. Cloudflare names push-based Worker consumers as a roadmap item, not an available launch behavior.
Does an ordered log guarantee that my consumers finish in order?
No. Cloudflare states that processing order is not guaranteed across workers sharing a subscription. The event order in the log and completion order in a parallel consumer pool are different guarantees.