Send item changes to your own app with webhooks
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 your settings.
Create a webhook
- Go to Settings / Integrations / Webhooks.
- Select New webhook.
- Enter the URL Keep should call and an optional name.
- Choose which events to receive and an optional scope.
- 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 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 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.
curl -X POST https://keep.md/api/webhooks \
-H "Authorization: Bearer $KEEP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Research sync",
"url": "https://example.com/keep",
"eventTypes": ["*"],
"resourceScope": { "tagSlugs": ["ai"] }
}'
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).