Integrations & webhooks
Integrations let external software participate in a workspace: they receive signed webhook
events, post chat messages through a bot API, and handle slash commands with deferred
responses. An integration is a synthetic, non-human actor scoped to one workspace — it authenticates
with its own knbi_… token, never a user credential, and its posts render in chat as a bot with an
APP badge.
Integration tokens are the machine-to-machine story; a personal
API key (knak_…) acts as you instead.
Managing integrations (admin)
Section titled “Managing integrations (admin)”Management routes are workspace-scoped and require an owner or admin; they accept a session JWT or API key. The UI equivalent lives in Settings → Integrations — see the admin guide.
| Endpoint | Description |
|---|---|
POST /v1/workspaces/{wid}/integrations |
Create → 201 {integration, token} — the knbi_… token appears here and never again |
GET /v1/workspaces/{wid}/integrations |
List (no hashes or private keys) |
GET /v1/workspaces/{wid}/integrations/{iid} |
One integration |
PATCH /v1/workspaces/{wid}/integrations/{iid} |
Update name/description/webhook URL/subscriptions/commands, or enable/disable |
DELETE /v1/workspaces/{wid}/integrations/{iid} |
Delete |
POST /v1/workspaces/{wid}/integrations/{iid}/rotate |
Rotate the token — returned once; the old token dies immediately |
POST /v1/workspaces/{wid}/integrations/{iid}/ping |
Send a signed ping envelope to the webhook URL (visible test) |
GET /v1/workspaces/{wid}/integrations/{iid}/deliveries |
Delivery ledger, newest first — every attempt with status code and timing |
POST /v1/workspaces/{wid}/integrations/{iid}/deliveries/{did}/redeliver |
Re-send a delivery: a fresh attempt reusing the original signed envelope |
An integration carries scopes (chat:read, chat:write, commands), a webhookUrl,
eventSubscriptions, and optional slash commands ({name, description, usageHint}).
Webhook events
Section titled “Webhook events”Every event, ping, and command uses one JSON envelope, POSTed to your webhookUrl:
{ "id": "evt_7c2a…", "type": "chat.message.created", "workspaceId": "w_5d81…", "timestamp": "2026-07-16T12:00:00Z", "payload": { "channelId": "c_…", "messageId": "m_…", "fromUid": "u_…", "text": "…" }}Event catalog (chat, v1): ping, chat.message.created, chat.channel.created, and
command.invoked. Payloads carry full content so you never need a read-back call, and an
integration never receives events for its own bot’s actions.
Verifying signatures
Section titled “Verifying signatures”Each delivery is signed with the integration’s Ed25519 key:
| Header | Value |
|---|---|
X-Knobs-Signature |
ed25519=<base64 sig> over the string "v1:" + timestamp + ":" + rawBody |
X-Knobs-Timestamp |
RFC 3339 timestamp bound into the signature — reject stale ones |
X-Knobs-Key-Id |
which key signed (kid) |
X-Knobs-Event-Id |
stable across retries — use it for idempotency |
Fetch the public keyset (unauthenticated) and verify the detached signature:
| Endpoint | Description |
|---|---|
GET /v1/integrations/{iid}/keys |
Public Ed25519 keyset by kid — no auth required |
Delivery and retries
Section titled “Delivery and retries”Failed deliveries retry on a backoff of 1m → 5m → 30m → 2h (5 attempts total), then the ledger
row is marked exhausted — never silently dropped. Admins can inspect every attempt in the
delivery log and redeliver with a fresh retry budget; a redelivery reuses the original envelope
byte-for-byte, so X-Knobs-Event-Id idempotency still holds.
Bot API
Section titled “Bot API”Bot routes authenticate with the integration token (Authorization: Bearer knbi_…), are scoped to
the integration’s own workspace, and gate on scopes. Every response carries X-RateLimit-Remaining
and X-RateLimit-Reset; the budget is 60 requests/minute per integration and 1 message/second per
channel.
| Endpoint | Scope | Description |
|---|---|---|
GET /v1/bot/self |
— | The integration’s identity and granted scopes |
GET /v1/bot/channels |
chat:read |
List channels in the workspace |
POST /v1/bot/channels/{cid}/messages |
chat:write |
Post as the bot; supports an Idempotency-Key header — a replay returns the original message with 200 |
POST /v1/bot/interactions/{iid}/respond |
commands |
Deferred slash-command reply (15-minute window) |
curl -X POST https://api.knobs.io/v1/bot/channels/{cid}/messages \ -H "Authorization: Bearer knbi_..." \ -H "Idempotency-Key: 7f3a2b1c-…" \ -H "Content-Type: application/json" \ -d '{"text": "Deploy finished ✅"}'Slash commands
Section titled “Slash commands”There is no 3-second ack cliff — commands are async by design:
- A member runs
/yourcommand some textin chat. The channel-scoped invoke route is documented with the rest of chat: see Chat. - Your integration receives a
command.invokedenvelope containing aninteractionIdand the actor/channel context. - Respond whenever ready — up to 15 minutes later — via
POST /v1/bot/interactions/{iid}/respondwith{"text": "…"}. The reply posts to the channel as your bot. After expiry the endpoint returns410and the interaction is markedexpired.
Related
Section titled “Related”- Admin: Integrations — the Settings UI for everything above.
- API keys — personal tokens that act as a user, not a bot.
- Chat — the member-facing chat surface, including the command invoke route.