# Webhooks, Zapier and Make

> Send board events – new cards, moves, completed cards, comments, status updates – to your own server, Zapier or Make. Deliveries are signed and carry IDs only; a delivery log shows each one, and failed ones can be sent again.

Source: https://contals.com/docs/en/integrations/webhooks/  
Last updated: 2026-10-16

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](/docs/en/ai/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

1. 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**.
2. **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**.
3. **Save webhook**. Copy the **Signing secret** now – it is shown only once. Your receiver checks every delivery with it.
4. 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

- **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 the `links.card` of 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](/docs/en/ai/api/#authentication) 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

| 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

```http
POST /your/address HTTP/1.1
Content-Type: application/json
User-Agent: Contals-Webhooks/1.0
webhook-id: itg_01jq8m6g1hm5b7e9g1j3m5p7r9:v42
webhook-timestamp: 1792137600
webhook-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"
  }
}
```

- `id` is the same for every attempt of one delivery: use it to ignore repeats.
- `actor.type` is `person`, `automation`, `guest` (a public page) or `system`.
- Fetch the details from `links` with your own token. A board the token cannot open answers `404`.

## Check the signature

The headers follow [Standard Webhooks](https://www.standardwebhooks.com/): `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.

```js
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

- Answer with a `2xx` status within 10 seconds. Timeouts, `429` and `5xx` answers 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 `4xx` answers and redirects are not retried. `410 Gone` switches 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

| | 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

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/webhooks` with `url`, `events` and either `boardId` or `workspaceId` (and an optional `name`) – the answer carries the subscription and its `secret`, 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](/docs/en/settings/audit-log/).

## See also

- [API and MCP server](/docs/en/ai/api/) – tokens, scopes and every operation
- [Chat notifications](/docs/en/power-ups/integrations/) – board events as readable messages in Slack, Teams, Google Chat or Discord
- [Calendar subscriptions](/docs/en/views/across-boards/) – due dates in your calendar app
