Webhooks

Configure webhooks to receive real-time notifications when experiments complete, agents error, pods terminate, and more.

Webhooks API

Webhooks let you receive HTTP callbacks when events occur in your lab. Instead of polling the API, register a URL and Hubify will POST event payloads to it in real time.

Create a Webhook

Lab this webhook is scoped to.

HTTPS endpoint to receive webhook payloads. Must be publicly accessible.

List of event types to subscribe to. Use ["*"] for all events.

Shared secret for HMAC-SHA256 payload signing. Auto-generated if omitted.

Human-readable description of this webhook's purpose.

Whether the webhook is active.

curl -X POST https://api.hubify.com/v1/webhooks \
  -H "Authorization: Bearer $HUBIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "labId": "j57a8k9m2n3p4q5r",
    "url": "https://example.com/webhooks/hubify",
    "events": ["experiment.completed", "experiment.failed", "pod.terminated"],
    "description": "Notify CI pipeline on experiment completion"
  }'
const webhook = await hubify.webhooks.create({
  labId: "j57a8k9m2n3p4q5r",
  url: "https://example.com/webhooks/hubify",
  events: ["experiment.completed", "experiment.failed", "pod.terminated"],
  description: "Notify CI pipeline on experiment completion",
});

console.log(webhook.secret); // Save this -- used to verify payloads
Convex document ID.
Lab ID.
Destination URL.
Subscribed event types.
HMAC signing secret (shown only on creation).
Description.
Whether active.
Creation timestamp (ms).

Event Types

Experiment Events

EventTrigger
experiment.createdA new experiment is created
experiment.startedAn experiment begins running on a pod
experiment.completedAn experiment finishes with pass status
experiment.failedAn experiment finishes with fail status
experiment.blockedAn experiment transitions to blocked

Agent Events

EventTrigger
agent.startedAn agent is activated
agent.stoppedAn agent is retired
agent.errorAn agent encounters an unrecoverable error
agent.task_completedAn agent completes a task
agent.escalationAn agent escalates an issue to the Captain

Task Events

EventTrigger
task.createdA new task is created
task.assignedA task is assigned to an agent
task.completedA task moves to done status
task.review_requestedA task moves to review status
task.review_completedA review is submitted for a task

Pod Events

EventTrigger
pod.createdA pod is provisioned
pod.runningA pod is online and ready
pod.stoppedA pod is stopped
pod.terminatedA pod is permanently terminated
pod.idleA pod has been idle beyond the configured threshold
pod.budget_alertPod costs approach the lab's budget limit

Paper Events

EventTrigger
paper.updatedA paper's content or metadata changes
paper.review_completedA cross-model review completes on a paper
paper.submittedA paper status changes to submitted
paper.publishedA paper status changes to published

Knowledge Events

EventTrigger
knowledge.createdA new knowledge entry is created
knowledge.updatedA knowledge entry is modified

Payload Format

All webhook payloads follow a consistent structure:

{
  "id": "evt_abc123def456",
  "type": "experiment.completed",
  "createdAt": 1713100800000,
  "labId": "j57a8k9m2n3p4q5r",
  "data": {
    "_id": "exp_abc123",
    "experimentId": "EXP-054",
    "title": "MCMC Chain -- Planck 2018 + BAO",
    "status": "pass",
    "runTimeSeconds": 3847,
    "completedAt": 1713100800000
  }
}

Unique event ID. Use for idempotency.

Event type (e.g., experiment.completed).

Event timestamp in milliseconds.

Lab the event belongs to.

Event-specific payload. Contains the relevant resource object.

Signature Verification

Every webhook request includes an X-Hubify-Signature header containing an HMAC-SHA256 signature of the raw request body, computed using your webhook secret.



function verifyWebhook(
  body: string,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your webhook handler:
app.post("/webhooks/hubify", (req, res) => {
  const signature = req.headers["x-hubify-signature"] as string;
  if (!verifyWebhook(req.rawBody, signature, WEBHOOK_SECRET)) {
    return res.status(401).send("Invalid signature");
  }

  const event = JSON.parse(req.rawBody);
  console.log("Received " + event.type + ":", event.data);
  res.status(200).send("OK");
});
import hmac
import hashlib

def verify_webhook(body: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        body,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(signature, expected)

Retry Policy

Failed webhook deliveries are retried with exponential backoff:

AttemptDelay
1Immediate
230 seconds
32 minutes
410 minutes
51 hour
66 hours

After 6 failed attempts, the webhook is marked as failing and the Captain receives a notification. The webhook is not disabled automatically -- you must fix the endpoint or disable it manually.

A delivery is considered failed if:

  • The endpoint returns a non-2xx status code
  • The request times out after 30 seconds
  • The endpoint is unreachable (DNS failure, connection refused)

List Webhooks

curl "https://api.hubify.com/v1/webhooks?labId=j57a8k9m2n3p4q5r" \
  -H "Authorization: Bearer $HUBIFY_TOKEN"
const webhooks = await hubify.webhooks.list({
  labId: "j57a8k9m2n3p4q5r",
});

Update a Webhook

Updated destination URL. Updated event subscriptions. Enable or disable the webhook. Updated description.

curl -X PATCH https://api.hubify.com/v1/webhooks/w78k9l0m1n2o3p4q \
  -H "Authorization: Bearer $HUBIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "events": ["experiment.completed", "experiment.failed", "agent.error"],
    "enabled": true
  }'
await hubify.webhooks.update({
  webhookId: "w78k9l0m1n2o3p4q",
  events: ["experiment.completed", "experiment.failed", "agent.error"],
  enabled: true,
});

Delete a Webhook

curl -X DELETE https://api.hubify.com/v1/webhooks/w78k9l0m1n2o3p4q \
  -H "Authorization: Bearer $HUBIFY_TOKEN"
await hubify.webhooks.delete({ webhookId: "w78k9l0m1n2o3p4q" });

Test a Webhook

Send a test payload to verify your endpoint is working correctly.

curl -X POST https://api.hubify.com/v1/webhooks/w78k9l0m1n2o3p4q/test \
  -H "Authorization: Bearer $HUBIFY_TOKEN"
const result = await hubify.webhooks.test({
  webhookId: "w78k9l0m1n2o3p4q",
});
// { success: true, statusCode: 200, responseTime: 142 }

Delivery History

View recent webhook delivery attempts and their outcomes.

curl "https://api.hubify.com/v1/webhooks/w78k9l0m1n2o3p4q/deliveries?limit=20" \
  -H "Authorization: Bearer $HUBIFY_TOKEN"
const deliveries = await hubify.webhooks.listDeliveries({
  webhookId: "w78k9l0m1n2o3p4q",
  limit: 20,
});
Delivery ID.
Event ID that triggered this delivery.
Event type.
HTTP response status code.
Whether the delivery succeeded.
Attempt number (1-6).
Response time in milliseconds.
Error message if the delivery failed.
Delivery timestamp (ms).
← Back to docs index