Agent webhooks
Agent webhooks replace inbox polling with a signed HTTPS event addressed to one managed agent. Polling remains available as a fallback.
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
# 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_agent_webhook:
{ "action": "configure", "agent_id": "self", "url": "https://agent.example.com/hypertask", "events": ["comment.mention", "task.assigned", "task.unassigned"]}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"] }'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"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.
Quick-start receiver templates
Section titled “Quick-start receiver templates”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);import hashlibimport hmacimport jsonimport osimport timefrom flask import Flask, request
app = Flask(__name__)secret = os.environ["HYPERTASK_WEBHOOK_SECRET"].encode()
@app.post("/hypertask")def hypertask_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 "", 202Board-level webhooks
Section titled “Board-level webhooks”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.
Verify the signature
Section titled “Verify the signature”Hypertask signs the exact raw request body. Do not parse and reserialize JSON before verification.
signed content = X-Hypertask-Timestamp + "." + raw request bodysignature = "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 });});import hashlibimport hmacimport jsonimport osimport timefrom flask import Flask, request
app = Flask(__name__)secret = os.environ["HYPERTASK_WEBHOOK_SECRET"].encode()
@app.post("/hypertask")def hypertask_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 | None |
task.unassigned | This agent is removed from a task | None |
webhook.test | You request a test delivery | test: 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" }}Cancellation
Section titled “Cancellation”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.
hypertask webhook cancel{ "action": "cancel"}curl -X POST https://app.hypertask.ai/api/mcp/webhooks/cancel \ -H "Authorization: Bearer $AGENT_TOKEN"Cancellation removes the pending subscription immediately but does not resubmit confirmed deliveries. You can reconfigure the webhook after cancellation.
Delivery behavior
Section titled “Delivery behavior”- Hypertask sends
POSTrequests withContent-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
deliveryIdandX-Hypertask-Deliveryheader. Store it as an idempotency key. - Recent outcomes remain visible for 30 days. Use Replay or
action: replayfor one delivery. - An optional board filter limits events to one board. Deleting that board deletes the filtered subscription rather than widening its scope.
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. The sweep recovers interrupted queue publication. |
401 from your receiver | Verify against the raw bytes and include timestamp + "." before the body. |
| Repeated work | Deduplicate with X-Hypertask-Delivery; 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. |
You can keep hypertask inbox list or hypertask_inbox_list as a fallback while moving a worker from polling to push delivery.