Overview
Webhooks connect Taskade automations to external services 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 (for example, task completed) with an outbound HTTP action to push events to your app.
Two ways to receive Taskade events:
- No-code: build an automation with a Taskade trigger plus an HTTP action. See Receiving Taskade Events.
- Programmatic: register a signed outbound webhook with
POST /api/v2/webhooks. See the Webhook Registration API.
When your integration needs registrations managed in code and verifiable HMAC deliveries, use the signed API. Its events are the explicit list in Supported events.
The no-code route is broader. Automation triggers also cover events such as task added, task completed, due-date changes, custom-field updates, schedules, and connected services. You choose the outgoing payload in the HTTP action.
Inbound Webhooks
Receive data from an external service to start a Taskade automation.
How It Works
- Create a webhook trigger in an automation.
- Copy the URL from the Webhook URL field in the trigger panel.
- Enable the automation. Taskade then generates the authorization token. See Authentication.
- Configure your external service to POST JSON data to the URL, with the token in the
Authorizationheader. - Use the webhook payload as dynamic data in subsequent actions.
Taskade generates one webhook URL per automation. The URL has the formhttps://www.taskade.com/webhooks/flow/<automation-id>/sync. It identifies the automation, so it does not change.Webhook triggers run on Pro and above. On Free and Starter, a call returns
402withcode: "PAYMENT_REQUIRED", and the automation does not run.
Payload Structure
The webhook accepts a JSON body (application/json) or multipart/form-data. Taskade rejects any other content type with 400. A JSON body larger than about 1.1 MB returns 413. Taskade applies the limit after it encodes the body, so the message of a PAYLOAD_TOO_LARGE error states about 1.5 MB. Each payload field becomes a dynamic variable for subsequent actions.
Example payload:
{
"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
A webhook trigger requires a Bearer token by default. When you enable the automation, Taskade generates the token and rejects every call that does not send it. The token is the secret that protects the webhook, because the URL does not change.
Get the token:
- Open the webhook trigger panel in your automation.
- Make sure that Require Authorization Token is on.
- Enable the automation. Taskade generates the token.
- In the Authorization Token field, click Show or Copy.
Calling the webhook:
Every inbound request must include the token in the Authorization header:
POST https://www.taskade.com/webhooks/flow/<automation-id>/sync
Authorization: Bearer your_webhook_token_placeholder
Content-Type: application/json
{
"event": "form_submitted",
"name": "Jane Doe"
}
Taskade returns 401 Unauthorized for a request with a missing or incorrect token. The automation does not run.
To accept calls without a token, turn off Require Authorization Token. The panel then warns that anyone with the URL can trigger the automation. If you do this, validate each payload in your automation logic. For example, require an expected event value.
Treat the authorization token like a password. Do not share it publicly. Do not commit it to source control.
Return a response to the caller
The URL ends in /sync. If the automation defines a Webhook Response output, the call waits for the run and returns that response. Otherwise, Taskade returns 200 with an empty body as soon as it accepts the call.
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 and others) | Sync contact data to a Taskade project |
Outbound HTTP Requests
For outbound communication, use the Send HTTP Request action in an automation. It can call an external API. Later steps can use the response.
Configuration
| Setting | Details |
|---|---|
| Method | GET, POST, PATCH, PUT, DELETE, HEAD |
| URL | The external endpoint |
| Headers | Request headers such as Authorization and Content-Type |
| Query Parameters | Values added to the URL query string |
| Body Type | none, json, raw, formData, or multipart |
| Response Type | No parsed body, or JSON |
| Timeout (ms) | Maximum wait. Invalid, zero, or negative values use 30 seconds |
| Follow Redirects | Off by default. When enabled, Taskade follows up to five safe redirects |
| Return Status On Error | Return the response and status instead of failing on a non-2xx response |
Response data from the HTTP request becomes dynamic variables in subsequent automation steps. Use these variables to chain API calls.
Choose a body type
Match the body mode to the API contract.
| Body type | What Taskade sends | Use it for |
|---|---|---|
none |
No request body | GET, HEAD, or another bodyless request |
json |
A JSON value or supported structured value | JSON APIs |
raw |
The configured body without JSON conversion | Text, XML, or another raw payload |
formData |
Object fields as form data | Simple form submissions |
multipart |
Validated multipart fields and files | File uploads and mixed form data |
JSON mode sets Content-Type: application/json when you do not provide another content type. If the body is text, enter valid JSON when the receiving API expects JSON.
Use none for a request without a body. Do not send an empty JSON object as a placeholder.
Insert dynamic values with the variable controls in the automation editor. The editor writes the supported expression into the field.
Example: Post to an External API
| Setting | Value |
|---|---|
| Method | POST |
| URL | https://api.example.com/notifications |
| Headers | Content-Type: application/jsonAuthorization: Bearer your_api_token_placeholder |
Body:
{
"channel": "#alerts",
"text": "New task created: {{task.name}}"
}
Webhook Registration API
Registration returns a signing secret exactly once, so store it before anything else. The authoritative schema is the live Action API v2 spec.
Register signed outbound webhooks through the public API. You do not need an automation.
Taskade sends the event payload with POST. It signs every delivery with an HMAC secret.
The programmatic event list is smaller than the no-code automation trigger catalog.
This API supersedes the unsigned subscribeWebhook and unsubscribeWebhook operations. Those operations still work and are deprecated. See Legacy unsigned subscriptions.
Register a webhook
POST /api/v2/webhooks takes one target URL, one or more events, and optional workspace scoping:
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):
{
"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. When you use it as a path parameter, URL-encode it.
Supported events
The events array accepts only these six names. Any other value returns 400:
| 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 |
Delivery payload
The delivery body is the event's own object. There is no event-name envelope. Branch on the fields you require rather than on a type discriminator:
| Event | Top-level fields |
|---|---|
task.due |
spaceName, spaceId, projectName, projectId, id, text, isCompleted, assignees, taskStartDate, taskStartTime, taskStartTimezone, taskEndDate, taskEndTime, taskEndTimezone |
task.assigned |
projectName, projectId, assignerName, assignedNodes[] (each nodeId, nodeText, isCompleted, assignees) |
comment.created |
projectName, projectId, nodeId, nodeText, commenterDisplayName, commenterHandle, commentBody, commentBodyType, assignees, mentionedHandles |
project.created |
spaceName, spaceId, projectName, projectId, creatorName |
project.assigned |
spaceName, spaceId, projectName, projectId, assignerName, assigneeName, assigneeId |
project.joined |
spaceId, projectName, projectId, joinerName, joinerUserId |
When your handler needs to know which event arrived, register one URL per event. Otherwise, key off a field that only one event carries.
Scope to specific workspaces
"spaceIds": [] (the default) delivers matching events from all your workspaces. Pass workspace ids to receive events from only those workspaces:
{
"targetUrl": "https://your-app.example.com/hooks/taskade",
"events": ["project.created"],
"spaceIds": ["SPACE_ID_1", "SPACE_ID_2"]
}
List, inspect, delete
# 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": <boolean> }. deleted is false when no webhook matched that id. The status is 200 either way.
GET /api/v2/webhooks/{id} returns { "ok": true, "webhook": { ... } }.
Validate delivery signatures
Every delivery carries an X-Taskade-Signature header. Its value is sha256= followed by the hex HMAC-SHA256 of the raw request body, keyed with your webhook's secret:
X-Taskade-Signature: sha256=5f4dcc3b5aa765d61d8327deb882cf99...
Before JSON parsing, recompute the HMAC over the raw body. Compare the signatures in constant time:
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 each delivery whose signature does not match.
Limits & requirements
| Rule | Detail |
|---|---|
| Plan | Pro or above, otherwise 402 with code: "PAYMENT_REQUIRED". Deleting is always allowed, so a downgraded account can still clean up. |
| Account | The account email must be verified, otherwise 403 with code: "FORBIDDEN". |
| Target URL | Must be https (deliveries use an SSRF-guarded fetch). Max 2,048 characters once URL-encoded, because the encoded URL is the :id path parameter. |
| Limit | 100 event-workspace combinations per account. One webhook with 3 events scoped to 2 workspaces counts as 6. |
| Scope | All workspaces by default. Narrow the scope with spaceIds. |
| Dashboard | You can also create and manage outgoing webhooks in Settings > API. |
Legacy: unsigned subscriptions (deprecated)
The subscribeWebhook and unsubscribeWebhook operations still work, but they are deprecated.
Each subscription covers one event. Its scope is account-wide. Deliveries are not signed.
Use POST /api/v2/webhooks for new integrations. To migrate, register the same URL there. Then remove the old subscription.
Receiving Taskade Events
To send Taskade events to your app, build an automation with two ends. Start it with a Taskade trigger, such as a new task or a completed task.
End it with an Outbound HTTP Request 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:
{
"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:
{
"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" } ]
}
Task custom-field values appear as additional keys. Other triggers use the same pattern.
See the Action & Trigger Reference for new comments, dates, project completion, and schedules.
Rate Limits
Taskade can throttle excessive inbound webhook calls. For high-volume traffic, batch events. You can also add a queue on the sender.
Next Steps
- Authentication - set up API tokens for outbound requests
- Native integrations - Use a built-in connector for supported services.
- Outbound MCP - Use an MCP Client connector for another MCP server.