curl -sS https://tycoon.us/api/public/webhooks \
-H "Authorization: Bearer ***" \
-H "Content-Type: application/json" \
-d '{
"url": "https://api.example.com/webhooks/tycoon",
"events": ["task.check_in", "task.requires_action", "task.done", "task.canceled"]
}'
The url must be https. A workspace may register up to 10 receivers. The
valid event names are task.check_in, task.requires_action, task.done,
task.canceled, and webhook.test: Task transitions emit the first four,
while webhook.test is emitted only by the test endpoint below. Omit events
(or pass an empty array) to subscribe to every event.
A callback supplied at Task creation is separate: it is attached only to
that Task and is not listed or managed through the workspace-wide webhook API.
It receives the same signed public-safe events without allowing one customer's
callback to receive another customer's Task.
List, test, and remove receivers
# List this workspace's receivers. The signing secret is never returned.
curl -sS https://tycoon.us/api/public/webhooks \
-H "Authorization: Bearer ***"
# → { "webhooks": [{ "id": "wh...", "url": "...", "events": [...] }],
# "events": ["task.check_in", "task.requires_action", "task.done", "task.canceled", "webhook.test"] }
# Send a signed webhook.test event now and read the receiver's HTTP status.
curl -sS -X POST https://tycoon.us/api/public/webhooks/wh.../test \
-H "Authorization: Bearer ***"
# → { "delivered": true, "receiverStatus": 200 }
# Remove a receiver.
curl -sS -X DELETE https://tycoon.us/api/public/webhooks/wh... \
-H "Authorization: Bearer ***"
# → { "deleted": true }
Verify the signature
Each delivery sets three headers: x-tycoon-event (the event name),
x-tycoon-signature (the HMAC), and x-tycoon-delivery (a stable
per-transition, per-endpoint delivery id). Verify the raw request body with
HMAC-SHA256 and compare it with x-tycoon-signature in the form sha256=<hex>.
Use x-tycoon-delivery as the deduplication key — a receiver that already
processed a delivery id can safely ignore a repeat of it. task.check_in is
sent after a material canonical checkpoint with a safe outcome projection.
task.requires_action additionally contains the safe primary ApprovalRequest
summary, current action version, safe decision items, api_resolvable, and an
authenticated action_url; when it is API-resolvable, call the resolve
endpoint above, or use POST /tasks/{id}/messages to add context. Terminal
payloads (task.done, task.canceled) contain safe Task identity and status;
call GET /tasks/{id} for the normalized result, outcome, and usage.
Node.js verification example:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyTycoonWebhook(rawBody, signature, secret) {
const expected = `sha256=${createHmac("sha256", secret)
.update(rawBody)
.digest("hex")}`;
const left = Buffer.from(signature ?? "");
const right = Buffer.from(expected);
return left.length === right.length && timingSafeEqual(left, right);
}