> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Managing subscriptions

> Create webhook and queue subscriptions in Streaming Hub, wire an AWS delegated grant, rotate signing secrets, recover auto-disabled destinations, and re-pin.

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 API reference has the exact request and response shapes (start from the [reference introduction](/en/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`**: source-free `<resource>.<event>` keys to deliver. The hub accepts any well-formed key, so a catalog gap never stalls onboarding. A key that no producer emits matches nothing. Take each key from the per-product pages under [Event streaming](/en/reference/events/overview).
* **`origin`**: an optional exact `ce-source` pin. Omit it to accept the same key from any producing application.
* **`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:

| Sink kind     | Endpoint                                                                                                                           |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `webhook`     | An `https://` URL, no embedded userinfo.                                                                                           |
| `pull`        | Omitted — the server synthesizes `pull://<id>`.                                                                                    |
| `sqs`         | The `https://` SQS queue URL.                                                                                                      |
| `rabbitmq`    | `<exchange>/<routingKey>` (exchange required, routing key optional). The broker host lives in the encrypted credential.            |
| `eventbridge` | The EventBridge event-bus name. `DetailType` is derived from the matched event type. The region lives in the encrypted credential. |

<Warning>
  Queue delivery does not preserve the canonical source-qualified `ce-type`. SQS and RabbitMQ set `ce-type` to the bare `<resource>.<event>` key. EventBridge uses that bare key for `DetailType` and `Detail.type`, and its `source` identifies the hub rather than the producer. Pin `origin` and keep the subscription identity alongside the delivery when producer identity matters.
</Warning>

### 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_verification` → `active` → `degraded`, 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](/en/platform/streaming-hub/how-streaming-hub-works) for how the two interact.

## Creating a webhook subscription

***

A webhook subscription is the simplest to onboard. It needs no credential, only one probe:

1. **Create**: `POST /v1/subscriptions` with `sink_kind: "webhook"` and your `https://` endpoint. Send a unique `X-Idempotency` header: the hub rejects a create that omits it, before any write. The hub validates the destination URL against private, loopback, and cloud-metadata address ranges before any write. It never stores a private or metadata target. The response returns the new subscription in **`pending_verification`**, and the **signing secret exactly once**.
2. **Save the signing secret**: only this response shows it. The hub stores it only as ciphertext, and no read ever returns it. Save it on receipt. If you lose it, you can only rotate to a new one.
3. **Activate the destination**: `POST /v1/subscriptions/:id/ping` sends a synthetic, signed probe through the real delivery path and reports the classified outcome. A successful probe moves the subscription to `active`, and that is what makes it deliverable. Deploy your endpoint before you ping: it must answer `2xx`.

<Warning>
  The hub returns the signing secret only in the create response, and again on rotation. The hub never logs it, never stores it in plaintext, and never returns it from a `GET`. Capture it when you create the subscription.
</Warning>

After the probe succeeds, matching admits the webhook subscription and it starts to receive events. For the full walkthrough from create to a first confirmed delivery, see the [quick start](/en/platform/streaming-hub/streaming-hub-quick-start). See [Consuming events](/en/platform/streaming-hub/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 a probe verifies their destination. How you verify depends on the kind:

* **RabbitMQ**: supply a broker credential, verified on write (below).
* **SQS and EventBridge**: either supply an outbound credential, verified on write (below), or [wire an AWS delegated grant](#wiring-an-aws-delegated-grant) (next section) without storing a credential.

For any queue kind, the outbound-credential flow is three steps:

1. **Create**: `POST /v1/subscriptions` with the queue `sink_kind` and its endpoint, and **no** inline credential (the hub rejects an inline `sink_config` or `credential`). Send a unique `X-Idempotency` header, as for any create. The hub stores the subscription `pending_verification`. Matching excludes it, so it produces no delivery jobs yet.
2. **Supply the credential**: `PUT /v1/subscriptions/:id/credential` with the write-only outbound credential. The hub holds it in memory, **probes it immediately** (connect and authenticate against the destination), and persists it as ciphertext **only if the probe succeeds**. A failed probe stores nothing. The hub validates destination addresses against blocked ranges before storage. 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 no read path ever returns it, not even masked. To change it, `PUT` a new one. The same probe-on-write applies.

## Wiring an AWS delegated grant

***

For the credentialless AWS path (`sqs`, `eventbridge`), the hub delivers by assuming a role **in your AWS account**. You wire that trust instead of supplying an outbound credential:

1. **Fetch the setup artifacts**: `GET /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. That condition closes the confused-deputy gap. A trust policy that omits it fails verification, and never counts as verified.
3. **Register the grant**: `PUT /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. **Verify**: `POST /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, and the hub never stores one. 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. Your consumer can therefore 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 your verifier holds both secrets, it accepts a delivery if **either** signature validates.
3. After the overlap, retire the old secret.

A `pull` sink has no signing secret, so the hub rejects rotation. See [handling two signatures during rotation](/en/platform/streaming-hub/consuming-events) for the consumer-side verification.

## Recovering an auto-disabled subscription

***

When a destination stays broken long enough, the hub [auto-disables](/en/platform/streaming-hub/how-streaming-hub-works) 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. On a successful probe, it re-enables the subscription and clears the auto-disable mark, in place and with the same id and signing secret.

A failed re-probe changes nothing, so re-enable is always bound to a real, current probe success. Call `GET /v1/subscriptions/:id/health` for the delivery-health rollup (recent outcomes, dead-letter counts, and the auto-disable verdict). Use it 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.

The hub **rejects** any other field, an empty body, or a value below `1`. It never silently ignores them, so you can never believe a forbidden change took effect. The `event_types` and `origin` fields are immutable. Create a new subscription when either filter must change.

Changing the endpoint, sink kind, or secret is also out of scope for `PATCH` by design. 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:

```
X-Idempotency: <your-unique-key>
```

The hub rejects a mutation sent without it **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. That 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](/en/reference/retries-idempotency).

## Next steps

***

<CardGroup cols={2}>
  <Card title="Consuming events" icon="inbox" href="/en/platform/streaming-hub/consuming-events">
    Verify webhook signatures, deduplicate deliveries, and pull events.
  </Card>

  <Card title="How Streaming Hub works" icon="diagram-project" href="/en/platform/streaming-hub/how-streaming-hub-works">
    Matching, dispatch, retries, and auto-disable in detail.
  </Card>
</CardGroup>
