Search

Search the documentation

Protobox
Use cases

Observability & audit

Every tool call is a queryable record: execution logs, per-session activity, and signed platform webhooks — the material for debug panels, alerting, and compliance answers.

Scenario: A customer asks Acme: "what exactly did the assistant do on our account last Tuesday?" Compliance asks: "who approved that refund?" On-call asks: "why are Gmail sends failing since 14:00?" All three answers come from the same three surfaces.

The execution log

Every execution — agent tool-call, MCP session call, background actions.execute, approval replay — writes one record:

// Slice by tool, agent, status, or time window:
const { items, nextCursor } = await protobox.executions.list({
  status: 'error',
  startDate: '2026-07-22T00:00:00Z',
  endDate: '2026-07-23T00:00:00Z',
  limit: 100,
});

// Drill into one:
const record = await protobox.executions.get(executionId);
// → tool, status, arguments, result, timing — the "what exactly ran" answer

This is the join point for the other recipes: approvals carry an executionId after replay, webhook events carry one on completion/failure, and session events reference the same records.

Per-session activity

Conversation-scoped questions go to the session itself:

// The 90-day feed, newest first, cursor-paginated:
const page = await protobox.sessions.listEvents(sessionId, { type: 'execute', status: 'error' });

// The at-a-glance answer for a debug panel:
const session = await protobox.sessions.get(sessionId);
// session.summary → { toolCalls, errors, lastCall, toolkitsUsed, toolkitsInScope }

listEvents filters by type (search / execute / connection / sandbox) and status — "show me every failed execute in this conversation" is one call. One nuance: calls to code-bundle tools are typed sandbox, not execute — an execute-only filter silently misses them, so query both types when the session mixes integration and code tools.

Platform webhooks

One endpoint per workspace receives signed events — push-based monitoring instead of polling:

const hook = await protobox.webhooks.register({ url: 'https://api.acme.com/protobox/events' });
// hook.secret is returned ONLY on create/rotate — store it for verification.

Verify every delivery

verifyWebhook is a pure function (no HTTP): it checks the X-Protobox-Signature header (HMAC-SHA256 over "{t}.{rawBody}", 5-minute default tolerance) and returns the typed event:

import { verifyWebhook, WebhookVerificationError } from '@protoboxai/sdk';

app.post('/protobox/events', express.raw({ type: 'application/json' }), (req, res) => {
  let event;
  try {
    event = verifyWebhook(req.body.toString('utf8'), req.get('X-Protobox-Signature') ?? '', {
      secret: process.env.PROTOBOX_WEBHOOK_SECRET!,
    });
  } catch (err) {
    if (err instanceof WebhookVerificationError) return res.status(400).end();
    throw err;
  }
  res.status(200).end();          // ack fast, process async
  void handleEvent(event);
});

Verify against the raw request body — a re-serialized JSON body will fail the signature. Rotate the secret with webhooks.register({ url, rotateSecret: true }).

The event vocabulary

EventFires whenUseful for
connection.activatedA user finished connecting an appFlip UI state, kick off first-sync
connection.revokedA credential was revokedReflect disconnect, pause dependent jobs
execution.pending_approvalA policy paused a callNotify your approval inbox
execution.completedAn execution finishedProgress tracking for async work
execution.failedAn execution failedAlerting

Event payloads are deliberately minimal — ids, integration, status, your callerId. Never arguments, results, or credential material; fetch details via executions.get when you need them.

An audit answer, end-to-end

"Who approved the refund on ticket T-4821, and did it run?"

const { items } = await protobox.approvals.list({ status: 'approved' });
const approval = items.find((a) => a.arguments['ticketId'] === 'T-4821');
// approval.decidedBy, approval.decidedAt        → who and when
const execution = approval?.executionId
  ? await protobox.executions.get(approval.executionId)
  : undefined;                                    // → did it run, and what happened

Production notes

  • Correlate with requestId. Every SDK error carries the platform requestId (from x-request-id) — log it, and support conversations become lookups.
  • Ack webhooks in under a second and process on a queue; slow receivers get treated like failing ones.
  • Alert on execution.failed rates, not single failures — a not_connected burst usually means one user's token expired, while a cross-user upstream_error burst means a provider incident.

On this page