---
title: "Keep webhooks - signed real-time notifications for item changes | Keep"
description: "Configure webhooks in Keep to receive signed HTTP notifications when items are saved, updated, tagged, or removed. Verify signatures, scope events, and handle retries."
canonical: "https://keep.md/docs/webhooks"
language: "en"
---

# Send item changes to your own app with webhooks

Configure webhooks in Keep to receive signed HTTP notifications when items are saved, updated, tagged, or removed. Verify signatures, scope events, and handle retries.

Webhooks let Keep notify another app whenever your items change. When something happens (a new item is saved, a tag is added, an item is removed from a collection), Keep sends a signed HTTP `POST` to a URL you control. Use them to mirror your library into another tool, trigger automations, or keep an external system in sync.

Create and manage webhooks from [Connection settings](https://app.keep.md/settings/connections#webhooks).

## Create a webhook

1.  Open [Connection settings](https://app.keep.md/settings/connections#webhooks).
2.  Select **New webhook**.
3.  Enter the URL Keep should call and an optional name.
4.  Choose which events to receive and an optional scope.
5.  Select **Create webhook**.

When the webhook is created, Keep shows the signing secret once. Copy it right away. You use it to verify deliveries, and it is not shown again. If you lose it, rotate the secret to generate a new one.

## Events

A webhook can listen for all events or a specific subset.

| event | when it fires |
| --- | --- |
| item.created | A new item is saved |
| item.updated | An item’s metadata changes |
| item.deleted | An item is deleted |
| item.tagged | A tag is added to an item |
| item.untagged | A tag is removed from an item |
| item.collection\_added | An item is added to a collection |
| item.collection\_removed | An item is removed from a collection |
| item.removed\_from\_scope | An item leaves the webhook’s tag or collection scope |
| webhook.test | A test delivery you triggered from settings |

Selecting **All events** subscribes the endpoint to every event type, including new ones added later.

## Scope

By default a webhook fires for every item in your account. You can narrow it to a single tag or a single collection so you only receive changes that matter to that endpoint. When an item moves out of a scoped webhook’s tag or collection, Keep sends an `item.removed_from_scope` event so you can drop it on your side.

## Payload

Every delivery is a JSON body with this shape:

```
{
  "id": "evt_...",
  "type": "item.created",
  "createdAt": 1709251200000,
  "accountId": "acct_...",
  "data": {
    "eventId": "...",
    "itemId": "a1b2c3",
    "beforeScope": {
      "status": "stashed",
      "tagSlugs": ["reading"],
      "collectionIds": ["col_1"]
    },
    "afterScope": {
      "status": "stashed",
      "tagSlugs": ["reading", "ai"],
      "collectionIds": ["col_1"]
    },
    "payload": {
      "changedFields": ["tags"],
      "tagSlugsAdded": ["ai"]
    }
  }
}
```

`beforeScope` and `afterScope` describe the item’s status, tags, and collections before and after the change. `payload` carries the specifics of what changed. The body references the item by `itemId` and does not include the full content. Fetch it from the [API](https://keep.md/docs/api) with `GET /api/items/:id` when you need the title, markdown, or other fields.

## Verify the signature

Keep signs every request using the [Standard Webhooks](https://www.standardwebhooks.com/) specification. Each delivery includes these headers:

| header | description |
| --- | --- |
| webhook-id | Unique id for this delivery |
| webhook-timestamp | Unix timestamp (seconds) when the request was signed |
| webhook-signature | Signature over the id, timestamp, and raw body |

Verify the signature with your endpoint’s secret before trusting a payload. Any Standard Webhooks library works. For example, in Node:

```
import { Webhook } from 'standardwebhooks'

const wh = new Webhook(process.env.KEEP_WEBHOOK_SECRET)

// `body` must be the raw request body string, not a re-serialized object
const payload = wh.verify(body, {
  'webhook-id': req.headers['webhook-id'],
  'webhook-timestamp': req.headers['webhook-timestamp'],
  'webhook-signature': req.headers['webhook-signature'],
})
```

Use the raw request body for verification. Parsing and re-serializing the JSON can change the bytes and break the signature.

## Test deliveries

Use **Send test event** in settings to send a `webhook.test` delivery to an active endpoint. Recent deliveries and their status appear under each webhook so you can confirm your endpoint is receiving and accepting events.

## Retries and failures

Your endpoint should respond with a `2xx` status within 10 seconds. If it returns another status or times out, Keep retries with increasing delays (after roughly 1 minute, 5 minutes, 15 minutes, and 1 hour) for up to 5 attempts per delivery. After repeated consecutive failures, Keep automatically disables the endpoint. Re-enable it from settings once your endpoint is healthy again.

## Manage webhooks from the API

You can also manage endpoints programmatically with a personal [API key](https://keep.md/docs/api-keys).

```
jq -n --arg url "$KEEP_WEBHOOK_URL" '{
  name: "Research sync",
  url: $url,
  eventTypes: ["*"],
  resourceScope: { tagSlugs: ["ai"] }
}' | curl -X POST https://keep.md/api/webhooks \
  -H "Authorization: Bearer $KEEP_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @-
```

Other endpoints let you list (`GET /api/webhooks`), update (`PATCH /api/webhooks/:id`), pause or resume (set `status`), rotate the secret (`POST /api/webhooks/:id/rotate-secret`), send a test (`POST /api/webhooks/:id/test`), inspect deliveries (`GET /api/webhooks/:id/deliveries`), and delete (`DELETE /api/webhooks/:id`).
