Skip to main content

Configure event sinks: Kafka or HTTP batch

An event sink streams the entire platform event taxonomy — the same message.*, call.*, … events your webhooks receive — to one destination you run: a Kafka topic, or an HTTP collector that receives batched JSON arrays. One sink replaces a fleet of single-event webhook endpoints. This guide walks through choosing a transport, configuring it in the dashboard or through the API, and operating it in production.

What event sinks are vs webhooks

Both surfaces are push-based: Orbit delivers events to you rather than you polling for them. The difference is the destination shape:
  • Webhooks — per-endpoint HTTPS POSTs, one event per request, with an X-Orbit-Signature HMAC over each body. You register endpoints on an event list and each becomes a steeply-secured receiver.
  • Event sinks — a single declared destination per transport (kafka or http_batch); a subscriber filter such as ["message.*"] decides which of the platform’s event taxonomy lands there. The Kafka transport is not an HTTP receiver at all, and the HTTP-batch transport gets a JSON array of events in one POST.
Choose sinks when you want one destination carrying many event types, when you already run Kafka, or when per-POST HMAC signing is wasteful against your consumer code (sink records are byte-identical to the webhook envelope — one parser serves both). Choose webhooks when a third-party expects a single-purpose URL or when you want per-endpoint signing without any consumer library. The full model is in Webhook fan-out; the live transport contract is in Webhooks overview.

Choose destination — HTTP batch or Kafka

  • HTTP batch fits a receiver you can stand up behind HTTPS: a Lambda-flavored endpoint, a log-collecting pipeline, an internal orchestration layer that accepts arrays. Tuning knob is max_batch_size (1–1000 events per POST body, default 100).
  • Kafka fits a data plane that already runs brokers: you give Orbit the bootstrap brokers, a topic, and an optional SASL credential; Orbit produces one record per event keyed by your partition field so per-contact ordering holds.
Both sinks are independent — you can run either, both, or neither. If neither is enabled, nothing is delivered to sinks and your webhooks still work.

Configure HTTP batch

Configure it in the dashboard under Developer → Event Sinks → HTTP batch, or through PATCH /api/v1/developer/event-sinks/http_batch:
The collector url must be https and resolve to a public address — private and metadata hosts are rejected, which is also why a PATCH that fails your organization’s ingress proxy can’t be smuggled in. The credentials value becomes the Authorization: Bearer header on every batch POST; it is encrypted at rest and never returned (credentials_configured: true is your proof it stored). Retry behaviour is covered below.

Node.js consumer for HTTP batch (payload-rich envelopes)

The body is a plain JSON array; every entry is the same id / type / created_at / data envelope the webhook page documents. Ack the whole batch with 2xx — a non-2xx makes the sink retry the batch with exponential backoff, which is why you dedupe on event.id at the receiver.

Configure Kafka

In the dashboard under Developer → Event Sinks → Kafka, or through PATCH /api/v1/developer/event-sinks/kafka:
The four required connection inputs:
  • brokers — 1–16 bootstrap brokers as host:port, no scheme, no path.
  • destination.topic — the Kafka topic to produce to; 1–249 chars, [A-Za-z0-9._-], and never . or ...
  • destination.partition_key_path — optional. A dot-path into the event envelope used to derive the record’s partition key. Default chain: data.contact_iddata.conversation_iddata.message_iddata.call_iddata.to → event id. Co-locating one contact’s events on one partition is the ordering guarantee downstream consumers can rely on.
  • sasl_mechanism + sasl_username + credentials — the SASL handshake. Mechanisms: plain, scram-sha-256, scram-sha-512. The password is the encrypted credential; the username is stored plaintext alongside the brokers. If your brokers are not SASL-protected, omit all three and the sink produces without authentication.
destination.kind must equal the path segment — PATCH /…​/kafka with destination.kind: "http_batch" is a 400.

Python consumer (confluent-kafka)

Kafka records are one event per message — no array unpacking. The x-orbit-event-type header duplicates the envelope type, so a consumer that branches only on headers can skip parsing the value when you want to filter cheaply.

Delivery semantics — batched, ordered, at-least-once

A sink delivery is at-least-once, same as a webhook:
  • Batched. http_batch groups up to max_batch_size envelopes per POST; Kafka produces one record per event but linger-batches them inside the producer. Either way, retries that haven’t yet exhausted their backoff can result in the same event appearing twice — dedupe on the envelope id.
  • Partition-ordered. Kafka guarantees ordering only within a partition; that’s why the partition key exists (a single contact’s events are co-located). Ordering across two different contacts is not defined. http_batch delivers in flush order per tenant but a retry can interleave — rely on created_at for entity state, not receive order.
  • Retries. A failed attempt (non-2xx on the HTTP POST, or a producer error on the Kafka send) is retried with exponential backoff, bounded at ~5s maximum delay, 3 attempts per record/batch. Each retry of the same event carries the identical serialized value — the bytes don’t change mid-flight.
For the full retry/ordering/DLQ model as it applies to all surfaces, see Webhook delivery semantics.

Inspect deliveries + DLQ mechanics

Both the dashboard page and a GET expose the sink’s worker-owned run-status fields:
Read last_run_status as the health signal; consecutive_failures is a leading indicator even before you’d triage last_error. In the dashboard the same fields render as the status chip and health line on Developer → Event Sinks. When retries exhaust on a record or batch, the event is filed to a per-sink dead-letter list and the last_run_status flips to failed — the sink keeps consuming new events, it just stops retrying the poisoned one. You can run your own error-handling on your side based on the delivery outcome (your consumer logs + the sink’s run status are the two surfaces).

Event sink vs webhook — comparison

Pick the one that matches your consumer’s natural shape. A customer receiving webhooks today can keep them while onboarding a Kafka sink in parallel — the event taxonomy, envelope shape, and filter vocabulary are identical.

Production checklist

  • Collector url (http_batch) or brokers + destination.topic (kafka) set and enabled.
  • credentials_configured: true on a read — the secret round-tripped.
  • events filter set to the tightest prefix you actually consume (message.*, not *, unless you genuinely handle all).
  • Receiver dedupes on the envelope id — at-least-once re-delivers under retry.
  • A nightly job reads the sink (GET /api/v1/developer/event-sinks/{kind}) and alerts on last_run_status: "failed" or a growing consecutive_failures.
  • SASL credentials live in a secrets manager, not in source control.

Next steps