Managing Subscriptions
Once a subscription is created, you can list, inspect, update, pause, resume, and delete it through the wh sub CLI commands or the MCP subscription tools. Subscription management REST endpoints are not currently mounted; the HTTP API surface below is limited to action delivery observability.
List Subscriptions
Section titled “List Subscriptions”Via CLI
Section titled “Via CLI”wh sub listOutput shows each subscription’s name, kind, and active state. 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] active upstream-hook [webhook] active ← myorg/otherrepoVia 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, 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).
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.
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 trigger or webhook configuration needs to change. Provided fields patch the existing subscription.
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.
Delete
Section titled “Delete”Deleting a subscription permanently removes it along with its credential bindings.
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. Failed runs display the error code and message:
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-1Follow 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 richer per-delivery feed (matched-operation context, attempt diagnostics, live updates), use the CLI (wh sub log) or MCP (warmhub_action_livefeed). 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) shown in wh sub log and wh sub attempts JSON output. 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, or failed |
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 |
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 for the delivery outcomes you can act on: terminal failures, and successes when the subscription opts in. Which outcomes record a notification — and when one is held until a delivery’s failure is final — is covered in Action Delivery Lifecycle → Terminal Failure and Fallback.
Enable success notifications with notifyOnSuccess through the SDK client.subscription.create() / update() surface or the warmhub_subscription_create MCP tool. The CLI and the warmhub_subscription_update MCP tool do not expose a setter for it.
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.
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.
Hit a problem or have a question? Get in touch.