> For the complete documentation index, see [llms.txt](https://docs.kinwall.family/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kinwall.family/integrations/webhooks.md).

# Webhooks

Kinwall can POST to your URL whenever something changes. Every change also bumps `GET /api/rev` (a `calendar.synced` that found nothing new doesn't, since nothing changed).

## Create one

* **UI**: **Settings → Access → Webhooks → New webhook**, then pick a URL and events.
* **API**: `POST /api/webhooks {url, events, secret?, enabled?}`. An empty `events` array means **all events**. Use an admin API key: a [connected app](/integrations/mcp.md#what-connected-apps-cant-do)'s sign-in can't create, change, rotate or delete webhooks (403).

If you don't pass a `secret`, Kinwall generates a random one. Either way, the create response includes the plain `secret` **once**; the UI shows it in a copyable field with the note "Shown once. Use it to verify the X-Kinwall-Signature header." Copy it then: secrets are encrypted at rest and never returned by `GET /api/webhooks` or anything else.

## Rotate the secret

Lost the secret, or want a new one? **Rotate secret** next to the webhook (or `POST /api/webhooks/{id}/rotate`) generates a new one and shows it once. The old secret stops working immediately, so update your receiver. Each rotation is logged on the server (`webhook <id> secret rotated`).

URLs must be public `http(s)` addresses. Private and LAN addresses are refused, and on a Node server (Docker, the Home Assistant add-on) so is a name that resolves to one, checked when Kinwall connects. Redirects aren't followed. Both are allowed when the server runs with `ALLOW_PRIVATE_WEBHOOK_URLS=1` (the Home Assistant add-on sets it, since Home Assistant is the receiver). `ALLOW_PRIVATE_FEED_URLS` does not affect webhooks.

## Events

| Type                       | Fired when                                                                                                                                                                                                                                                                                                                          |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `member.changed`           | A member is added, edited or removed.                                                                                                                                                                                                                                                                                               |
| `calendar.changed`         | A calendar is added, edited or removed.                                                                                                                                                                                                                                                                                             |
| `calendar.synced`          | A calendar finished syncing and something changed, or you pressed Sync now (includes `error` on failure; a background sync repeating the same error doesn't send it again).                                                                                                                                                         |
| `events.changed`           | Events were created, edited or deleted, including by a sync that brought in changes.                                                                                                                                                                                                                                                |
| `chore.changed`            | A chore is added, edited or deleted, or a parent resets the play time of an [activity chore](/using-kinwall/chores.md#activity-chores).                                                                                                                                                                                             |
| `chore.completed`          | A chore is marked done for a date (for a chore that needs a parent's OK, when it's approved). `data`: `{ id, date, title, memberId, points }`. `memberId` is who gets the credit (for an Anyone chore, the person it was ticked off for, or `null`), and `points` is what was awarded, so late completions show the reduced amount. |
| `chore.uncompleted`        | A completion is undone. `data`: `{ id, date, title, memberId }`.                                                                                                                                                                                                                                                                    |
| `chore.pending`            | A chore that needs a [parent's OK](/using-kinwall/chores.md#parent-approval) was ticked on a wall screen or kid's device and is waiting. `data`: `{ id, date, title, memberId }`. `chore.completed` follows when a parent approves it.                                                                                              |
| `chore.rejected`           | A parent said **Not yet** to a waiting chore: the tick is removed. `data`: `{ id, date, title, memberId, note }` (`note` may be `null`).                                                                                                                                                                                            |
| `chore.suggestion.changed` | A kid [suggested a chore](/using-kinwall/chores.md#kids-suggest-chores), a parent answered it, or the kid put the answer away. `data`: `{ id, memberId, status }` (`pending`, `approved` with `choreId`, `declined` or `seen`).                                                                                                     |
| `chore.library.changed`    | A [chore library](/using-kinwall/chores.md#chore-library) item is added, edited or removed. `data`: `{ id }`. Handing one out sends `chore.changed` with `{ id, libraryId }`.                                                                                                                                                       |
| `tempcheck.changed`        | Someone answered or changed their [Temp check](/using-kinwall/snapshot.md#temp-check), including the evening goal check. `data`: `{ memberId, date }`, never the answers.                                                                                                                                                           |
| (no medication events)     | [Medications](/using-kinwall/medications.md) never send a webhook, not even an ID.                                                                                                                                                                                                                                                  |
| `journal.changed`          | A [journal](/using-kinwall/journal.md) entry was added, changed or deleted. `data`: `{ memberId, date, id }`, never the words.                                                                                                                                                                                                      |
| `checkin.completed`        | Someone checked in for the day from their [snapshot](/using-kinwall/snapshot.md#daily-check-in) (sent once per person per day). `data`: `{ memberId, date, points }`.                                                                                                                                                               |
| `list.changed`             | A list is created, edited, archived, deleted, cleared, reset or reordered.                                                                                                                                                                                                                                                          |
| `list.item.changed`        | A list item is added, edited, ticked or deleted.                                                                                                                                                                                                                                                                                    |
| `category.changed`         | A category is added, edited, reordered or deleted.                                                                                                                                                                                                                                                                                  |
| `settings.changed`         | Household settings changed.                                                                                                                                                                                                                                                                                                         |
| `sticker.changed`          | A sticker pack is bought, or the scrapbook is edited.                                                                                                                                                                                                                                                                               |
| `reward.changed`           | A [reward](/using-kinwall/rewards.md) is added, edited, archived or deleted. `data`: `{ id }`. Also when a kid cancels a pending request: `{ id, redemptionId, memberId, canceled: true }`.                                                                                                                                         |
| `reward.redeemed`          | Someone spent points on a reward. `data`: `{ id, rewardId, memberId, title, emoji, cost, status }`, where `id` is the request and `status` is `pending` (waiting for a parent's OK) or `approved`. A split adds `shares`: `[{ memberId, points }]`, the asker first.                                                                |
| `reward.approved`          | A parent approved a waiting reward. `data`: `{ id, rewardId, memberId, title, emoji, cost }`, plus `shares` for a split.                                                                                                                                                                                                            |
| `reward.declined`          | A parent said **Not this time**, or canceled an approved one; the points went back. `data`: the same plus `note` (may be `null`).                                                                                                                                                                                                   |
| `reward.given`             | A parent marked an approved reward as given. `data`: `{ id, rewardId, memberId, title, emoji, cost }`.                                                                                                                                                                                                                              |
| `points.awarded`           | A parent gave someone [bonus points](/using-kinwall/chores.md#bonus-points). `data`: `{ id, memberId, points, note, date }` (`note` may be `null`; `date` is the day they count on).                                                                                                                                                |
| `points.removed`           | A parent took back bonus points. `data`: `{ id, memberId, points }`.                                                                                                                                                                                                                                                                |
| `photo.changed`            | A photo is added, captioned, reassigned or deleted. `data`: `{ id }` (plus `deleted: true` on delete), or `{ imported }` after a zip import.                                                                                                                                                                                        |
| `newscast.posted`          | Someone shared a [Newscast](/using-kinwall/newscast.md) announcement for everyone. `data`: `{ id, memberId, text, emoji, photoId, audience }`. Grown-ups-only posts never send it. Enough for "announce it on the kitchen speaker".                                                                                                 |
| `newscast.changed`         | A Newscast post was removed or deleted, a grown-ups-only post was shared, or someone reacted. `data`: `{}`.                                                                                                                                                                                                                         |
| `plugin.action`            | An [activity action](/contributing/plugins.md#actions) was queued, like a spelling list for Spelling practice. `data`: `{ id, pluginId, memberId, action }` (`memberId` is `null` for the family's shared data), never the input.                                                                                                   |
| `tracker.changed`          | A [tracker](/using-kinwall/nooks/trackers.md) entry is added, edited or deleted. `data`: `{ id, kind }` (plus `deleted: true`), never the entry's fields.                                                                                                                                                                           |
| `recipe.changed`           | A [recipe](/using-kinwall/nooks/meals.md) is added, edited, archived or deleted. `data`: `{ id }`.                                                                                                                                                                                                                                  |
| `meal.changed`             | A meal is planned, edited or deleted, or someone's order for it changed. `data`: `{ id }`.                                                                                                                                                                                                                                          |
| `restaurant.changed`       | A [restaurant](/using-kinwall/nooks/meals.md#restaurants) is added, edited (its menu or favorites too, or by an [import from a phone](/using-kinwall/nooks/meals.md#add-restaurants-from-your-phone) that changed something), archived or deleted. `data`: `{ id }`.                                                                |
| `upkeep.changed`           | A [home upkeep](/using-kinwall/nooks/upkeep.md) job, thing, area or Done was added, changed or deleted, a job was snoozed or skipped, or a file was added or removed. `data`: `{ id, kind }` (`kind`: `job`, `thing`, `area` or `log`; plus `deleted: true`).                                                                       |
| `upkeep.done`              | An upkeep job was marked done (also when a kid ticks off its chore). `data`: `{ id, jobId, thingId, title, doneOn, memberId }`, where `id` is the Done; never the cost, reading or notes.                                                                                                                                           |
| `upkeep.due`               | The morning's heads-up, due or late note went out for an upkeep job, once per job and due date. `data`: `{ jobId, title, dueOn, status }` (`soon`, `due` or `overdue`). Enough for "flash the hall light".                                                                                                                          |
| `outing.changed`           | An [outing](/using-kinwall/nooks/outings.md) or place was added, changed, marked (👀 / ⭐), put on the calendar or deleted, or its categories changed. `data`: `{ id }` (or `{ categoryId }`), never who marked what.                                                                                                                |
| `poll.changed`             | A [family poll](/using-kinwall/polls.md) was started, voted in, closed, linked to a meal or deleted. `data`: `{ id, status }` (`open`, `closed` with `winnerOptionId`, or `deleted`), never who voted for what.                                                                                                                     |
| `display.paired`           | A wall display was paired.                                                                                                                                                                                                                                                                                                          |
| `display.night_screen`     | The [Night screen](/using-kinwall/night.md#start-it-from-home-assistant) was started or ended remotely. `data`: `{ on, displays, until }`, where `displays` is the display key IDs or `null` for every wall screen, and `until` is when "on" runs out (`null` for off).                                                             |
| `contact.changed`          | A [contact](/using-kinwall/contacts.md) is added, edited, deleted, imported or merged. `data`: `{ id, action }`, where `action` is `created`, `updated`, `deleted`, `imported` or `merged`.                                                                                                                                         |
| `contact.category.changed` | A contact category is added, edited or deleted. `data`: `{ id }`.                                                                                                                                                                                                                                                                   |

## Payload and signature

```http
POST /your/endpoint
Content-Type: application/json
X-Kinwall-Signature: sha256=<hex HMAC-SHA256 of the raw body, keyed with the secret>

{"type":"chore.completed","data":{...},"at":"2026-09-24T18:02:11.000Z"}
```

To verify, compute the HMAC-SHA256 of the **raw request body** with your secret and compare it to the header in constant time:

```js
import { createHmac, timingSafeEqual } from 'node:crypto';
const expected = 'sha256=' + createHmac('sha256', SECRET).update(rawBody).digest('hex');
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-kinwall-signature']));
```

## Delivery

* Webhooks are sent in the background after the change is saved, with a **5-second timeout**.
* There are **no retries**. If you need every change, poll `GET /api/rev` as a backstop.
* A disabled webhook (`enabled: false`) is skipped.

### Reward milestones

* `reward.milestone.changed { id }`: created, archived, or restored.
* `reward.milestone.earned { id, memberId, title, reward }`: this member reached the target, once.
* `reward.milestone.given { id, memberId }`: a grown-up marked the earned reward given, once.

These events advance the `chores` revision. They are distinct from purchased reward events such as `reward.redeemed`; an existing Home Assistant automation for purchased rewards will not run just because someone reaches a milestone. No new Home Assistant entities or native widgets are added.

### Family announcements

`announcement.changed` is emitted when an announcement is created, edited, or deleted, with `{id}`. Import emits `{imported}` with the restored count. The event bumps the global revision. A scheduled start or expiration is a clock boundary, not a data change: it does not emit a webhook. Readers can use `GET /api/announcements`'s `nextChangeAt` to refresh at the boundary. Turning the feature off prevents announcement mutations and their webhooks.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.kinwall.family/integrations/webhooks.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
