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"]}Event types
Section titled “Event types”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.
The envelope
Section titled “The envelope”{ "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 }}eventIdis stable across retries: dedupe on it.sequenceis monotonic per installation. Delivery is not ordered; sort bysequenceif you care.- The TypeScript types (
KitEvent, narrowed bytype) are in@ketvia/kit.
Signatures
Section titled “Signatures”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-separatedv1=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) andKetvia-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.
The signing secret
Section titled “The signing secret”A private Kit’s owner creates and rotates the signing secret (kss_…) as a workspace admin:
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.
Verify the URL
Section titled “Verify the URL”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.
Delivery guarantees
Section titled “Delivery guarantees”- 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
2xxis 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 Gonedisables the subscription at once. Answer410only 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.deletedevent follows.
Handling events
Section titled “Handling events”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.