Webhooks, Zapier and Make
A webhook sends what happens on your boards to an address on the internet: your own server, a Zapier Zap or a Make scenario. Every delivery is a signed POST with IDs only – no titles, no texts. The receiver reads the details through the API with its own token, so that token’s access decides what it may see.
Who sets them up: board admins for their board; workspace owners and admins for every board that the whole workspace can see. A private board needs a webhook of its own, set up by its admins. Confidential boards never send anything.
Set up a webhook
Section titled “Set up a webhook”- Open the webhooks page: on the workspace settings page (your avatar → the workspace) → Webhooks, Zapier and Make → Webhooks; for one board: board header → Settings → Webhooks of this board.
- Add a webhook: a Name (for people), the Address (URL), Which boards – Every board visible to the whole workspace or One board – and the Events.
- Save webhook. Copy the Signing secret now – it is shown only once. Your receiver checks every delivery with it.
- Open the webhook and Send a test event; the Delivery log shows whether it arrived.
The address must be https on the standard port, on a public host. Addresses in private networks (10.x, 192.168.x, localhost, link-local and cloud metadata addresses, also IPv6 and names that point there) are refused – when you save and again at every delivery. Redirects are not followed. The address and the secret are stored encrypted; the page shows only the host and the last four characters.
Zapier and Make
Section titled “Zapier and Make”- Zapier: create a Zap with the trigger Webhooks by Zapier → Catch Hook, copy its address and paste it as the webhook’s address in Contals.
- Make: create a scenario with Webhooks → Custom webhook, copy its address and paste it in Contals.
- To act on the event, add a step that reads the card through the Contals API (for example Zapier’s Webhooks by Zapier → GET with
Authorization: Bearer <token>, using thelinks.cardof the delivery).
Contals apps for both are ready on our side and wait for their listing in the Zapier and Make directories. They connect with a personal API token with the scopes read, write and admin, subscribe by themselves when you switch a Zap or scenario on, unsubscribe when you switch it off, and fill in the card, comment or status update for you. Subscriptions made that way appear on the webhooks page like any other, marked Zapier or Make.
Events
Section titled “Events”| Event | When |
|---|---|
card.created | A card is created |
card.updated | A card’s title or description changes |
card.moved | A card moves to another list |
card.completed / card.reopened | A card is marked done / not done |
card.archived / card.restored | A card is archived / restored |
card.assignees_changed | People are added to or removed from a card |
card.labels_changed | Labels on a card change |
card.due_date_changed | A card gets a new due date (or loses it); data has dueDate and previousDueDate (plain dates), dueAt and previousDueAt (the time as an instant, or null when due all day), startDate and startAt |
comment.added | Someone comments on a card |
checklist_item.completed | A checklist item is done |
list.created / list.archived | A list is created / archived |
status.posted | A board gets a status update |
webhook.test | Send a test event (always delivered) |
Changes made by imports, copies between boards and Contals itself stay quiet; changes through the API, automation and public pages send events.
A delivery
Section titled “A delivery”POST /your/address HTTP/1.1Content-Type: application/jsonUser-Agent: Contals-Webhooks/1.0webhook-id: itg_01jq8m6g1hm5b7e9g1j3m5p7r9:v42webhook-timestamp: 1792137600webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{ "id": "itg_01jq8m6g1hm5b7e9g1j3m5p7r9:v42", "type": "card.created", "format": "contals-webhook/1", "occurredAt": "2026-10-16T08:00:00.000Z", "workspaceId": "wsp_01jq8m1v7hb2f5j9m3q7t1w5y9", "boardId": "brd_01jq8m2x9kd4h7n1p3s5v7w9y1", "boardVersion": 42, "actor": { "type": "person", "id": "usr_01jq8m0t5f9z3d7h1k5n9r3v7x" }, "data": { "cardId": "crd_01jq8m3a5bf9w8x0e2r4t6y8z0", "listId": "lst_01jq8m2zz4c8g2k6m0q4t8w2a6" }, "links": { "board": "https://app.contals.com/api/public/v1/boards/brd_01jq8m2x9kd4h7n1p3s5v7w9y1", "card": "https://app.contals.com/api/public/v1/boards/brd_01jq8m2x9kd4h7n1p3s5v7w9y1/cards/crd_01jq8m3a5bf9w8x0e2r4t6y8z0", "lists": "https://app.contals.com/api/public/v1/boards/brd_01jq8m2x9kd4h7n1p3s5v7w9y1/lists" }}idis the same for every attempt of one delivery: use it to ignore repeats.actor.typeisperson,automation,guest(a public page) orsystem.- Fetch the details from
linkswith your own token. A board the token cannot open answers404.
Check the signature
Section titled “Check the signature”The headers follow Standard Webhooks: webhook-signature holds v1,<signature> – a base64 HMAC-SHA256 of <webhook-id>.<webhook-timestamp>.<body> with the bytes of your secret after whsec_ (base64). Refuse deliveries whose timestamp is more than five minutes old.
import { createHmac, timingSafeEqual } from 'node:crypto'
function verify(secret, headers, rawBody) { const id = headers['webhook-id'], ts = headers['webhook-timestamp'] if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false const key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64') const expected = Buffer.from(createHmac('sha256', key).update(`${id}.${ts}.${rawBody}`).digest('base64')) return headers['webhook-signature'].split(' ').some((part) => { const sig = Buffer.from(part.replace(/^v1,/, '')) return sig.length === expected.length && timingSafeEqual(sig, expected) })}New signing secret on the webhook’s page creates a new secret, shown once. For 24 hours both secrets sign every delivery (the header then carries two signatures), so your receiver can switch without missing one.
Retries, the log and switching off
Section titled “Retries, the log and switching off”- Answer with a
2xxstatus within 10 seconds. Timeouts,429and5xxanswers are retried with growing pauses – after 30 seconds, 2 and 10 minutes, half an hour, 1, 3, 6 and 12 hours – about a day in all; then the delivery is marked Gave up. - Other
4xxanswers and redirects are not retried.410 Goneswitches the webhook off at once (Zapier answers this way for a Zap that was switched off). - After 10 failed deliveries in a row the webhook switches itself off, and its owner gets an e-mail with a link to the delivery log. Switch it on again on its page once the receiver works – that resets the count and makes you its owner.
- The Delivery log keeps the last 100 deliveries of 30 days with status, HTTP code, attempts and the body. Failed, given-up and skipped deliveries can be Sent again.
- A webhook whose owner can no longer manage its boards switches itself off. If its board becomes confidential, it sends nothing more; you can still pause or delete it.
Limits
Section titled “Limits”| Free | Team | Business | |
|---|---|---|---|
| Webhooks per organization | 2 | 25 | 100 |
| Deliveries per day | 500 | 10,000 | 50,000 |
A board can have at most 10 webhooks. Deliveries over the daily limit are skipped and marked in the log. Zapier and Make subscriptions count as webhooks.
For developers: REST hooks
Section titled “For developers: REST hooks”Zapier, Make and your own tools can manage subscriptions through the API with a token that has the admin scope (subscribing is configuration, like sharing a board):
POST /api/public/v1/webhookswithurl,eventsand eitherboardIdorworkspaceId(and an optionalname) – the answer carries the subscription and itssecret, the only time it is shown.GET /api/public/v1/webhooks?workspaceId=…– the subscriptions you manage (never the address or the secret).DELETE /api/public/v1/webhooks/{webhookId}– unsubscribe.
Creating, changing, rotating, sending again and deleting are recorded in the audit log.
See also
Section titled “See also”- API and MCP server – tokens, scopes and every operation
- Chat notifications – board events as readable messages in Slack, Teams, Google Chat or Discord
- Calendar subscriptions – due dates in your calendar app