Skip to main content

Run your first event sink deployment

An event sink streams the platform’s event taxonomy — message.*, call.*, and the rest of the same catalog your webhooks receive — to one destination you own, instead of a fleet of single-purpose webhook endpoints. This guide takes one sink from zero to production: pick the destination shape, wire it, validate the envelope, operate it under backpressure, and fan out when you outgrow one destination. For the full transport contract, see Configure event sinks. This guide is the deployment path; that guide is the field reference.

1. When a sink beats webhooks and SSE

Webhooks and sink deliveries are both push-based — the difference is the destination shape, not the delivery model.
  • Webhooks fit when a third party expects a single-purpose HTTPS URL, or when each endpoint handles a narrow event list. Past a handful of endpoints, you are running a receiver farm with per-endpoint signing, per-endpoint retry state, and per-endpoint auto-disable logic.
  • Server-sent events fit interactive dashboards that can tolerate a dropped event on reconnect. They do not fit delivery you cannot afford to miss.
  • An event sink fits when you already run Kafka, when you want one destination carrying many event types, or when delivery volume makes per-POST signing wasteful. One collector endpoint or one topic replaces the receiver farm.
If your webhook endpoints are saturating on message.* volume, or your queue consumers (an SQS poller, a Kinesis consumer) are starving because each event arrives as an individual webhook, consolidate onto a sink and point it at the queue front.

2. Source vs sink — pick the right direction

Orbit has three adjacent event surfaces. Pick the outbound one here:
  • Event sources (inbound) — consume your Kafka topic into Orbit, so your own domain events (orders, payments, CRM updates) drive platform behaviour. That is the inbound direction; documented in Configure event sources.
  • Event sinks (outbound) — stream the platform’s own event taxonomy out to a destination you run. That is this guide.
  • Connector downloads — the Zapier, Make, and n8n app definitions on Developer → Connectors are a different surface entirely: pre-built automations on top of webhooks. If you are deciding between writing your own sink consumer and importing a connector, see Connector downloads first — it can remove the need to run anything yourself.
The rest of this guide is the outbound sink path.

3. Wire your first sink

Two sink transports ship today: http_batch (an HTTPS collector you run, receiving JSON arrays) and kafka (Orbit produces native records to your brokers). AWS queue destinations — SQS, Kinesis — sit behind the HTTP-batch transport: stand up a small collector that puts the array onto the queue, and the sink delivers to it.

3.1 Choose the destination

Both sinks are independent. Run either, both, or neither.

3.2 Configure via the API (or the dashboard)

The same fields are settable in the dashboard under Developer → Event Sinks, or over PATCH /api/v1/developer/event-sinks/:kind. This example configures an HTTP-batch sink feeding an SQS front; for Kafka-native see Configure event sinks.
  • destination.kind must equal the path segment — PATCH /…​/http_batch with a Kafka destination body is a 400.
  • The collector url must be public https — private and metadata hosts are rejected before the config can ever persist.
  • credentials becomes the Authorization: Bearer header on every batch POST. It is encrypted at rest, write-only, and never returned on a read.
  • Keep the events filter tight. message.* over * is the difference between a sink that stays cheap and one that carries every contact.* update you never consume.
For an SQS front, the collector is a few lines — receive the array, verify the Bearer token you set, put each entry on the queue, and 200 only when the batch is durably enqueued:

3.3 Validate the schema with a test event

Sink records are byte-identical to the webhook envelope — one parser serves both surfaces. Before you point traffic at the sink, confirm the shape your consumer will see:
The envelope is always id / type / created_at / data; data carries the event-specific payload. Your consumer should branch on type, dedupe on id (deliveries are at-least-once — retries re-deliver), and treat created_at as the entity-state clock rather than receive order. Generate real traffic the cheapest way you can (send yourself a test SMS, complete a test call), then read the sink back:
credentials_configured: true proves the secret round-tripped. last_produced_count climbing past zero proves events are landing. Seeing last_run_status: "ok" on a read is the green light to cut your consumer over.

4. Operate — backpressure, retries, dead letters

A sink delivery is at-least-once, and failure is never silent — it surfaces in the run-status fields above.
  • Retries. A failed attempt (non-2xx on the HTTP POST, or a Kafka producer error) is retried with exponential backoff bounded at ~5s, 3 attempts per record or batch. Each retry carries the identical serialized bytes.
  • Dead letters. When retries exhaust, the event files to a per-sink dead-letter list and last_run_status flips to failed. The sink keeps consuming new events — it stops retrying the poisoned one, so a single bad event never stalls the stream.
  • Backpressure at your receiver. The sink reads any non-2xx as a retry signal. If your collector is saturated, answer with a 429 (or 5xx) rather than queuing unsustainably, and drain at your own pace; the bounded backoff spreads the redeliveries. If you need a stronger lever, lower max_batch_size so each POST is a smaller unit of work.
  • Replay. Redrive dead-letter entries by re-ingesting them on your side (your consumer logs plus the sink’s run status are the two surfaces of truth). For webhook-surface dead letters, the dashboard offers a 7-day replayable DLQ; the sink-side list is yours to re-drive from the stored payloads.
Run a nightly job that reads GET /api/v1/developer/event-sinks/:kind and alerts on last_run_status: "failed" or a rising consecutive_failures. That is the whole monitoring contract — one field tells you the sink is healthy.

5. Scale — multiple sinks and fan-out

The Kafka and HTTP-batch sinks are independent, so fan-out is a configuration decision, not a pipeline problem:
  • Split by event family — route message.* to your queue front (http_batch → SQS) and call.* to Kafka, so each consumer pool scales against its own traffic shape.
  • Both directions at once — keep your existing webhook endpoints live while you onboard the sink; the event taxonomy, envelope shape, and filter vocabulary are identical, so cutover is a consumer change, not a data change.
  • Land events in a warehouse — if the end goal is analytics rather than real-time consumption, mirror contacts and event streams into Snowflake, BigQuery, or another warehouse on a cadence instead of running a receiver at all. That path is documented in CDP reverse ETL — the right call when the destination is a warehouse rather than a queue.

Production checklist

  • Destination set (http_batch collector URL, or Kafka brokers + destination.topic) and enabled: true.
  • credentials_configured: true on a read — the secret round-tripped.
  • events filter set to the tightest prefix you actually consume.
  • Receiver dedupes on the envelope id — at-least-once re-delivers under retry.
  • Collector answers 429/5xx under saturation instead of queuing unboundedly.
  • A nightly reader alerts on last_run_status: "failed" or growing consecutive_failures.
  • Secrets live in a secrets manager, not in source control.

Next steps