Managing Subscriptions
Once a subscription is created, you can list, inspect, update, pause, resume, and delete it through the wh sub CLI commands, the client.subscription SDK methods, or the MCP subscription tools. The HTTP API surface on this page is limited to action delivery observability — subscription management is available through the CLI, SDK, and MCP tools.
List Subscriptions
Section titled “List Subscriptions”Via CLI
Section titled “Via CLI”wh sub listOutput shows each subscription’s name, kind, event type, and active state. Use --org myorg to manage org-scoped subscriptions. For cross-repo subscriptions, a ← org/repo marker indicating the source repo being watched is shown when you have read access to that source repo:
Subscriptions: myorg/myrepo signal-hook [webhook/commit] active upstream-hook [webhook/commit] active ← myorg/otherrepo
Subscriptions: myorg repo-feed [webhook/org.repo_created] activeVia MCP
Section titled “Via MCP”{ "name": "warmhub_subscription_list", "arguments": { "orgName": "myorg", "repoName": "myrepo" }}View Subscription Details
Section titled “View Subscription Details”Via CLI
Section titled “Via CLI”wh sub view signal-hookReturns the subscription’s kind, eventType, active state, and webhook target. By default the read exposes only the origin — the webhookOrigin field (and fallbackWebhookOrigin if a fallback is set), rendered as <origin>/*** — never the full URL. Add --show-secrets to reveal the raw webhookUrl / fallbackWebhookUrl (that read is audit-logged).
Operation subscriptions also return an operationSubscription block. It records the immutable revision stored for the subscription — router state, not live delivery status. Legacy delivery remains in effect until the exact router for that revision is activated, so this block on its own does not tell you which delivery path is serving events right now. What it does let you confirm is which revision the subscription is pinned to:
effectiveSourceRepo— the repo whose events this revision watches. Omitted if you no longer have read authority for that source.revisionandlifecycle— which numbered revision is stored, and whether it isactive,paused, orretired.matcherVersionandmatcherDigest— the event-matching definition that revision is pinned to.credentialGeneration— which generation of the subscription’s credentials the revision carries. Worth checking after a credential rotation.deliveryPolicy— the revision’sallowTraceReentrysetting, plus anotifyOnSuccessvalue that is accepted for compatibility only and has no effect (see Notifications).retirement— present only on a retired revision, carrying when it was retired and the required reason.
The default wh sub view output prints the effective source, the revision and lifecycle, the matcher, the credential generation, and any retirement — but not deliveryPolicy. Read it with --json or --format jsonl if you need that field. Fallback delivery targets are not part of this block either: they appear as the top-level fallbackWebhookOrigin and fallbackWebhookUrl fields, which the CLI prints on separate lines after it. Metadata events and legacy cron subscriptions have no operationSubscription block at all.
For cross-repo subscriptions, a source repo: field is shown when you have read access to the source repo, indicating which repo’s events trigger the subscription:
upstream-hook kind: webhook active: true source repo: myorg/otherrepo webhookOrigin: https://example.com/***Via MCP
Section titled “Via MCP”{ "name": "warmhub_subscription_get", "arguments": { "orgName": "myorg", "repoName": "myrepo", "name": "signal-hook" }}Pause and Resume
Section titled “Pause and Resume”Pausing a subscription stops all new deliveries. Existing in-flight deliveries complete normally.
Legacy cron subscriptions may be paused but cannot be resumed — attempting to resume a legacy cron subscription returns an error. Contact support for migration options if you need the subscription to remain active.
Via CLI
Section titled “Via CLI”# Pausewh sub pause signal-hook
# Resumewh sub resume signal-hookVia MCP
Section titled “Via MCP”{ "name": "warmhub_subscription_pause", "arguments": { "orgName": "myorg", "repoName": "myrepo", "name": "signal-hook" }}Update Subscription
Section titled “Update Subscription”Use update when the subscription should keep the same name and lifecycle state, but its configuration needs to change. Provided fields patch the existing subscription.
What can be patched depends on the subscription’s event type:
commitsubscriptions (same-repo) accept trigger flags (--on,--filter) as well as delivery configuration (--webhook-url).- Cross-repo
commitsubscriptions can only patch delivery configuration (--webhook-url). Passing trigger arguments to a cross-repo commit subscription returns aVALIDATION_ERROR:- CLI:
--onor--filter - MCP:
shapeNameorfilterJson
- CLI:
- Metadata subscriptions (e.g.
org.repo_created,org.renamed) keep a fixed event type and can only patch delivery configuration such as--webhook-url. Passing trigger arguments to a metadata subscription returns an error:- CLI:
--onor--filter - MCP:
shapeNameorfilterJson
- CLI:
Legacy cron subscriptions cannot be updated. If wh sub update returns an error on a subscription that appears in wh sub list with kind: cron, contact support for migration options.
Via CLI
Section titled “Via CLI”wh sub update signal-hook \ --on Signal \ --filter '{"shape":"Signal","operation":"add"}' \ --webhook-url https://example.com/hookVia MCP
Section titled “Via MCP”{ "name": "warmhub_subscription_update", "arguments": { "orgName": "myorg", "repoName": "myrepo", "name": "signal-hook", "shapeName": "Signal", "filterJson": { "shape": "Signal", "operation": "add" }, "webhookUrl": "https://example.com/hook" }}Lifecycle changes still go through pause and resume, and credential bindings remain managed via the bind/unbind commands.
Remove or retire
Section titled “Remove or retire”wh sub delete and the corresponding SDK/MCP operation permanently stop future deliveries for an operation subscription. Its history stays visible and its name cannot be reused. Metadata subscriptions and legacy cron subscriptions are removed entirely instead.
You cannot replace a subscription’s effective source repository in place. To watch a different source, delete the old subscription and create a new subscription with a new name.
Via CLI
Section titled “Via CLI”wh sub delete signal-hookVia MCP
Section titled “Via MCP”{ "name": "warmhub_subscription_delete", "arguments": { "orgName": "myorg", "repoName": "myrepo", "name": "signal-hook" }}Delivery Feed
Section titled “Delivery Feed”The delivery feed shows the history of action deliveries for a subscription — what was dispatched, when, and what happened.
Via CLI
Section titled “Via CLI”wh sub log signal-hookOutput shows each delivery’s run status, attempt count, timing, source label, and matched operation indexes. Deliveries that have a run attached show the run id and attempt count (e.g. 1/1). Deliveries that have not yet started a run — such as lease_wait deliveries still queued — show only a delivery id with no attempt counters. Failed runs display the error code and message, and include the response snippet when one is available:
Subscription: signal-hook succeeded 1/1 2m ago run 019d90f0-0000-7000-8000-000000000000 write ops[0,1] [0] add thing Sensor/temp-1 [1] add assertion Reading/temp-1-v1 dead_letter 3/5 15m ago run 019d90e0-0000-7000-8000-000000000000 write ops[0] HTTP_502: Webhook responded 502 [0] revise thing Sensor/temp-1 lease_wait 1m ago delivery 019d90d0-0000-7000-8000-000000000000 write ops[0] [0] add thing Sensor/temp-2When a delivery has no run yet, the feed prints delivery <id> in place of a run id and omits the attempt counters entirely. Once a run is created, subsequent output for that delivery switches to run <id> and includes the attempt count.
Follow deliveries in real time with --live:
wh sub log signal-hook --liveThis polls the delivery feed periodically and refreshes as new deliveries arrive.
Via MCP
Section titled “Via MCP”{ "name": "warmhub_action_livefeed", "arguments": { "orgName": "myorg", "repoName": "myrepo", "subscriptionName": "signal-hook", "limit": 20 }}The limit parameter controls how many deliveries to return (1–500, default 50). Pagination is supported via the cursor field in the response — when supplying cursor on a follow-up call, you must also pass an explicit limit (the tool rejects cursor alone with "cursor" requires "limit"). See warmhub_action_livefeed for the full parameter contract.
Via HTTP API
Section titled “Via HTTP API”The HTTP API exposes the underlying run list, optionally filtered by subscriptionName. The endpoint requires repo:configure — anonymous calls return an opaque 404:
curl -H "Authorization: Bearer $WH_TOKEN" \ "https://api.warmhub.ai/api/repos/myorg/myrepo/actions/runs?subscriptionName=signal-hook&status=failed_terminal&limit=20"This returns the run records themselves. For the per-delivery feed (matched-operation context, attempt diagnostics, live updates), use the CLI (wh sub log) or MCP (warmhub_action_livefeed) — both surfaces apply the same scoped-reader access rules described in the Via CLI and Via MCP sections above. See HTTP API → Actions for the full parameter list.
Run Attempts
Section titled “Run Attempts”Each delivery can have multiple attempts if retries are needed. To inspect the attempt history for a specific run:
Via CLI
Section titled “Via CLI”wh sub attempts 019d90f0-1111-7000-8000-000000000001The argument is the run ID (UUIDv7). When a delivery shows only a delivery id in wh sub log (e.g. a lease_wait delivery with no run yet), no attempt history is available until a run is created. Output shows each attempt’s status, duration, HTTP status, and error details:
Attempts: run 019d90f0-1111-7000-8000-000000000001 #1 failed (300ms) HTTP 502 HTTP_502: Webhook responded 502 #2 failed (450ms) HTTP 502 HTTP_502: Webhook responded 502 #3 failed (280ms) HTTP 502 HTTP_502: Webhook responded 502Via MCP
Section titled “Via MCP”{ "name": "warmhub_action_attempts", "arguments": { "orgName": "myorg", "repoName": "myrepo", "runId": "019d90f0-1111-7000-8000-000000000001" }}Each attempt records:
| Field | Description |
|---|---|
attempt | Attempt number (1-based) |
status | started, succeeded, failed, or suppressed |
startedAt | Timestamp when the attempt began |
finishedAt | Timestamp when the attempt completed (if finished) |
httpStatus | HTTP response status |
errorCode | Error classification code |
errorMessage | Human-readable error description |
A suppressed status indicates the delivery was intentionally skipped — WarmHub recorded it as skipped and did not call the webhook target.
Run Statuses and Error Codes
Section titled “Run Statuses and Error Codes”The status a run carries (succeeded, retry_wait, dead_letter, …) and the error code on a failed attempt (WEBHOOK_NETWORK_ERROR, HTTP_429, …) follow the same rules for every action delivery. They are documented once — with the retry, backoff, and fallback behavior — in Action Delivery Lifecycle.
Notifications
Section titled “Notifications”WarmHub records repo-scoped action notifications only for terminal failures. A notification is held until no retry or fallback can still change the outcome, as covered in Action Delivery Lifecycle → Terminal Failure and Fallback. Successful and suppressed runs do not produce notification records.
The legacy notifyOnSuccess subscription field is accepted for compatibility but has no effect.
wh notifications --repo and the MCP notification tools return these repo-scoped records for operators. Separately, the web app may aggregate related user-facing inbox entries; those inbox entries are not the same API surface as repo-scoped delivery notifications.
Via CLI
Section titled “Via CLI”wh notifications --repo myorg/myrepoFilter by time with either epoch milliseconds or an ISO timestamp:
wh notifications --repo myorg/myrepo --since 2026-03-30T12:00:00ZVia MCP
Section titled “Via MCP”{ "name": "warmhub_action_notifications", "arguments": { "orgName": "myorg", "repoName": "myrepo", "limit": 20 }}The MCP tool is also repo-scoped and returns delivery notification records rather than the web app’s user feed.
Via HTTP API
Section titled “Via HTTP API”The endpoint requires repo:configure:
curl -H "Authorization: Bearer $WH_TOKEN" \ "https://api.warmhub.ai/api/repos/myorg/myrepo/actions/notifications"Debugging a Failing Subscription
Section titled “Debugging a Failing Subscription”When a subscription stops delivering, work through these checks in order. Each one narrows the cause before the next.
1. Is the subscription paused?
wh sub view signal-hookA paused subscription shows active: false and delivers nothing. Resume it with wh sub resume signal-hook. Note: if the subscription has kind: cron, resume will fail — contact support for migration options.
2. What do recent deliveries look like?
wh sub log signal-hookThe feed shows each run’s status, attempt count, and — for failures — the error code and message. Note the run ID of a failing delivery, then inspect its attempt history:
wh sub attempts <run-id>3. What’s the status-code pattern?
Match the error code against the Error Codes table. The split that matters is retryable vs. not: a run of HTTP_502 attempts ending in dead_letter is a flapping target that recovered too late, while a single HTTP_400 straight to failed_terminal is a payload your handler rejected outright — retrying won’t help.
4. Are the right credentials bound?
wh sub view --json reports the bound credential sets as credentialSetNames. Take a name from that list and inspect its key names — substituting the name you saw for webhook-keys below:
wh credential view webhook-keysAn auth failure at the target (HTTP_401 / HTTP_403) usually means a missing or wrong binding — see Credentials & Signatures.
5. Test the URL directly.
Reveal the full target, then POST a sample webhook payload to it yourself, bypassing WarmHub:
wh sub view signal-hook --show-secrets
curl -X POST https://example.com/hook \ -H 'Content-Type: application/json' \ -d '{"event":"warmhub.write","runId":"<run-id>","matchedOperations":[]}'If the manual POST also fails, the problem is at the target, not in WarmHub’s delivery. When your handler runs locally, expose it with a tunnel (such as ngrok or cloudflared) and point a test subscription at the public URL.
Worked example
Section titled “Worked example”A signal-hook subscription stopped firing. wh sub log signal-hook shows the latest run at dead_letter 5/5 with HTTP_503. The repeated HTTP_503 across all five attempts — retryable, but exhausted — points at the target, not the filter or the credentials. wh sub view signal-hook confirms active: true, and a manual POST to the revealed URL also returns 503: the receiving service is down. Once it recovers, new matching writes deliver again — the dead-lettered run does not replay automatically.