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


Every webhook delivery is signed with an HMAC over the request timestamp and the exact request body, so you can prove the request came from Streaming Hub and was not tampered with or replayed. Two headers carry the signature: The signature is computed as:
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.
The signing secret is the one you received when you created the subscription (or last rotated it). It is never sent in a header — only the derived signature is. 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 — sent 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:

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. A slow or failing response is treated as a failed delivery, which triggers the retry curve and, if the destination stays broken long enough, eventually auto-disables the subscription. Acknowledge fast, process out of band.

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.