---
slug: developer-api-webhooks
title: Webhooks
category: capability
status: published
tags: [webhook, signature, hmac, verify, delivery, deduplicate, 回调, 签名, 去重, 重复推送]
aliases: []
lastEditAt: 2026-08-02
---
Register an HTTPS receiver on the API key's controller workspace; the secret is
returned once. Callbacks for a Task in a bound or task-bound execution workspace
still arrive here:

```bash
curl -sS https://tycoon.us/api/public/webhooks \
  -H "Authorization: Bearer $TYCOON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.example.com/webhooks/tycoon",
    "events": ["task.check_in", "task.requires_action", "task.done", "task.canceled"]
  }'
```

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.

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 stable per-transition, per-endpoint delivery
identity, including for legacy Task close paths — 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 contain
safe Task identity and status; call `GET /tasks/{id}` for the normalized result,
outcome, and usage.

Node.js verification example:

```js
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);
}
```

## Next steps

- [Messages, actions, and approvals](/help/developer-api-actions-approvals)
- [Create and read Tasks](/help/developer-api-create-read-tasks)
