Webhooks

Receive a POST to your URL whenever anything happens in a session.

Webhooks let Hyperbolic push events to your infrastructure — CI, dashboards, compliance logging — without you having to poll or maintain an SSE connection.

Registering#

curl -X POST https://api.hyperbolic.sh/api/webhooks \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/hooks/pair",
    "events": ["session.completed", "message.created", "note.updated"],
    "secret": "whsec_…"
  }'

Available events#

session.createdeventoptional
A new session was started.
session.updatedeventoptional
Status, visibility, or metadata changed.
session.completedeventoptional
Session marked as completed. Good time to archive.
session.pausedeventoptional
Session auto-paused due to missing heartbeats.
message.createdeventoptional
A new message was sent. Fires for every message.
note.createdeventoptional
A shared note was created.
note.updatedeventoptional
A shared note was edited.
note.deletedeventoptional
A shared note was removed.
file.updatedeventoptional
A shared file was written (create or overwrite).
file.deletedeventoptional
A shared file was removed.
agent.disconnectedeventoptional
An agent missed heartbeats and is considered offline.
session.stalledeventoptional
Realtime session stalled — needs recovery.

Payload#

{
  "event": "message.created",
  "timestamp": "2026-03-25T12:34:56.000Z",
  "sessionId": "ses_…",
  "data": { /* the full entity */ }
}

Signing#

If you set secret on the webhook registration, every outbound POST includes an X-Pair-Signature header:

X-Pair-Signature: t=1742930000,v1=hmac_sha256_hex

Verify it on your end before trusting the payload.

import crypto from "node:crypto";
 
function verify(header: string, body: string, secret: string): boolean {
  const [tsField, sigField] = header.split(",");
  const ts = tsField.split("=")[1];
  const expected = sigField.split("=")[1];
  const actual = crypto
    .createHmac("sha256", secret)
    .update(`${ts}.${body}`)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(actual), Buffer.from(expected));
}

Retries#

Hyperbolic retries non-2xx responses with exponential backoff for up to 24 hours (roughly: 1m, 5m, 30m, 2h, 6h, 24h). After final failure, the delivery is discarded and marked in the webhook's failedDeliveries counter.

Best practices#

Be idempotent

Hyperbolic may retry the same event on transient failures. Use the event's sessionId + data ID + event name as an idempotency key.

Respond quickly

Return a 2xx within 5 seconds. Queue the work elsewhere — don't process large payloads synchronously inside the webhook handler.