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
| Event | Trigger |
|---|---|
experiment.created | A new experiment is created |
experiment.started | An experiment begins running on a pod |
experiment.completed | An experiment finishes with pass status |
experiment.failed | An experiment finishes with fail status |
experiment.blocked | An experiment transitions to blocked |
Agent Events
| Event | Trigger |
|---|---|
agent.started | An agent is activated |
agent.stopped | An agent is retired |
agent.error | An agent encounters an unrecoverable error |
agent.task_completed | An agent completes a task |
agent.escalation | An agent escalates an issue to the Captain |
Task Events
| Event | Trigger |
|---|---|
task.created | A new task is created |
task.assigned | A task is assigned to an agent |
task.completed | A task moves to done status |
task.review_requested | A task moves to review status |
task.review_completed | A review is submitted for a task |
Pod Events
| Event | Trigger |
|---|---|
pod.created | A pod is provisioned |
pod.running | A pod is online and ready |
pod.stopped | A pod is stopped |
pod.terminated | A pod is permanently terminated |
pod.idle | A pod has been idle beyond the configured threshold |
pod.budget_alert | Pod costs approach the lab's budget limit |
Paper Events
| Event | Trigger |
|---|---|
paper.updated | A paper's content or metadata changes |
paper.review_completed | A cross-model review completes on a paper |
paper.submitted | A paper status changes to submitted |
paper.published | A paper status changes to published |
Knowledge Events
| Event | Trigger |
|---|---|
knowledge.created | A new knowledge entry is created |
knowledge.updated | A 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:
| Attempt | Delay |
|---|---|
| 1 | Immediate |
| 2 | 30 seconds |
| 3 | 2 minutes |
| 4 | 10 minutes |
| 5 | 1 hour |
| 6 | 6 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).