İçeriğe geç

Events

Bu içerik henüz dilinizde mevcut değil.

The Events API sends a signed HTTPS POST to the events.url of your manifest whenever something you subscribed to happens. There is no relay operated by Ketvia: your endpoint must be reachable from the internet (use a tunnel while developing).

"events": {
"url": "https://kit.acme.example/ketvia/events",
"subscribe": ["message.created", "kit.mentioned", "reaction.added"]
}

Every subscription needs the matching scope in the manifest, so a Kit receives nothing it could not read through the Web API.

Event Scope Notes
message.created, message.updated, message.deleted messages:read Only channels the Kit bot was added to. Your own bot’s messages are never sent to you. A deleted message carries no content.
kit.mentioned none The Kit bot was mentioned. Ships with the text, so messages:read is not needed.
reaction.added, reaction.removed reactions:read { messageId, userId, reaction }.
conversation.member_joined, conversation.member_left conversations:read { userId }.
conversation.created, .renamed, .archived conversations:read Public channels and channels the bot is in.
file.shared files:read Metadata only; fetch the content with your token.
kit.installed, kit.scopes_changed none Always deliverable.
kit.tokens_revoked, kit.uninstalled none Sent once, also after the installation ended, signed with the last signing secret.
approval.decided assistant:tools { approvalId, tool, decision, finalState }. Never the diff.

Private content is never an event. Assistant DMs, private results, private follow-ups and other members’ private state are not delivered. A message published by an assistant run is announced with text: null and blocks: null.

{
"eventId": "evt_01J9Z6Q4X8T3V5N7K2M4P6R8S0",
"type": "message.created",
"installationId": "0b4f…",
"workspaceId": "7c1e…",
"occurredAt": "2026-10-04T09:12:33.120Z",
"sequence": 18342,
"conversationId": "d3a9…",
"data": {
"messageId": "e1c2…",
"author": { "kind": "user", "id": "a7f0…" },
"text": "Deploy is green",
"blocks": null,
"threadParentId": null,
"createdAt": "2026-10-04T09:12:33.120Z",
"editedAt": null
}
}
  • eventId is stable across retries: dedupe on it.
  • sequence is monotonic per installation. Delivery is not ordered; sort by sequence if you care.
  • The TypeScript types (KitEvent, narrowed by type) are in @ketvia/kit.

Every request carries:

  • Ketvia-Request-Timestamp: unix seconds.
  • Ketvia-Signature: v1=<hex HMAC-SHA256(secret, "v1:" + timestamp + ":" + rawBody)>. During a secret rotation the header carries two comma-separated v1= values.
  • Ketvia-Installation-Id: the installation. Derive tenant identity from it, never from the payload alone.
  • On retries: Ketvia-Retry-Num (the attempt, 2 or more) and Ketvia-Retry-Reason (http_error, timeout, network…).

Reject a request whose timestamp is more than 300 seconds from your clock. The SDK does all of this for you.

A private Kit’s owner creates and rotates the signing secret (kss_…) as a workspace admin:

Terminal window
curl -s -X POST "https://app.ketvia.com/v1/workspaces/$WORKSPACE/kits/private/$KIT_ID/signing-secret" \
-b "$SESSION_COOKIE" -H 'Origin: https://app.ketvia.com' -H 'X-Ketvia-Csrf: 1'

The secret is shown once in the response. Ketvia stores only an AES-256-GCM envelope. A rotation keeps the previous secret signing for 7 days (two v1= values), so you can switch without downtime: deploy a server that accepts both (signingSecret: [newSecret, oldSecret]), then drop the old one.

POST …/kits/installations/{id}/events/verify (admins) sends {"type":"url_verification","challenge":"…"} and expects the challenge echoed within 3 seconds. The SDK answers it automatically.

  • At least once. An event is written in the same database transaction as the change that caused it, so no event exists without its commit and no commit loses its event. If a worker crashes after sending, the delivery is claimed again and sent again with the same eventId.
  • Timeout 3 s. Any 2xx is success; the body is ignored.
  • Retries at 10 s, 1 min, 5 min, 30 min, 2 h, 6 h and 12 h (8 attempts in about 21 hours), then the delivery is dead.
  • 410 Gone disables the subscription at once. Answer 410 only when you want Ketvia to stop.
  • Concurrency: at most 10 in-flight deliveries per installation and 50 per Kit.
  • Circuit breaker. If more than 95% of the last 60 minutes’ attempts failed (and there were at least 100), the subscription is paused: no new events are queued for it. Fix your endpoint, then resume (POST …/events/resume, which re-runs the verification). Events that happened while paused are not replayed; read the Web API to catch up.
  • Retention: delivery payloads and the attempt log are kept for 7 days after a delivery finished. The delivery log (GET …/events) shows state, attempt count and the last status or error code, never payloads.
  • Deleted messages keep no content anywhere: earlier payloads that carried the message are redacted and unsent ones are dropped; the message.deleted event follows.
import { createServer } from 'node:http';
import { createKitServer, toNodeListener } from '@ketvia/kit';
const kit = createKitServer({
signingSecret: [process.env.KETVIA_SIGNING_SECRET ?? ''],
onEvent: async (event) => {
if (event.type === 'kit.mentioned') await reply(event.conversationId, event.data.text);
},
});
createServer(toNodeListener(kit)).listen(3000);

Return quickly (3 seconds) and do slow work in the background; a thrown error answers 500 and Ketvia retries.