notifications / channels
Reusable account notification channels and each project's delivery routing, with secrets that never touch argv
Two command groups configure where alerts are delivered. channels manages the account's reusable delivery destinations — Slack, PagerDuty, Microsoft Teams, Discord, and Telegram — each with stored credentials and an id. notifications manages one project's routing: which channel ids it uses, a severity floor, an event-type allow-list, and whether email and SMS are on. Budget rules reference the same channel ids, so a channel is created once and used everywhere.
Both groups share one rule for credentials: a secret is never a command-line argument. Supply it on stdin with --secret-stdin, inside a JSON object with --config-stdin, or through the SOLWYN_CHANNEL_SECRET environment variable. Reads mask every stored secret to its final four characters, and any echo of a secret you supplied — in a success payload or an API error — is replaced with [REDACTED]. Unlike a webhook signing secret, a channel credential is never revealed, not even once.
Channels need a paid plan (Production and above). A Free-tier account gets the API's upgrade response on channels create, exit 1, with a link to the dashboard's billing page. See Alerts & notifications for the dashboard view of the same settings.
channels
Every channels subcommand requires a user login (JWT); a project key exits 3 before any request. create, update, test, and delete additionally require the account owner role — the API answers 403 and the CLI exits 3 with a hint to inspect your role with solwyn whoami.
| Contract | |
|---|---|
| Auth | JWT; owner for mutations |
| Exit codes | 0 success · 1 generic/API or delivery failure · 2 usage · 3 auth · 5 not found |
| JSON schemas | solwyn.channels.list.v1 · solwyn.channels.v1 · solwyn.channels.test.v1 · solwyn.channels.delete.v1 |
What each type needs
| Type | Credential fields | Required to create |
|---|---|---|
slack | webhook_url, bot_token, channel | webhook_url, or bot_token plus channel |
pagerduty | routing_key, api_token | routing_key |
teams | webhook_url | webhook_url |
discord | webhook_url | webhook_url |
telegram | bot_token, chat_id | bot_token plus chat_id |
The first field listed for each type is its primary secret — the one --secret-stdin and SOLWYN_CHANNEL_SECRET fill unless you name another with --secret-field. These requirements are checked locally, so a misconfigured create exits 2 without a network call.
channels list
$ solwyn channels list
ID Type Name
ch_one slack OpsThe table shows three columns; the JSON payload carries every channel's full configuration with secrets masked ("webhook_url":"****abcd").
channels show
$ solwyn channels show ch_one
Field Value
ID ch_one
Account acct_one
Type slack
Name Ops
slack.webhook_url ****abcd
slack.bot_token —
slack.channel —
Created 2026-09-01T12:00:00Z
Updated 2026-09-02T12:00:00ZSecret fields retain only their final four characters. The CLI masks defensively — even if the API returned a full value, the output would still show ****abcd.
channels create
solwyn channels create --type <type> --name <name> \
[--secret-stdin] [--secret-field <field>] [--channel <id>] [--chat-id <id>] | [--config-stdin]| Option | Type | Default | Purpose |
|---|---|---|---|
--type | slack | pagerduty | teams | discord | telegram | required | Channel type. Immutable after creation. |
--name | string | required | Display name; must not be blank. |
--secret-stdin | boolean | false | Read the primary secret (or the --secret-field) from stdin. Without it, SOLWYN_CHANNEL_SECRET is consulted. |
--secret-field | string | type's primary | Which secret field the stdin or environment value fills. Must name a secret field for the type. |
--channel | string | none | Slack destination id, for bot-token mode. |
--chat-id | string | none | Telegram destination id. |
--config-stdin | boolean | false | Read the whole type-specific configuration as a JSON object from stdin, secrets included. Cannot be combined with the four options above. |
Two ways to supply the credential:
$ printf '%s' "$SLACK_WEBHOOK_URL" | solwyn channels create --type slack --name Ops --secret-stdinFor Slack bot-token mode, name the field and the destination:
$ printf '%s' "$SLACK_BOT_TOKEN" | solwyn channels create \
--type slack --name Ops --secret-stdin --secret-field bot_token --channel C0123456789$ echo '{"routing_key":"'"$PD_KEY"'","api_token":"'"$PD_TOKEN"'"}' \
| solwyn channels create --type pagerduty --name On-call --config-stdinThe object may set any field of the type, including optional secrets. A key outside the type's field set, a non-string value, or a masked ****… value is a usage error.
stdin is read only when a flag opts in, so an interactive create never blocks waiting for input. A blank secret, or none at all when the type requires one, exits 2. The output is the same table as channels show.
channels update
solwyn channels update <channel_id> [--name <name>] [--secret-stdin] [--secret-field <field>] [--channel <id>] [--chat-id <id>] | [--config-stdin]Change the name, the credentials, or both. At least one is required. Omitted values stay unchanged, and the type cannot change — the CLI reads the existing channel first to learn its type, then writes.
- Passing a masked value (
****abcd) back keeps the stored credential. - With
--config-stdin, a JSONnullclears exactly that field:{"bot_token": null}. The API refuses a clear that would leave the channel unusable (for example, removing a Slack channel's only webhook URL) and the CLI relays that constraint with exit1. - Setting
SOLWYN_CHANNEL_SECRETcounts as a credential update even with no flags.
channels show
solwyn channels show chan_exampleRead one channel by ID with masked configuration, using solwyn.channels.v1. Any JWT role can read it; no credential is revealed.
channels test
Send a real test message through a stored channel using its stored credentials. No secret input is needed.
$ solwyn channels test ch_one
Field Value
Success true
Message Test sentA delivery the API reports as failed — success: false on an HTTP 200 — still exits 1 after printing the payload, so a script can gate on the exit code alone.
channels delete
Delete a channel. The API also removes its id from every project's notification settings and from every budget rule's channel_ids. Deletion displays routing effects on stderr and requires confirmation or --yes. Without a TTY and without --yes, it exits 2 before HTTP. Decline, EOF or Ctrl-C submits no deletion.
$ solwyn channels delete ch_one --yes
Field Value
Id ch_one
Deleted trueUse channels delete ID --yes only after reviewing its removal and routing effects. Channel deletion is CLI-only.
notifications
notifications manages one project's routing and requires a project; a missing one exits 2. show and set accept either credential — a project key or a login — though writes need a full-scope key or owner/admin JWT (project.write). notifications test email is the exception: it sends to your account address, so it requires a login.
| Contract | |
|---|---|
| Auth | any (full-scope key for writes); test email requires JWT |
| Exit codes | 0 success · 1 generic/API or delivery failure · 2 usage · 3 auth · 5 not found |
| JSON schemas | solwyn.notifications.v1 · solwyn.notifications.test.v1 |
notifications show
$ solwyn --project proj_0123456789abcdef01234567 notifications show
Field Value
Channels ch_one
Severity warning
Events all events
Email true
SMS false
Available channels email, sms, slack
SMS allowed events budget_exceeded
Email allowed events budget_thresholdThe last three rows report the account's tier capabilities — which channel kinds the plan unlocks and which event types email and SMS may carry — so you can see why a setting has no effect before you change it. The JSON payload adds the full (masked) channel objects under channels, alongside channel_ids, severity_threshold, enabled_event_types, email, sms_config, and capabilities.
notifications set
Change only the settings you name; everything else is preserved.
solwyn --project <project_id> notifications set [OPTIONS]| Option | Type | Purpose |
|---|---|---|
--channel | string, repeatable | Channel id. Repeating replaces the whole reference list. |
--clear-channels | boolean | Remove every channel reference. |
--severity | info | low | warning | medium | high | critical | Severity floor — events below it are not delivered. |
--event | string, repeatable | Event type to allow. Repeat for several; the API accepts future event names. |
--all-events | boolean | Allow every event type (clears the allow-list). |
--email / --no-email | boolean | Turn email delivery on or off. |
--sms / --no-sms | boolean | Turn SMS delivery on or off. SMS needs Production or above, a verified phone number, and consent. |
--reset-email | boolean | Reset email to its enabled default. |
--reset-sms | boolean | Reset SMS to its disabled default. |
A setting and its clear or reset option cannot be combined (--channel with --clear-channels, --event with --all-events, --email with --reset-email, --sms with --reset-sms); running set with no options at all is also a usage error. Both exit 2. The response is the same as notifications show.
$ solwyn --project proj_0123456789abcdef01234567 notifications set \
--channel ch_one --channel ch_two --severity high --event budget_exceedednotifications test
Test delivery for one channel kind, or check SMS eligibility.
solwyn --project <project_id> notifications test <slack|pagerduty|sms|email> [--secret-stdin]| Channel | Input | What happens |
|---|---|---|
slack | webhook URL via --secret-stdin or SOLWYN_CHANNEL_SECRET | Sends a real test message. |
pagerduty | routing key via --secret-stdin or SOLWYN_CHANNEL_SECRET | Sends a real test event. |
sms | none | Checks eligibility only — no message is sent. |
email | none; requires JWT | Sends a real email to the account's alert address. |
This command tests a credential you hold, before you store it. To test a channel that is already stored, use channels test <id>. Passing --secret-stdin with sms or email exits 2. As with channels test, a failed delivery reported on an HTTP 200 exits 1.
$ solwyn --project proj_0123456789abcdef01234567 notifications test sms
Field Value
Success true
Message SMS eligibility confirmed (no message sent).Related
- Alerts & notifications — the dashboard's channel setup, tier gating, and rule types
alerts— history of what fired and whether each channel received itbudget rule— rules route to these channel idsaccount alert-email— the account-wide email destinationwebhooks— signed HTTP delivery for machines