Skip to main content
Streaming Hub delivers events two ways. Push sinks (webhook, sqs, rabbitmq, eventbridge) send each matched event to a destination you own. A pull sink holds events on a server-side cursor that your consumer reads on its own schedule. This page covers what your consumer must do in each case.

Verifying a webhook signature


Streaming Hub signs every webhook delivery with an HMAC over the request timestamp and the exact request body. The signature proves that the request came from Streaming Hub, and that it was not tampered with or replayed. Two headers carry the signature: The signature formula is:
To verify a delivery:
  1. Read the raw body and the timestamp: verify before parsing the body, over the exact bytes received. Any re-serialization changes the bytes and breaks the signature.
  2. Check freshness: reject the request if X-Webhook-Timestamp is more than 5 minutes from now. This is the replay-protection window.
  3. Recompute the signature: build signature_input as above with your signing secret and hex-encode the HMAC-SHA256.
  4. Compare in constant time: compare your v1,sha256=<hex> against the received X-Webhook-Signature with a constant-time (timing-safe) comparison. Reject on mismatch.
  5. Respond: return 2xx only after the signature and freshness both pass.
You receive the signing secret when you create the subscription, or when you last rotate it. Streaming Hub never sends the secret in a header. The header carries only the derived signature. See creating a webhook subscription for where the secret comes from.

Handling two signatures during rotation


When you rotate a signing secret, the hub runs a 24-hour dual-sign overlap. During the overlap each delivery carries two X-Webhook-Signature headers, one signed with the new secret and one with the previous secret. The hub sends them as repeated headers, so you can migrate to the new secret without dropping any delivery. Verify against both: recompute the expected signature with each secret you currently hold, and accept the request if either received signature matches. Once you have deployed and confirmed the new secret, retire the old one.
Read X-Webhook-Signature as a list of repeated header values using your framework’s multi-value header accessor (or its raw-headers view). Do not split a single joined string on commas: the signature value itself contains a comma (v1,sha256=…), so a naive comma-split corrupts it. Some HTTP stacks join repeated headers with ", " by default. Use the list accessor to get each value intact.

Correlation headers


Every webhook delivery also carries a set of X-Lerian-* context headers:
Webhook delivery carries no producer-source header. X-Lerian-Event-Type is deliberately source-free, so a consumer cannot distinguish two producers that emit the same key unless the subscription pins origin. Create one origin-pinned subscription per producer when source identity matters.

Deduplicating deliveries


Delivery is at-least-once: the hub may deliver the same event more than once, through retries or a redelivery after a worker restart. Deduplicate on X-Lerian-Event-Id. It is stable across every redelivery of the same event, while X-Lerian-Delivery-Id differs per attempt. Treat a X-Lerian-Event-Id you have already processed as a duplicate: acknowledge it with a 2xx and do not reprocess. Keep your handler idempotent.

Responding quickly


Return a 2xx as soon as you have verified and durably accepted the event. Then do the real work asynchronously. The hub treats a slow or failing response as a failed delivery. A failed delivery triggers the retry curve. If the destination stays broken long enough, the hub eventually auto-disables the subscription.

Pulling events


A pull subscription receives no push. Instead, your consumer reads a page of its events with:
The events come back in ascending arrival order, each with its seq, its ceId (for dedup), its type, and its payload. The response includes a next_cursor. The read is the acknowledgment (cursor-as-ack). Fetching a page advances the subscription’s durable cursor to the highest seq returned. There is no separate ack call. Reading a page acknowledges it. This is monotonic, so it never moves backward. To page through normally, resume from the server’s next_cursor and stop when a short page (fewer than your limit) returns null. Two behaviors to keep in mind:
  • A forward ?after= seek forfeits the gap. Passing ?after=N greater than your current cursor advances the cursor past everything up to N. The skipped events are never redelivered. Asking for events after N declares everything up to N consumed. This can only happen with a hand-crafted forward seek, never through normal next_cursor pagination.
  • A backward ?after= seek never rewinds. A stale or lower ?after= that reads an older page does not move the cursor back, because the cursor only ever advances.
The pull read is rate-limited per tenant. A denied read returns 429 rate_limited. Back off and retry. Deduplicate pulled events on ceId, just as a webhook consumer deduplicates on X-Lerian-Event-Id.

Next steps


Managing subscriptions

Create subscriptions, rotate secrets, and recover disabled destinations.

Operating Streaming Hub

Deploy, configure, and observe the hub.