Events
Today: Tracker webhook deliveries
Section titled “Today: Tracker webhook deliveries”Skribly Tracker sends four events as signed webhooks. Their payloads are defined once in @skribly/core, as WebhookDeliverySchema, a union keyed by event:
type WebhookDelivery = | { id: string; event: 'issue.created'; sent_at: string; data: { issue: Issue } } | { id: string; event: 'issue.updated'; sent_at: string; data: { issue: Issue } } | { id: string; event: 'issue.deleted'; sent_at: string; data: { issue: { key: string; title: string }; actor: string | null } } | { id: string; event: 'comment.created'; sent_at: string; data: { issue: Issue; comment: { id: number; author: string | null; body: string } } }id is a UUID per delivery and sent_at an ISO 8601 timestamp with an offset.
Building and verifying deliveries
Section titled “Building and verifying deliveries”import { buildWebhookRequest, parseWebhookBody, verifyWebhookRequest } from '@skribly/core/webhooks'
const { body, headers } = buildWebhookRequest({ event: 'issue.updated', data: { issue }, secret, timeZone: 'Europe/Warsaw' })await fetch(hook.url, { method: 'POST', headers, body })
if (verifyWebhookRequest(rawBody, request.headers, secret)) { const delivery = parseWebhookBody(rawBody)}| Export | What it does |
|---|---|
buildWebhookRequest |
The body, byte for byte as the released Tracker encodes it, and the headers: X-Skribly-Event, X-Skribly-Signature and the older X-Skryba-* pair |
signWebhookBody |
sha256=<hex HMAC-SHA256 of the raw body> |
verifyWebhookSignature, verifyWebhookRequest |
Constant-time check of a body against a signature or a request’s headers |
webhookEventOf |
The event name from either header |
parseWebhookBody |
Parses and validates a delivery |
WEBHOOK_DELIVERY_POLICY |
3 tries, a 10 and 60 second backoff, and a 5 second timeout |
Verify against the raw body, never a re-serialised object. Queueing the deliveries belongs to the app that sends them.
The shared event envelope
Section titled “The shared event envelope”@skribly/core 0.2.0 defines one envelope for every event in the ecosystem, in the CloudEvents 1.0 format, with dot-delimited types named <app>.<noun>.<verb>.
import { createEvent, parseEvent } from '@skribly/core/events'
const event = createEvent({ type: 'agent.session.finished', source: 'https://agent.example.com', subject: 'session-42', data: { sessionId: 'session-42', outcome: 'completed', durationSeconds: 310, finishedAt: new Date().toISOString() }})Every event has specversion, id, source, type, time, an optional subject and data. createEvent fills in the id and an ISO 8601 time with an offset, and checks the data against the catalogue:
| Type | Data |
|---|---|
tracker.issue.created, tracker.issue.updated |
{ issue }, as in today’s deliveries |
tracker.issue.deleted |
{ issue: { key, title }, actor } |
tracker.comment.created |
{ issue, comment } |
agent.session.started |
sessionId, startedAt, and optionally projectId, issueKey, model and userId |
agent.session.finished |
sessionId, outcome (completed, failed or cancelled), durationSeconds, finishedAt, and optionally summary |
skills.task.completed |
taskId, skill, outcome (success, failure or skipped), and optionally summary and durationSeconds |
time.entry.created |
{ entry } with minutes, activity, date and the issue key |
eventFromWebhookDelivery lifts a delivery of today’s format into an envelope, and CloudEventSchema reads any envelope, including types this package does not know yet.
Sending an event as a webhook
Section titled “Sending an event as a webhook”import { buildEventRequest, parseAnyWebhookBody, verifyWebhookRequest } from '@skribly/core/webhooks'
const { body, headers } = buildEventRequest({ event, secret })await fetch(hook.url, { method: 'POST', headers, body })
if (verifyWebhookRequest(rawBody, request.headers, secret)) { const event = parseAnyWebhookBody(rawBody, 'https://tracker.example.com')}The body is sent as application/cloudevents+json and signed with the same X-Skribly-Signature as today’s deliveries. parseAnyWebhookBody reads both formats and always returns an envelope, so a receiver built on it keeps working when an app moves to the new format.
Planned
Section titled “Planned”- Apps sending the envelope: the Tracker for its issue and comment events, Skribly Agent for its sessions.
- Signing in the Standard Webhooks style, with a timestamp in the signature and secret rotation.
- One stream that feeds webhooks, an inbox, activity feeds, the audit log and search.