Skip to main content
A subscription tells Streaming Hub which events go to which destination for your tenant. You manage subscriptions through the /v1 control-plane API, authenticated with a plugin-auth JWT. This page covers the model and the onboarding flows; the exact request and response shapes are in the API reference (start from the reference introduction).

The subscription model


A subscription records:
  • name — a human label (required, non-blank).
  • sink_kind — one of webhook, pull, sqs, rabbitmq, eventbridge.
  • endpoint — where deliveries go, in a shape that depends on the sink kind (see below).
  • event_types — the event types to deliver. An unknown type warns but never blocks a create, so a catalog gap never stalls onboarding.
  • schema_major — the payload major version the subscription follows.
  • plan_tier — the delivery tier the subscription runs under.
The endpoint format follows the sink kind:

Two orthogonal status fields


Every subscription carries two independent status fields. Confusing them is the most common source of “why did delivery stop” questions, so keep them separate:
  • verification_state is proof that the destination can actually receive events. It moves through pending_verificationactivedegraded, driven by probes. Only an active subscription is deliverable.
  • enabled is the deliverability switch. It is what auto-disable flips off, and what a re-enable turns back on.
Subscription matching requires both: a subscription delivers only when enabled and verification_state = active. Auto-disable lives entirely in enabled and never changes verification_state, so an auto-disabled subscription reads as enabled = false, verification_state = active. See auto-disabling a broken destination for how the two interact.

Creating a webhook subscription


A webhook subscription is the simplest to onboard — it is born ready to receive:
  1. CreatePOST /v1/subscriptions with sink_kind: "webhook" and your https:// endpoint. The destination URL is validated against private, loopback, and cloud-metadata address ranges before any row is written, so a private or metadata target can never be stored. The response returns the new subscription already active, and the signing secret exactly once.
  2. Save the signing secret — it is shown only in this response, stored only as ciphertext, and never returned by any read. Save it on receipt; if you lose it, you can only rotate to a new one.
  3. Confirm reachability (optional)POST /v1/subscriptions/:id/ping sends a synthetic, signed probe through the real delivery path and reports the classified outcome.
The signing secret is returned only in the create response (and again on rotation). It is never logged, never stored in plaintext, and never returned by a GET. Capture it when you create the subscription.
Once created, the webhook subscription is matched and begins receiving events. See Consuming events for how to verify the signature on each delivery.

Onboarding a queue subscription


Queue subscriptions (sqs, rabbitmq, eventbridge) are born pending_verification and deliver nothing until their destination is verified. How you verify depends on the kind:
  • RabbitMQ — supply a broker credential, verified on write (below).
  • SQS and EventBridgewire an AWS delegated grant (next section) instead of storing a credential.
For RabbitMQ, the credential flow is three steps:
  1. CreatePOST /v1/subscriptions with the queue sink_kind and its endpoint, and no inline credential (an inline sink_config or credential is rejected). The subscription is stored pending_verification; matching excludes it, so it produces no delivery jobs yet.
  2. Supply the credentialPUT /v1/subscriptions/:id/credential with the write-only broker credential. The hub holds it in memory, probes it immediately (connect and authenticate against the broker), and persists it as ciphertext only if the probe succeeds — a failed probe stores nothing. The broker host is validated against blocked address ranges before it is stored. A successful probe flips the subscription pending_verification → active in the same transaction.
  3. Active — once active, matching admits the subscription and it begins receiving events.
The credential is write-only: you supply it here and it is never returned, not even masked, on any read path. To change it, PUT a new one — the same probe-on-write applies.

Wiring an AWS delegated grant


For AWS sinks (sqs, eventbridge), the hub delivers by assuming a role in your AWS account — it never stores an AWS credential. You wire that trust before supplying the credential:
  1. Fetch the setup artifactsGET /v1/subscriptions/:id/setup-artifacts returns a cross-account IAM trust policy, a CloudFormation quick-create link, and a non-secret ExternalId the hub mints for this subscription.
  2. Apply them in your AWS account — create the delivery role from the trust policy (the quick-create link scaffolds it). The role trusts the hub’s principal only under the minted ExternalId condition, which closes the confused-deputy gap: a trust policy missing that condition is treated as a verification failure, never as verified.
  3. Register the grantPUT /v1/subscriptions/:id/delegated-grant with the delivery role ARN, region, and destination. This records non-secret coordinates only; it runs no probe and does not change verification_state.
  4. VerifyPOST /v1/subscriptions/:id/verify runs the hardened role-assumption probe and, on success, flips the subscription to active.
No AWS credential ever crosses these requests or is stored by the hub; the hub assumes your role per delivery, guarded by the ExternalId.

Rotating a signing secret


POST /v1/subscriptions/:id/secret/rotate mints a new webhook signing secret, returns it once, and starts a 24-hour dual-sign overlap. During the overlap the hub signs each delivery with both the new and the previous secret and sends two signature headers, so your consumer can switch to the new secret at any point in the window without dropping deliveries. Rotate cleanly like this:
  1. Call rotate and save the new secret from the response (alongside the overlapUntil timestamp).
  2. Deploy the new secret to your consumer within the overlap window. While both are configured, your verifier accepts a delivery if either signature validates.
  3. After the overlap, retire the old secret.
Rotating a pull sink — which has no signing secret — is rejected. See handling two signatures during rotation for the consumer-side verification.

Recovering an auto-disabled subscription


When a destination stays broken long enough, the hub auto-disables the subscription by flipping enabled = false. To recover it:
  1. Fix the destination (the endpoint, the queue, or the grant).
  2. Call POST /v1/subscriptions/:id/verify. It re-probes the destination and, on a successful probe, re-enables the subscription and clears the auto-disable mark — in place, keeping the same id and signing secret.
A failed re-probe changes nothing, so re-enable is always bound to a real, current probe success. GET /v1/subscriptions/:id/health gives you the delivery-health rollup — recent outcomes, dead-letter counts, and the auto-disable verdict — to confirm the destination is healthy before and after.

Re-pinning the schema major


PATCH /v1/subscriptions/:id re-pins the subscription’s schema_major — the only mutable field:
  • {"schema_major": 2} pins the subscription to that major version.
  • {"schema_major": null} clears the pin to follow the base version.
Any other field, an empty body, or a value below 1 is rejected (not silently ignored), so you can never believe a forbidden change took effect. Changing the endpoint, sink kind, or secret is out of scope for PATCH by design; create a new subscription for a different destination. A re-pin is not a destination change — it does not reset verification_state and echoes no secret.

Idempotency


The mutating routes — create, delete, PATCH, secret rotate, and delegated-grant registration — require an idempotency key:
A mutation sent without it is rejected before any write with 400 missing_idempotency_key. The store is fail-closed: if the idempotency store is unreachable, the request fails rather than risking a double write — this guarantees a replayed create or rotate re-serves the original once-shown secret instead of minting a new one.
  • Replaying a committed key returns the original response byte-for-byte, with X-Idempotency-Replayed: true.
  • Reusing a key with a different request body returns 409 idempotency_conflict — mint a new key for a corrected request.
The verification routes (ping, verify, PUT /credential, GET /setup-artifacts) are naturally idempotent and require no key. See the platform-wide guidance on retries and idempotency.

Next steps


Consuming events

Verify webhook signatures, deduplicate deliveries, and pull events.

How Streaming Hub works

Matching, dispatch, retries, and auto-disable in detail.