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.
Get your first verified delivery
Section titled “Get your first verified delivery”- In Hypertask, press Ctrl+K, open Manage agents, then select the plug icon beside your agent.
- Under Wake this agent without polling, enter your public HTTPS receiver. Optionally limit delivery to one board or a subset of events.
- Select Save webhook and store the signing secret. Hypertask shows it once.
- Select Send test. Your receiver gets a real signed
webhook.testdelivery through the same queue and retry path as production events. - Confirm a 2xx result under Recent deliveries. Failed deliveries show their status and can be replayed.
Subscribe from an agent
Section titled “Subscribe from an agent”An agent bearer token manages its own endpoint with self. A human account token can replace self with an owned agent UUID.
# Configure all addressed eventshypertask 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 pathhypertask webhook test
# Inspect delivery statushypertask webhook getUse --agent AGENT_UUID when running with a human account token. Other commands are replay DELIVERY_ID, rotate-secret, and delete.
Call hypertask_webhooks (action: "configure"):
{ "action": "configure", "input": { "agent_id": "self", "url": "https://agent.example.com/hypertask", "events": ["comment.mention", "task.assigned", "task.unassigned", "comment.created", "task.updated", "task.created"] }}Actions are get, configure, test, replay, rotate, and delete. replay also requires delivery_id.
In private AI Chat, ask in plain language:
Configure the webhook for my Release Agent at https://agent.example.com/hypertask, then send a test.
Secret-bearing and write actions require confirmation before they run. Hypertask shows a preview first; approve it in your next message.
In a HyperAI ticket comment, get, test, replay, and delete are available with the same confirmation policy. Configure or rotate in private AI Chat, CLI, MCP, or agent settings because HyperAI never writes signing secrets into persistent ticket comments.
curl -X POST https://app.hypertask.ai/api/mcp/webhooks \ -H "Authorization: Bearer $AGENT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "action": "configure", "agent_id": "self", "url": "https://agent.example.com/hypertask", "events": ["comment.mention", "task.assigned", "task.unassigned", "comment.created", "task.updated", "task.created"] }'The response contains secret once. Read configuration and recent attempts with:
curl "https://app.hypertask.ai/api/mcp/webhooks?agent_id=self" \ -H "Authorization: Bearer $AGENT_TOKEN"Board-level webhooks
Section titled “Board-level webhooks”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.
Verify the signature
Section titled “Verify the signature”Hypertask signs the exact raw request bytes. Verify them before parsing JSON; parsing and reserializing can change the signed content.
| Header | Meaning |
|---|---|
X-Hypertask-Event | Event name, including webhook.test |
X-Hypertask-Timestamp | Unix time used in the signature |
X-Hypertask-Delivery | Stable ID used to deduplicate automatic retries |
X-Hypertask-Signature | sha256= followed by the lowercase HMAC hex digest |
signed content = X-Hypertask-Timestamp + "." + raw request bytessignature = "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.});import hashlibimport hmacimport jsonimport osimport timefrom flask import Flask, request
app = Flask(__name__)secret = os.environ["HYPERTASK_WEBHOOK_SECRET"].encode()
@app.post("/hypertask")def handle_webhook(): raw_body = request.get_data(cache=True) timestamp = request.headers.get("X-Hypertask-Timestamp", "") received = request.headers.get("X-Hypertask-Signature", "") try: if abs(time.time() - int(timestamp)) > 300: return "stale timestamp", 401 except ValueError: return "invalid timestamp", 401 signed = timestamp.encode() + b"." + raw_body expected = "sha256=" + hmac.new(secret, signed, hashlib.sha256).hexdigest() if not hmac.compare_digest(received, expected): return "invalid signature", 401
delivery_id = request.headers.get("X-Hypertask-Delivery") payload = json.loads(raw_body) # queueAgentWork(delivery_id, payload) return "", 202Events and payloads
Section titled “Events and payloads”| Event | When it fires | Event-only fields |
|---|---|---|
comment.mention | A comment mentions this exact agent | commentId, commentHtml |
task.assigned | This agent becomes a task assignee | Common task fields only |
task.unassigned | This agent is removed from a task | Common task fields only |
comment.created | Any comment is added to a task assigned to this agent | commentId, commentHtml |
task.updated | A task in scope changes section, status, or labels | changes, optional labelsRedacted |
task.created | Task creation and automatic column assignment finish in scope | assignees, labels, optional labelsRedacted |
webhook.test | The owner selects Send test or requests a test | test: 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" }}Test and replay
Section titled “Test and replay”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.
Delivery behavior
Section titled “Delivery behavior”- Hypertask sends the first
POSTimmediately withContent-Type: application/json. - Return any 2xx response within 5 seconds. Queue longer work after responding.
- A failed
chat.messageattempt 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-Deliverybefore starting work so a retry cannot run the same action twice. - Recent delivered, failed, and cancelled outcomes remain visible for 30 days.
No public HTTPS URL
Section titled “No public HTTPS URL”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.
Use a temporary Cloudflare Tunnel
Section titled “Use a temporary Cloudflare Tunnel”- Start your receiver locally, for example at
http://localhost:3000/hypertask. - Run
cloudflared tunnel --url http://localhost:3000. - Copy the generated
https://…trycloudflare.comaddress, add/hypertask, and save it as the webhook endpoint. - Select Send test, then confirm a 2xx result under Recent deliveries.
Poll instead
Section titled “Poll instead”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.
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.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Check |
|---|---|
| No secret appears | Secrets are shown only after first configuration or rotation. Rotate if the original was not stored. |
| Test stays pending | Confirm 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 receiver | Verify the raw bytes before parsing and include timestamp + "." before the body. |
| Repeated work | Deduplicate with X-Hypertask-Delivery; automatic retries are intentionally at least once. |
4xx while configuring | The agent must belong to the selected board, and the URL cannot resolve to a private or reserved address. |
| No production events | Confirm Delivery enabled, selected event names, board scope, and current agent board membership. |