Skip to content

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.

Terminal window
wh sub list

Output 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/otherrepo
{
"name": "warmhub_subscription_list",
"arguments": {
"orgName": "myorg",
"repoName": "myrepo"
}
}
Terminal window
wh sub view signal-hook

Returns 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/***
{
"name": "warmhub_subscription_get",
"arguments": {
"orgName": "myorg",
"repoName": "myrepo",
"name": "signal-hook"
}
}

Pausing a subscription stops all new deliveries. Existing in-flight deliveries complete normally.

Terminal window
# Pause
wh sub pause signal-hook
# Resume
wh sub resume signal-hook
{
"name": "warmhub_subscription_pause",
"arguments": {
"orgName": "myorg",
"repoName": "myrepo",
"name": "signal-hook"
}
}

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.

Terminal window
wh sub update signal-hook \
--on Signal \
--filter '{"shape":"Signal","operation":"add"}' \
--webhook-url https://example.com/hook
{
"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.

Deleting a subscription permanently removes it along with its credential bindings.

Terminal window
wh sub delete signal-hook
{
"name": "warmhub_subscription_delete",
"arguments": {
"orgName": "myorg",
"repoName": "myrepo",
"name": "signal-hook"
}
}

The delivery feed shows the history of action deliveries for a subscription — what was dispatched, when, and what happened.

Terminal window
wh sub log signal-hook

Output 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-1

Follow deliveries in real time with --live:

Terminal window
wh sub log signal-hook --live

This polls the delivery feed periodically and refreshes as new deliveries arrive.

{
"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.

The HTTP API exposes the underlying run list, optionally filtered by subscriptionName. The endpoint requires repo:configure — anonymous calls return an opaque 404:

Terminal window
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.

Each delivery can have multiple attempts if retries are needed. To inspect the attempt history for a specific run:

Terminal window
wh sub attempts 019d90f0-1111-7000-8000-000000000001

The 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 502
{
"name": "warmhub_action_attempts",
"arguments": {
"orgName": "myorg",
"repoName": "myrepo",
"runId": "019d90f0-1111-7000-8000-000000000001"
}
}

Each attempt records:

FieldDescription
attemptAttempt number (1-based)
statusstarted, succeeded, or failed
startedAtTimestamp when the attempt began
finishedAtTimestamp when the attempt completed (if finished)
httpStatusHTTP response status
errorCodeError classification code
errorMessageHuman-readable error description

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.

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.

Terminal window
wh notifications --repo myorg/myrepo

Filter by time with either epoch milliseconds or an ISO timestamp:

Terminal window
wh notifications --repo myorg/myrepo --since 2026-03-30T12:00:00Z
{
"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.

The endpoint requires repo:configure:

Terminal window
curl -H "Authorization: Bearer $WH_TOKEN" \
"https://api.warmhub.ai/api/repos/myorg/myrepo/actions/notifications"

When a subscription stops delivering, work through these checks in order. Each one narrows the cause before the next.

1. Is the subscription paused?

Terminal window
wh sub view signal-hook

A paused subscription shows active: false and delivers nothing. Resume it with wh sub resume signal-hook.

2. What do recent deliveries look like?

Terminal window
wh sub log signal-hook

The 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:

Terminal window
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:

Terminal window
wh credential view webhook-keys

An 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:

Terminal window
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.

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.