# Delivery channels

The same subscription can deliver to an HTTPS endpoint, a Discord channel, the Tickerbot mobile app, or stay in the dashboard. Pick a channel at subscribe time with `channel`.

## The channels

- `webhook` — POST a signed `webhook.fired` JSON payload to your `target_url`. The default when you pass a `target_url`.
- `discord` — post a formatted embed to a Discord channel via an incoming-webhook URL. No HMAC — the URL *is* the credential.
- `in_app` — record fires in the dashboard's deliveries timeline only. No outbound request. The default when you pass neither a `target_url` nor a `discord_url`.
- `mobile_push` — push a notification to a phone signed in to the Tickerbot mobile app. Requires a `device_id` from `POST /v2/devices/register`.
- `slack` — *reserved.* Subscribing with this channel returns `501 not_implemented` today.

If you omit `channel`, it's inferred: `webhook` when `target_url` is set, `discord` when `discord_url` is set, otherwise `in_app`. The channel is fixed once the subscription exists — to switch, delete and re-subscribe.

## Discord

In Discord, open **Server Settings → Integrations → Webhooks → New Webhook**, pick a channel, and **Copy Webhook URL**. Pass it as `discord_url` with `channel: "discord"` on any subscribe endpoint:

```shell
curl -X POST "https://api.tickerbot.io/v2/scan/subscribe" \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "q": "above_sma_200 AND market_cap > 1e10",
    "channel": "discord",
    "discord_url": "https://discord.com/api/webhooks/123456789012345678/aBcDeF…",
    "cadence": "1m"
  }'
```

Each fire posts a single embed: the subscription name, the composed query, and up to the first dozen matching tickers with price and day change, plus a "+N more" line. Discord embed limits apply — long match sets are truncated, not split.

## The Discord URL is a credential

Anyone with the incoming-webhook URL can post to your channel, so we store it as a posting credential and **strip it from every read**. The create response echoes it back once; `GET /v2/webhooks/{id}` and list omit it and set `channel_config_present: true` so tooling can still tell a channel is configured. Delivery rows mask it the same way. To rotate it, delete and re-subscribe with the new URL.

## Retries & failures

A `2xx` (Discord returns `204`) is a success. A `429` is retried, honoring Discord's `Retry-After`. Other `4xx` responses (e.g. a deleted webhook → `404`) are `permanent_failure` — no retry. `5xx` and network errors retry on the standard ladder (30s → 2m → 10m → 1h → 6h), and five consecutive failures auto-disable the subscription, exactly like an HTTPS webhook.

## Validate it

`POST /v2/webhooks/{id}/test` posts a real-shape fire to the configured channel right now — for Discord, a real embed with a `[TEST]` title prefix — and returns the inline outcome. It's one-shot: failures don't retry or count toward auto-disable.
