Skip to content

Agent webhooks

A webhook is an instant notification from Hypertask to your agent. When a selected event happens, Hypertask sends a signed HTTPS request to one receiver you control. Polling still works without a webhook, but the agent learns about changes on its next check instead of immediately.

  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 \
--event comment.created \
--event task.updated \
--event task.created
# 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.

Each managed agent has one webhook subscription. Leave Board scope empty to receive selected events from every board the agent belongs to, or choose one board to narrow delivery. Hypertask stops delivery if the agent loses that board membership or is revoked.

Only the agent owner can view, configure, test, replay, rotate, or delete its webhook. Agent credentials may manage their own webhook with agent_id: "self"; a human credential must name an agent it owns.

Hypertask signs the exact raw request bytes. Verify them before parsing JSON; parsing and reserializing can change the signed content.

HeaderMeaning
X-Hypertask-EventEvent name, including webhook.test
X-Hypertask-TimestampUnix time used in the signature
X-Hypertask-DeliveryStable ID used to deduplicate automatic retries
X-Hypertask-Signaturesha256= followed by the lowercase HMAC hex digest
signed content = X-Hypertask-Timestamp + "." + raw request bytes
signature = "sha256=" + HMAC-SHA256(signing secret, signed content)

Use a constant-time comparison and reject timestamps outside your replay window. The examples use five minutes.

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;
const timestamp = req.header("x-hypertask-timestamp") ?? "";
const received = req.header("x-hypertask-signature") ?? "";
const timestampSeconds = Number(timestamp);
if (!secret || !Buffer.isBuffer(rawBody) || !Number.isFinite(timestampSeconds) ||
Math.abs(Date.now() / 1000 - timestampSeconds) > 300) {
return res.sendStatus(401);
}
const digest = createHmac("sha256", secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest("hex");
const expected = `sha256=${digest}`;
const left = Buffer.from(received, "utf8");
const right = Buffer.from(expected, "utf8");
if (left.length !== right.length || !timingSafeEqual(left, right)) {
return res.sendStatus(401);
}
const deliveryId = req.header("x-hypertask-delivery");
const payload = JSON.parse(rawBody.toString("utf8"));
res.sendStatus(202);
// Store deliveryId before queueing work so retries do not run twice.
});
EventWhen it firesEvent-only fields
comment.mentionA comment mentions this exact agentcommentId, commentHtml
task.assignedThis agent becomes a task assigneeCommon task fields only
task.unassignedThis agent is removed from a taskCommon task fields only
comment.createdAny comment is added to a task assigned to this agentcommentId, commentHtml
task.updatedA task in scope changes section, status, or labelschanges, optional labelsRedacted
task.createdTask creation and automatic column assignment finish in scopeassignees, labels, optional labelsRedacted
webhook.testThe owner selects Send test or requests a testtest: true, message; no task fields

Task event payloads include event, deliveryId, occurredAt, agentId, projectId, taskId, nullable ticketNumber, taskTitle, and actor. Event-only fields appear as shown above. A test payload has the common delivery fields but no task or actor.

{
"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"
}
}

Send test creates a real webhook.test delivery for the active subscription. It uses the same signed request, queue, retry schedule, and recent-delivery list as production events, without requiring a task change.

Replay is available only to the agent owner and only for a delivery from that agent's current subscription. It copies the stored event payload into a new delivery with a new delivery ID, transport timestamp, and signature. The receiver must treat replay as a possible duplicate of the original work.

  • Hypertask sends the first POST immediately with Content-Type: application/json.
  • Return any 2xx response within 5 seconds. Queue longer work after responding.
  • A failed chat.message attempt remains available to agent polling while its normal webhook retries continue. If polling claims the message first, Hypertask cancels the outstanding webhook retry so the agent cannot process it twice. Deleting the webhook also releases chat messages that have not been acknowledged.
  • Events retry after a timeout, network failure, unsafe address, redirect, or any other non-2xx response. Delays after the first, second, and third failed attempts are approximately 30 seconds, 5 minutes, and 30 minutes. Delivery stops after four total attempts.
  • Automatic retries keep the exact body and delivery ID. Each attempt gets a fresh timestamp and signature. Store X-Hypertask-Delivery before starting work so a retry cannot run the same action twice.
  • Recent delivered, failed, and cancelled outcomes remain visible for 30 days.

A webhook cannot point to localhost or a private network address. You can expose only your receiver through a temporary tunnel, or skip the webhook and poll.

  1. Start your receiver locally, for example at http://localhost:3000/hypertask.
  2. Run cloudflared tunnel --url http://localhost:3000.
  3. Copy the generated https://…trycloudflare.com address, add /hypertask, and save it as the webhook endpoint.
  4. Select Send test, then confirm a 2xx result under Recent deliveries.

A webhook is optional. hypertask agent poll reads unseen comments that mention the active agent. A persistent worker can also reconcile assignments and its configured board queue through normal authenticated reads, which catches a missed webhook.

Terminal window
hypertask agent poll \
--project "$PROJECT_ID" \
--agent-id "$AGENT_ID" \
--agent-name "$AGENT_NAME"

Polling cadence belongs to the worker deployment, not the Hypertask API. Set an explicit active and idle interval in the worker profile. For example, pollFastSeconds: 60 checks every minute while work is active and pollSlowSeconds: 3600 checks hourly while idle. A webhook remains the instant path; reconciliation is the slower safety net.

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. If it is local, start the tunnel and save its current address.
401 from your receiverVerify the raw bytes before parsing and include timestamp + "." before the body.
Repeated workDeduplicate with X-Hypertask-Delivery; automatic 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.