> For the complete documentation index, see [llms.txt](https://docs.taskade.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.taskade.com/developers/webhooks.md).

# Webhooks

## Overview

Webhooks let your Taskade automations communicate with the outside world in both directions:

* **Inbound webhooks** — external services send data *into* Taskade to trigger automations.
* **Outbound HTTP requests** — automations call *out* to external APIs as action steps.
* **Receiving Taskade events** — combine a Taskade **trigger** (e.g. *task completed*) with an outbound HTTP action to push events to your app.

{% hint style="info" %}
Two ways to receive Taskade events:

* **No-code:** build an automation with a Taskade trigger plus an HTTP action — see [Receiving Taskade Events](#receiving-taskade-events) below.
* **Programmatic:** register a **signed** outbound webhook with `POST /api/v2/webhooks` — see the [Webhook Registration API](#webhook-registration-api). Available on **Pro and above**.
  {% endhint %}

***

## Inbound Webhooks

Receive data from any external service to kick off a Taskade automation.

### How It Works

1. **Create a webhook trigger** in any automation flow.
2. Taskade **generates a unique webhook URL** for that trigger.
3. Configure your external service to **POST JSON data** to the URL.
4. The webhook payload becomes available as **dynamic data** in every subsequent action.

{% hint style="info" %}
The webhook URL is generated per automation. You'll find it in the **trigger configuration** panel after selecting the Webhook trigger type.
{% endhint %}

### Payload Structure

Any valid JSON body is accepted. Each field in your payload automatically becomes a **dynamic variable** you can reference in downstream actions.

**Example payload:**

```json
{
  "event": "form_submitted",
  "name": "Jane Doe",
  "email": "jane@example.com",
  "message": "Interested in a demo"
}
```

All four fields (`event`, `name`, `email`, `message`) are available as dynamic variables in your automation steps.

### Authentication

Webhook URLs are **unique and unguessable** — each contains a cryptographically random token. For additional security:

* Validate incoming payloads in your automation logic (e.g., check for an expected `event` value).
* Rotate the webhook URL if you suspect it has been compromised by deleting and re-creating the trigger.

{% hint style="warning" %}
Treat your webhook URLs like passwords. Do not share them publicly or commit them to source control.
{% endhint %}

#### Bearer Token Authentication

For stronger protection on inbound webhook triggers, you can require callers to supply a secret bearer token. When enabled, Taskade rejects any request that does not present a valid token before the automation runs.

**Setup:**

1. Open the webhook trigger configuration panel in your automation.
2. Enable **Bearer Token** authentication and set a secret token value.
3. Taskade generates (or lets you enter) a secret — store it securely; it will not be shown again.

**Calling the webhook:**

Every inbound request must include the token in the `Authorization` header:

```http
POST https://www.taskade.com/webhooks/<your-webhook-id>
Authorization: Bearer your_api_token_placeholder
Content-Type: application/json

{
  "event": "form_submitted",
  "name": "Jane Doe"
}
```

Requests that omit or supply an incorrect token receive a `401 Unauthorized` response and are not processed.

{% hint style="info" %}
Webhook automations are available on **Pro and above**. Free and Starter plans cannot create webhook triggers or subscriptions. See [taskade.com/pricing](https://www.taskade.com/pricing).
{% endhint %}

### Common Patterns

| Source                        | What Happens in Taskade                   |
| ----------------------------- | ----------------------------------------- |
| **External form submission**  | Create a task + notify the team           |
| **Stripe payment webhook**    | Update project status + send confirmation |
| **GitHub CI/CD webhook**      | Update deployment status in a project     |
| **CRM event (HubSpot, etc.)** | Sync contact data to a Taskade project    |

***

## Outbound HTTP Requests

For outbound communication, use the **HTTP Request** action in any automation to call external APIs.

### Configuration

| Setting     | Details                                                          |
| ----------- | ---------------------------------------------------------------- |
| **Method**  | `GET`, `POST`, `PUT`, `DELETE`                                   |
| **URL**     | Any valid endpoint                                               |
| **Headers** | Custom headers supported (e.g., `Authorization`, `Content-Type`) |
| **Body**    | JSON or form data                                                |

{% hint style="info" %}
Response data from the HTTP request is available as dynamic variables in subsequent automation steps — so you can chain API calls together.
{% endhint %}

### Example: Post to an External API

| Setting     | Value                                                                                                                  |
| ----------- | ---------------------------------------------------------------------------------------------------------------------- |
| **Method**  | `POST`                                                                                                                 |
| **URL**     | `https://api.example.com/notifications`                                                                                |
| **Headers** | <p><code>Content-Type: application/json</code><br><code>Authorization: Bearer your\_api\_token\_placeholder</code></p> |

**Body:**

```json
{
  "channel": "#alerts",
  "text": "New task created: {{task.name}}"
}
```

***

## Webhook Registration API

{% hint style="info" %}
Requires **Pro or above**. Registration returns a **signing secret exactly once** — store it before anything else. The authoritative schema is the live [Action API v2 spec](https://www.taskade.com/api/documentation/v2).
{% endhint %}

Register **signed** outbound webhooks over the public API — no automation flow needed. Taskade `POST`s the event payload to your endpoint and signs every delivery with an HMAC secret so you can verify it really came from Taskade. This is the same event stream the official [Taskade integrations](https://github.com/taskade/integrations) (Zapier, n8n, Activepieces) consume, and it replaces the legacy unsigned `subscribeWebhook` / `unsubscribeWebhook` operations (still available, now deprecated — see [below](#legacy-unsigned-subscriptions-deprecated)).

### Register a webhook

**`POST /api/v2/webhooks`** — one target URL, one or more events, optional workspace scoping:

```http
POST https://www.taskade.com/api/v2/webhooks
Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN
Content-Type: application/json

{
  "targetUrl": "https://your-app.example.com/hooks/taskade",
  "events": ["task.due", "task.assigned"],
  "spaceIds": []
}
```

**Response** — `secret` is shown **once**:

```json
{
  "ok": true,
  "webhook": {
    "id": "https://your-app.example.com/hooks/taskade",
    "url": "https://your-app.example.com/hooks/taskade",
    "events": ["task.due", "task.assigned"],
    "spaceIds": [],
    "createdAt": "2026-07-21T09:00:00.000Z"
  },
  "secret": "your_signing_secret_placeholder"
}
```

A webhook's **`id` is its target URL** — URL-encode it whenever you use it as a path parameter.

### Supported events

| Event              | Fires when                       |
| ------------------ | -------------------------------- |
| `task.due`         | A task's due date arrives        |
| `task.assigned`    | A task is assigned to someone    |
| `comment.created`  | A comment is added to a task     |
| `project.created`  | A project is created             |
| `project.assigned` | A project is assigned to someone |
| `project.joined`   | Someone joins a project          |

### Scope to specific workspaces

`"spaceIds": []` (the default) delivers matching events from **all** your workspaces. Pass workspace ids to receive events from only those workspaces:

```json
{
  "targetUrl": "https://your-app.example.com/hooks/taskade",
  "events": ["project.created"],
  "spaceIds": ["SPACE_ID_1", "SPACE_ID_2"]
}
```

### List, inspect, delete

```bash
# list all registered webhooks
curl https://www.taskade.com/api/v2/webhooks \
  -H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN"

# get or delete one — the :id is the URL-encoded target URL
curl -X DELETE \
  "https://www.taskade.com/api/v2/webhooks/https%3A%2F%2Fyour-app.example.com%2Fhooks%2Ftaskade" \
  -H "Authorization: Bearer YOUR_PERSONAL_ACCESS_TOKEN"
```

`GET /api/v2/webhooks` returns `{ "ok": true, "items": [ ... ] }`; `DELETE /api/v2/webhooks/{id}` returns `{ "ok": true, "deleted": true }`.

### Verify delivery signatures

Every delivery carries an `X-Taskade-Signature` header — `sha256=` followed by the hex HMAC-SHA256 of the **raw request body**, keyed with your webhook's secret:

```
X-Taskade-Signature: sha256=5f4dcc3b5aa765d61d8327deb882cf99...
```

Recompute the HMAC over the raw body (before any JSON parsing) and compare in constant time:

```typescript
import crypto from "node:crypto";

function verifyTaskadeWebhook(rawBody: Buffer, signatureHeader: string, secret: string): boolean {
  const expected = `sha256=${crypto.createHmac("sha256", secret).update(rawBody).digest("hex")}`;
  return (
    expected.length === signatureHeader.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader))
  );
}
```

Reject any delivery whose signature doesn't match.

### Limits & requirements

| Rule       | Detail                                                                                                                                               |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| Plan       | **Pro or above** (`402 PAYMENT_REQUIRED` otherwise) on a verified account. Deleting is always allowed, so a downgraded account can still clean up.   |
| Target URL | Must be **`https`** (deliveries use an SSRF-guarded fetch). Max **2,048 characters** URL-encoded.                                                    |
| Limit      | Up to **100** event–workspace combinations per account across your registered webhooks (a webhook with 3 events scoped to 2 workspaces counts as 6). |
| Scope      | All workspaces by default; narrow with `spaceIds`.                                                                                                   |
| Dashboard  | You can also create and manage outgoing webhooks in [**Settings > API**](https://www.taskade.com/settings/api).                                      |

### Legacy: unsigned subscriptions (deprecated)

`POST /api/v2/subscribeWebhook` (`{ targetUrl, triggerType }` -> `{ ok, hookId }`) and `POST /api/v2/unsubscribeWebhook` (`{ hookId }`) still work, but they are **deprecated**: each subscription covers a single event, scoping is account-level only, and deliveries are **not signed**. New integrations should use `POST /api/v2/webhooks`; existing ones can migrate by registering the same target URL with the new endpoint and removing the old subscription.

***

## Receiving Taskade Events

To notify your app when something happens **in** Taskade (a task is added, a task is completed), build an automation that starts with a Taskade **trigger** and ends with an [Outbound HTTP Request](#outbound-http-requests) to your endpoint. The trigger's fields are available as dynamic variables in the HTTP body.

### Common triggers and their payloads

**Task added** — fires when a new task is added to a project:

```json
{
  "projectId": "abc123",
  "nodeId": "node_456",
  "nodeText": "Follow up with client",
  "projectTitle": "Sales Pipeline",
  "nodeNote": "Optional note text",
  "projectLink": "https://www.taskade.com/d/abc123",
  "assignees": [ { "handle": "jane" } ],
  "startDate": "2026-06-10",
  "endDate": "2026-06-12"
}
```

**Task completed** — fires when a task is marked complete:

```json
{
  "projectId": "abc123",
  "nodeId": "node_456",
  "nodeText": "Follow up with client",
  "projectTitle": "Sales Pipeline",
  "projectLink": "https://www.taskade.com/d/abc123",
  "completedBy": "jane",
  "completedAt": "2026-06-11T14:30:00Z",
  "triggerTime": "2026-06-11T14:30:01Z",
  "assignees": [ { "handle": "jane" } ]
}
```

Custom field values on the task are included as additional keys. Other triggers (new comment, due date, project completed, schedule) follow the same pattern — see the [Action & Trigger Reference](/automations/automation/actions.md).

***

## Rate Limits

Excessive inbound webhook calls may be throttled to protect system stability. If you expect high-volume webhook traffic, consider batching events or adding a queue on the sender side.

***

## Next Steps

* [Authentication](/developers/authentication.md) — set up API tokens for outbound requests
* [MCP Connectors](/developers/workspace-mcp/workspace-mcp-advanced.md#mcp-connectors) — use native integrations instead of raw webhooks for supported services


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.taskade.com/developers/webhooks.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
