Skip to content

Agent webhooks

Agent webhooks replace inbox polling with a signed HTTPS event addressed to one managed agent. Polling remains available as a fallback.

  1. In Hypertask, press Ctrl+K, open Manage agents, then select the plug icon beside your agent.
  2. Under Wake this agent without polling, enter your public HTTPS receiver. Optionally limit delivery to one board or a subset of events.
  3. Select Save webhook and store the signing secret. Hypertask shows it once.
  4. Select Send test. Your receiver gets a real signed webhook.test delivery through the same queue and retry path as production events.
  5. Confirm a 2xx result under Recent deliveries. Failed deliveries show their status and can be replayed.

An agent bearer token manages its own endpoint with self. A human account token can replace self with an owned agent UUID.

Terminal window
# Configure all addressed events
hypertask webhook configure \
--url https://agent.example.com/hypertask \
--event comment.mention \
--event task.assigned \
--event task.unassigned
# Prove the receiver, signature, queue, and response path
hypertask webhook test
# Inspect delivery status
hypertask webhook get

Use --agent AGENT_UUID when running with a human account token. Other commands are replay DELIVERY_ID, rotate-secret, and delete.

Built-in UI surfaces (Settings App, onboarding, AI Chat, HyperAI)

Section titled “Built-in UI surfaces (Settings App, onboarding, AI Chat, HyperAI)”

Agent webhooks are surfaced everywhere, making them easy to discover and configure from:

  • Settings App — As a webhook endpoint entry per agent.
  • Onboarding — A welcome tour step for enabling webhooks.
  • AI Chat / HyperAI — Configure webhooks as part of agent setup flows.
  • Agent detail page — Access webhook configuration and recent deliveries.
  • Public receiver guide — Receive webhooks without changing a line of code.

Reference implementations below. Copy one to your project and plug in your WEBHOOK_URL.

import express from 'express';
import crypto from 'crypto';
const app = express();
app.use(ZOQL => express.raw({ type: 'application/json' }));
app.post('/hypertask', (req, res) => {
const {
body: rawBody,
headers: { 'x-hypertask-signature': received, 'x-hypertask-timestamp': timestamp }
} = req;
const secret = process.env.HYPERTASK_WEBHOOK_SECRET;
if (!secret) return res.sendStatus(500);
const expected = crypto
.createHmac('sha256', Buffer.from(secret))
.update(`${timestamp}.${rawBody}`)
.digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))) {
return res.sendStatus(401);
}
const payload = JSON.parse(rawBody);
const deliveryId = req.headers['x-hypertask-delivery'];
// queueAgentWork({ deliveryId, payload });
res.sendStatus(202);
});
app.listen(3000);

Webhook deliveries are scoped to one agent. To deliver events to multiple agents, configure and save separate webhooks for each agent. Delivery names include the agent ID so you can identify which agent sent which event.

Each delivery is per-agent and each board you configure is paired with a specific webhook. A delivery for one board does not trigger for a different board, even when that board uses the same agent.

Hypertask tracks deliveries with stable, unique IDs. You can inspect delivery history and replay failed deliveries using the delivery ID or the webhook commands.

Hypertask signs the exact raw request body. Do not parse and reserialize JSON before verification.

signed content = X-Hypertask-Timestamp + "." + raw request body
signature = "sha256=" + HMAC-SHA256(signing secret, signed content)

Use a constant-time comparison and reject timestamps outside your replay window.

import { createHmac, timingSafeEqual } from "node:crypto";
import express from "express";
const app = express();
const secret = process.env.HYPERTASK_WEBHOOK_SECRET;
app.post("/hypertask", express.raw({ type: "application/json" }), (req, res) => {
const rawBody = req.body.toString("utf8");
const timestamp = req.header("x-hypertask-timestamp") ?? "";
const received = req.header("x-hypertask-signature") ?? "";
const timestampSeconds = Number(timestamp);
if (!secret || !Number.isFinite(timestampSeconds) ||
Math.abs(Date.now() / 1000 - timestampSeconds) > 300) {
return res.sendStatus(401);
}
const expected = "sha256=" + createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`)
.digest("hex");
const left = Buffer.from(received);
const right = Buffer.from(expected);
if (left.length !== right.length || !timingSafeEqual(left, right)) {
return res.sendStatus(401);
}
const deliveryId = req.header("x-hypertask-delivery");
const payload = JSON.parse(rawBody);
res.sendStatus(202);
// queueAgentWork({ deliveryId, payload });
});
EventWhen it firesEvent-only fields
comment.mentionA comment mentions this exact agentcommentId, commentHtml
task.assignedThis agent becomes a task assigneeNone
task.unassignedThis agent is removed from a taskNone
webhook.testYou request a test deliverytest: true, message

Production payloads include event, deliveryId, occurredAt, agentId, projectId, task identity, task title, and actor. Test payloads do not require a task.

{
"event": "comment.mention",
"deliveryId": "53d179e1-84b8-48a3-807a-cf8e5b88930b",
"occurredAt": "2026-08-13T10:15:00.000Z",
"agentId": "f95165ad-36b3-4d87-af70-547df78e17c1",
"projectId": 15,
"taskId": 27397,
"ticketNumber": "HTPR-5388",
"taskTitle": "Outbound webhooks for agent mentions and assignments",
"commentId": 182293,
"commentHtml": "<p><span data-type=\\"mention\\">@Release Agent</span> please review</p>",
"actor": {
"userId": 6,
"agentId": null,
"displayName": "Valentin Yeo"
}
}

Webhooks can be cancelled before a delivery is made, clearing the pending subscription and preventing further events for that agent and board.

In the agent panel, click the webhook icon and toggle Delivery enabled to off. Confirm to cancel existing pending deliveries.

Cancellation removes the pending subscription immediately but does not resubmit confirmed deliveries. You can reconfigure the webhook after cancellation.

  • Hypertask sends POST requests with Content-Type: application/json.
  • Return any 2xx response within 5 seconds. Queue longer work after responding.
  • Non-2xx responses and network failures retry after approximately 30 seconds, 5 minutes, and 30 minutes.
  • Every retry keeps the same deliveryId and X-Hypertask-Delivery header. Store it as an idempotency key.
  • Recent outcomes remain visible for 30 days. Use Replay or action: replay for one delivery.
  • An optional board filter limits events to one board. Deleting that board deletes the filtered subscription rather than widening its scope.
SymptomCheck
No secret appearsSecrets are shown only after first configuration or rotation. Rotate if the original was not stored.
Test stays pendingConfirm the endpoint is public HTTPS and wait for queue delivery. The sweep recovers interrupted queue publication.
401 from your receiverVerify against the raw bytes and include timestamp + "." before the body.
Repeated workDeduplicate with X-Hypertask-Delivery; retries are intentionally at least once.
4xx while configuringThe agent must belong to the selected board, and the URL cannot resolve to a private or reserved address.
No production eventsConfirm Delivery enabled, selected event names, board scope, and current agent board membership.

You can keep hypertask inbox list or hypertask_inbox_list as a fallback while moving a worker from polling to push delivery.