Webhooks & Events
Session Webhooks allow your backend services to receive push notifications whenever an Agent Session progresses, requests human approval, or completes. Webhooks deliver streaming chunks and status transitions directly to your HTTP endpoints without maintaining persistent WebSockets.
Webhook subscriptions
Webhooks are scoped per environment (e.g. production or staging). When
creating a subscription, provide the destination URL:
const result = await skopik.webhooks.create({
environment: 'production',
url: 'https://api.example.com/webhooks/skopik',
})
console.log('Subscription ID:', result.webhookSubscription.webhookSubscriptionId)
// Plaintext signing secret is returned ONLY upon creation
console.log('Signing Secret:', result.signingSecret)
curl "https://api.skopik.com/api/v1/webhook_subscriptions" \
-H "Authorization: Bearer $SKOPIK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"environment": "production",
"url": "https://api.example.com/webhooks/skopik"
}'
Save the signingSecret in your application's secret store immediately. It is
used to cryptographically verify that inbound HTTP requests originate from
Skopik.
Event payload format
Skopik delivers events as JSON payloads under the session.chunks event
structure:
{
"event": "session.chunks",
"environment": "production",
"sessionId": "ses_98765",
"agentId": "ag_12345",
"cursor": "turn_3:seq_12",
"timestamp": "2026-08-20T14:32:00.000Z",
"chunks": [
{
"type": "message.chunk",
"turn": 3,
"seq": 12,
"delta": "Security review completed: 0 high-severity issues found."
},
{
"type": "session.status",
"status": "idle",
"waitingFor": null
}
]
}
Common chunk types
| Chunk Type | When Emitted | Key Fields |
|---|---|---|
message.chunk |
Model streaming response tokens. | delta, turn, seq |
session.status |
Session transitions state. | status, waitingFor |
tool.call |
Agent initiates a tool invocation. | tool, toolCallId, input |
approval.requested |
Effectful operation paused for approval. | operationId, name, input |
approval.resolved |
An approval decision was recorded. | operationId, decision |
Verify webhook signatures
Every webhook POST request includes two security headers:
X-Skopik-Timestamp: Epoch timestamp in seconds when the webhook was dispatched.X-Skopik-Signature: HMAC-SHA256 signature in hexadecimal format computed over${timestamp}.${rawBody}using your subscription's signing secret.
Node.js verification example
import crypto from 'node:crypto'
import type { Request, Response } from 'express'
export function verifySkopikWebhook(
rawBody: string | Buffer,
signatureHeader: string,
timestampHeader: string,
signingSecret: string,
): boolean {
const timestamp = Number.parseInt(timestampHeader, 10)
const now = Math.floor(Date.now() / 1000)
// Reject replays older than 5 minutes
if (Math.abs(now - timestamp) > 300) {
return false
}
const payload = `${timestampHeader}.${typeof rawBody === 'string' ? rawBody : rawBody.toString('utf8')}`
const expectedSignature = crypto
.createHmac('sha256', signingSecret)
.update(payload)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(signatureHeader, 'hex'),
Buffer.from(expectedSignature, 'hex'),
)
}
// Express route handler example
app.post('/webhooks/skopik', express.raw({ type: 'application/json' }), (req: Request, res: Response) => {
const signature = req.headers['x-skopik-signature'] as string
const timestamp = req.headers['x-skopik-timestamp'] as string
const isValid = verifySkopikWebhook(
req.body,
signature,
timestamp,
process.env.SKOPIK_WEBHOOK_SECRET!,
)
if (!isValid) {
return res.status(401).send('Invalid signature')
}
const event = JSON.parse(req.body.toString('utf8'))
console.log(`Received event for session ${event.sessionId}:`, event.chunks)
res.status(200).json({ received: true })
})
Zero-downtime secret rotation
When rotating a webhook signing secret, Skopik generates a new secret while granting a transition grace period where signatures generated with either the old or new secret are accepted:
const { signingSecret } = await skopik.webhooks.rotateSecret('production')
// Update your receiver to accept the new secret
await secretStore.set('SKOPIK_WEBHOOK_SECRET', signingSecret)
curl "https://api.skopik.com/api/v1/webhook_subscriptions/production/rotate_secret" \
-X POST \
-H "Authorization: Bearer $SKOPIK_API_KEY"
Organization event log
For auditability, activity dashboards, and pull-based integrations, inspect the organization event log:
curl "https://api.skopik.com/api/v1/events?limit=50" \
-H "Authorization: Bearer $SKOPIK_API_KEY"
The event log tracks all resource creation, updates, deletions, session runs, and administrative actions across your workspace.
API reference
| SDK Method | HTTP Endpoint | Description |
|---|---|---|
webhooks.list() |
GET /webhook_subscriptions |
List webhook subscriptions across environments. |
webhooks.create() |
POST /webhook_subscriptions |
Create a webhook subscription for an environment. |
webhooks.get() |
GET /webhook_subscriptions/{env} |
Read subscription details and secret version. |
webhooks.rotateSecret() |
POST /webhook_subscriptions/{env}/rotate_secret |
Rotate the HMAC signing secret with grace period. |
webhooks.delete() |
DELETE /webhook_subscriptions/{env} |
Delete a webhook subscription. |