Webhooks and Slack
Send work-item activity to a Slack channel or to your own HTTPS endpoint with signed JSON payloads.
Skopo can notify other systems whenever work changes in a project. Each project can have any number of hooks:
- Slack: posts a readable message to a channel, such as "Ana moved REQ-12 Fix login to In review", with a link back to Skopo.
- Generic webhook: sends a signed JSON
POSTto your own HTTPS endpoint.
Workspace owners and project leads can manage hooks.
Set up a hook
- Open Workspace → Integrations and scroll to Slack & webhooks.
- Choose the project.
- Pick Slack channel or Generic webhook and paste the URL.
- For Slack, create an incoming webhook for the channel and paste its
https://hooks.slack.com/services/...URL. - Generic URLs must use
https.
- For Slack, create an incoming webhook for the channel and paste its
- Choose which events to send and click Add.
- For a generic webhook, copy the signing secret right away. It's shown only once.
- Click Test to send a sample event. The hook's last delivery status and time appear in the list.
You can pause a hook by clearing Active, or delete it.
Events
| Event | Sent when |
|---|---|
work_item.created | A work item is created |
work_item.updated | Title, description, points, labels, or priority change |
work_item.status_changed | A work item moves to a different workflow state |
work_item.assigned | Assignees change |
work_item.commented | Someone comments |
work_item.removed | A work item is removed |
webhook.test | You click Test |
Generic payload
{
"event": "work_item.status_changed",
"project": { "id": "4f6c...", "publicId": "prj_S8qDRLbDJWbp", "key": "ACME", "name": "Acme web" },
"issue": { "id": "9a1e...", "key": "REQ-12", "title": "Fix login", "displayRef": "#12 Request", "status": "review" },
"changes": { "status": { "from": "inprogress", "to": "review" } },
"comment": null,
"actor": { "id": "user_123", "name": "Ana" },
"url": "https://skopo.dev/p/prj_S8qDRLbDJWbp/dashboard?item=REQ-12",
"occurredAt": "2026-10-16T12:00:00.000Z"
}
Request headers:
| Header | Value |
|---|---|
Content-Type | application/json |
X-Skopo-Event | The event name |
X-Skopo-Signature | sha256= followed by the hex HMAC-SHA256 of the raw request body, keyed with your signing secret |
Verify the signature
Compute the HMAC over the raw body before you parse it, and compare it in constant time:
import { createHmac, timingSafeEqual } from "node:crypto";
function isFromSkopo(rawBody, header, secret) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
return header.length === expected.length && timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}
Delivery
- Skopo sends each event once, right after the change is saved. Failed deliveries aren't retried.
- Your endpoint must respond within 5 seconds. Any
2xxresponse counts as success. - Redirects aren't followed.
- A failed delivery never blocks the change in Skopo.
To read or change work from your own code, use the REST API.