Webhooks

Tell other systems when something happens in Resourcecloud - a task is finished, a comment is added, an incident comes in - so n8n, Make, Zapier or your own service can react. Resourcecloud posts each event as signed JSON to a URL you choose.

Overview

1. Add a webhook

  1. Open Settings → External Services → Webhooks (RC Admins) or the Webhooks card in your team's Team Settings (team lead, deputy). Click Add webhook.
  2. Give it a name, the URL that receives the events (https, a public address) and the events you want - or All events.
  3. Choose Events from: the whole instance, or one team. This cannot be changed later.
  4. Save. The secret is shown once: copy it into the receiver now. If you lose it, use New secret.
  5. Click Test: a ping event goes through the whole path and the page tells you what the receiver answered.

The URL must be reachable from the Resourcecloud server. Addresses in private ranges (10.x, 192.168.x, localhost, cloud metadata) are refused so a webhook cannot be used to probe your own network. A self-hosted instance that really must post into its intranet sets webhook_allow_private in its configuration, or the environment variable RC_WEBHOOK_ALLOW_PRIVATE=1.

2. The events

EventWhenTeam
task.createda task is created (any way: form, quick insert, e-mail, API)the task's team
task.updateda task is changed in the task editor, moved on the Kanban board or changed through the APIthe task's team
task.donea task is finished (Done button, Done lane, API)the task's team
comment.createda comment is added to a task or a projectthe task's team
incident.createdan incident is created (comes in addition to task.created)the incident's team
mention.createdsomebody is mentioned with @username in a comment (one event per person mentioned; only if that person may see the task)the task's team; a project comment: the project's teams
incident.escalatedan incident ran past its reaction time or its escalation time (comes whoever else is notified)the incident's team
project.status_changeda project is requested for approval, approved, rejected, kicked off, cancelled or finishedthe project's own team and the teams that have tasks in it
vacation.approvedleave is approvedinstance-wide webhooks only

A webhook for a team receives only the events of that team's work. A project has no team of its own, so its events go to the webhooks of its project team and of the teams that have tasks in it; leave has no team at all, so only instance-wide webhooks get it.

Every event has the same envelope; data depends on the event:

{
  "id": "5d1f0c2e8a7b4e3f9c6a1b2d3e4f5a6b",
  "event": "task.done",
  "created": "2026-10-11T09:25:13+00:00",
  "instance": "https://your-instance.example.com",
  "data": {
    "id": 4711, "title": "Write the release notes", "type": "Development", "team_id": 3, "project_id": 12,
    "parent_task_id": null, "customer_id": null, "assigned_worker": "anna", "priority": 0, "storypoints": 0,
    "due": "2026-10-20", "deadline": null, "done": true, "done_date": "2026-10-11 11:25:13", "deleted": false,
    "url": "https://your-instance.example.com/edit_workflow?workid=4711",
    "actor": "anna"
  }
}
Eventdata
task.*, incident.createdas above; task.updated adds changed (the fields, when known: always from the API and the Kanban board, ["stage"] for a move), actor is who did it (absent for the system)
comment.createdid, task_id, project_id, author, internal (false = visible to the customer), text (plain text, at most 2000 characters), minutes, created, task_url
project.status_changedid, name, code, status, state, done, project_manager, url, actor; status is planning, approved, rejected, in_progress, delayed, waiting, waiting_for_approval, canceled or done
incident.escalatedas a task, plus violation: reaction or escalation
mention.createdcomment_id, mentioned (username), by, text (at most 2000 characters), task_id, task_title, project_id, team_id, url
vacation.approvedid, worker, substitute, begin_date, end_date, days, type, approved_by
pingmessage, subscription_id - only from the Test button

The description of a task and the history of its comments are not sent. A receiver that needs more reads it from the REST API with the id.

Private tasks never produce events - neither for the task nor for its comments. An integration is not a party of a private task.

3. Check the signature

Every request carries:

HeaderMeaning
X-RC-Eventthe event name
X-RC-Deliverythe event id - the same on every retry; remember it if your reaction must not run twice
X-RC-Attempt1 for the first try, 2 for the first retry ...
X-RC-Signaturet=<unix time>,v1=<hex> where v1 is the HMAC-SHA256, with the webhook's secret as key, of t + "." + the raw request body

A receiver recomputes the HMAC from the raw body (before any JSON parsing), compares it in constant time and rejects a t older than five minutes.

PHP

$body = file_get_contents('php://input');
sscanf($_SERVER['HTTP_X_RC_SIGNATURE'], 't=%d,v1=%s', $t, $sig);
$ok = abs(time() - $t) <= 300
   && hash_equals(hash_hmac('sha256', $t . '.' . $body, $secret), $sig);

Node.js

const crypto = require('crypto');
const [, t, sig] = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(req.headers['x-rc-signature']);
const expected = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
const ok = Math.abs(Date.now() / 1000 - t) <= 300
        && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));

Python

import hmac, hashlib, re, time
t, sig = re.match(r't=(\d+),v1=([0-9a-f]{64})$', headers['X-RC-Signature']).groups()
expected = hmac.new(secret.encode(), (t + '.').encode() + raw_body, hashlib.sha256).hexdigest()
ok = abs(time.time() - int(t)) <= 300 and hmac.compare_digest(expected, sig)

After New secret the old secret stops signing at once - change the receiver first or accept a short gap.

4. Delivery, retries, log

5. n8n

The file docs/webhooks/n8n-task-done.json is a ready flow: Webhook → Verify signature → Task finished? → What was finished (title, who, team, link) → a placeholder node for your own step.

  1. In n8n choose Import from file and pick the JSON.
  2. Open Verify signature and replace PASTE-YOUR-WEBHOOK-SECRET-HERE with the secret. n8n must allow the crypto module: start it with NODE_FUNCTION_ALLOW_BUILTIN=crypto.
  3. Open the Webhook node and copy its Production URL into a new webhook in Resourcecloud, event task.done. Publish (activate) the flow.
  4. Click Test in Resourcecloud, then finish a task. The flow runs; the ping is filtered out by Task finished?.

Replace the last node with what you need: a Slack or Teams message, a row in a sheet, a ticket in another tool. Change the condition in Task finished? for other events. The flow answers immediately (Respond: immediately); a signature that does not match shows up as a failed execution in n8n.

6. Make and Zapier

Make: add a Webhooks → Custom webhook module, copy its address into a Resourcecloud webhook, click Test so Make learns the structure, then build the scenario on it. Make cannot check the signature by itself; keep the address secret, or add a Tools step that computes the HMAC (see above).

Zapier: create a Zap with the trigger Webhooks by Zapier → Catch Hook, copy the Custom Webhook URL into a Resourcecloud webhook and click Test. The fields of data then show up as data title, data assigned_worker ... in the next steps. Zapier too does not check signatures on its own; a Code by Zapier step can.

7. Slack and Teams

A webhook can post a ready-made message into a chat channel instead of sending JSON. Choose Send to: Slack channel or Microsoft Teams channel when you add it. The webhook is then for one team's channel (choose the team under Events from) - or for the whole instance, if one channel should hear everything.

  1. Slack: create an Incoming Webhook for the channel (Slack app → Incoming Webhooks → Add New Webhook to Workspace) and copy its URL (https://hooks.slack.com/services/...).
  2. Teams: in the channel choose Workflows and the template Post to a channel when a webhook request is received (When a Teams webhook request is received), and copy the URL it shows. Microsoft is retiring the older "Incoming Webhook" connector; use Workflows.
  3. In Resourcecloud add a webhook, pick the type, paste the URL, choose the team and save. A channel starts with the four events it is for: new incidents, escalations (an incident ran past its reaction or escalation time), mentions (@name in a comment) and project approvals (a project waits for approval, is approved or rejected, starts, is cancelled or finished). Change them if you like.
  4. Click Test: the channel gets a test message.

The message says what happened in one line, with the team, assignee and due date as facts and a link Open to the task or project. It is written in the instance's default language. The address is the only secret there is - anyone who has it can post to the channel - so a Slack or Teams webhook is not signed and has no New secret. Retries, the log and the rules of section 4 are the same.

Task changes, new comments and "task finished" can be switched on per channel in the same list. A mention in a private task never reaches a channel.

8. REST API and MCP

Everything on the page is in the API (openapi.yaml, tag Webhooks): GET /webhooks/events, GET|POST /webhooks, GET|PUT|DELETE /webhooks/{id}, POST /webhooks/{id}/test, POST /webhooks/{id}/rotate-secret, GET /webhooks/{id}/deliveries, POST /webhooks/{id}/deliveries/{delivery_id}/redeliver. The same rules apply: RC Admin, or lead/deputy for the team's webhooks. The secret is in the answer of the POST that creates it, and of rotate-secret, and nowhere else (a Slack or Teams webhook, "format": "slack" or "teams", has none).

curl -u USER:TOKEN -X POST https://YOUR-INSTANCE/api/v1/webhooks \
  -H 'Content-Type: application/json' \
  -d '{"name": "n8n", "url": "https://n8n.example.com/webhook/resourcecloud", "events": ["task.done"], "team_id": 3}'

The hosted MCP endpoint has the read tools list_webhook_events, list_webhooks and list_webhook_deliveries. Creating a webhook (and so a secret) is left to the page and the REST API.

Rules and limits