Usage

Hosted & self-host

Provisioning is paperwork, not behavior. channels.add mints a connection. Always pass via="hosted" or via="self-host". A Caspian API key is only for hosted. The same handlers run in both modes.

Add a channel

cx = Caspian(api_key=KEY)
cx.channels.add("telegram", via="hosted", bot_token=TG)

cx = Caspian()   # no Caspian key
cx.channels.add(
    "telegram",
    via="self-host",
    bot_token=TG,
    webhook_url="https://myapp.example.com/hook",
    webhook_secret=SECRET,   # optional; generated if omitted
)

cx.channels.add("telegram", via="self-host", bot_token=TG, inbound=False)
# send-only: no platform webhook
const cx = new Caspian()
await cx.channels.add("telegram", { via: "hosted", bot_token: TG })
await cx.run({ apiKey: KEY })

await cx.channels.add("telegram", {
  via: "self-host",
  bot_token: TG,
  webhook_url: "https://myapp.example.com/hook",
  webhook_secret: SECRET,
})

await cx.channels.add("telegram", { via: "self-host", bot_token: TG, inbound: false })
// send-only: no platform webhook

Self-host without the required channel secret is an error (BotFather token, Slack tokens, …). That is not a Caspian API key. A local adapter is required only for via="self-host". Hosted-only names the gateway speaks (e.g. Bluesky) have no local adapter — inbound arrives as a gateway event.

Inbound verbs

VerbWhen
cx.handle("telegram", body, headers)Self-host HTTP. Signatures are checked. channel must have been added with via="self-host".
cx.poll("telegram")Self-host long-poll (getUpdates). No public URL. Token authenticates the fetch; webhook signatures are not checked. Poll and webhook cannot both be active.
cx.listen("discord")Self-host held-open socket. Today: Discord (only inbound path) and Slack Socket Mode. Needs caspian-sdk[discord] or caspian-sdk[slack-socket].
cx.handle("gateway", body, headers)Hosted push. Body is a core Event envelope, HMAC-SHA256 (X-Caspian-Signature).
cx.run()Hosted poll: GET /v1/events then handle("gateway", …). Requires Caspian(api_key=…).

If inbound is owned by the gateway, handle("telegram", …) / listen / poll return a ProvisionError telling you to use run() or handle("gateway", …).

Webhook channels share one HTTP front

Repo examples use a small HTTP helper: POST → cx.handle(channel, body, headers), plus Meta GET challenge, X CRC, or TwiML response when the adapter needs it. The platform is acknowledged with HTTP 200 before the model; a wrong secret is 401.

Socket channels do not use handle()

Discord guild messages have no HTTP webhook in this SDK. Slack Events url_verification currently parses to [] with no HTTP echo — the Slack example uses listen("slack"), not handle("slack").