Receiving webhooks

Have new items pushed to your own endpoint the moment they are saved. An item becoming searchable (or failing to), and an item being deleted, cannot be learned by polling and arrive only this way. Set the destination up on your account page.

Headers

Sent on every delivery. Verifying them is optional.

HeaderExampleWhat it is
webhook-idmsg_2KWPBg…Idempotency key. Identical across all retries of one event.
webhook-timestamp1614265330Unix seconds. Check it is recent to reject replays.
webhook-signaturev1,g0hM9SsE…HMAC-SHA256 of the body, base64.
content-typeapplication/jsonAlways this.
your auth header nameyour auth header valueSent too, if you registered one.
POST <your endpoint>
content-type: application/json
webhook-id: msg_2KWPBgLlAfxdpx2AI54pPJ85f4W
webhook-timestamp: 1614265330
webhook-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=

Body

The envelope never changes; type decides the shape of data.

typedataWhen
note.createdone item, including its contentAn item is stored. Not the same as searchable — read status.
note.ready{ count, group_id?, items[] }Stored items become searchable. One event per group, per processing pass.
note.failed{ count, group_id?, items[] }Stored items could not be made searchable. They are still there.
note.deleted{ count, items[{id}] }Items are removed. Ids only — they are already gone.
webhook.ping{}You press Test connection.

A single save

{
  "type": "note.created",
  "timestamp": "2026-09-05T04:12:07Z",
  "data": {
    "id": 41,
    "title": "…",
    "summary": "…",
    "content": "…",
    "created_at": "2026-09-05T04:11:58Z",
    "origin": "Claude",
    "status": "ready",
    "group_id": null
  }
}

One part of an upload

{
  "type": "note.created",
  "timestamp": "2026-09-05T04:12:09Z",
  "data": {
    "id": 42,
    "title": "… (3/77, p.47–53)",
    "summary": "…",
    "content": "…",
    "created_at": "2026-09-05T04:12:08Z",
    "origin": "Upload",
    "status": "pending",
    "group_id": "9f2c1ab84e7d4f0b93c5a61d2e8b7c40"
  }
}

Parts of an upload became searchable

{
  "type": "note.ready",
  "timestamp": "2026-09-05T04:31:12Z",
  "data": {
    "count": 2,
    "group_id": "9f2c1ab84e7d4f0b93c5a61d2e8b7c40",
    "items": [
      { "id": 42, "title": "… (3/77, p.47–53)", "status": "ready" },
      { "id": 43, "title": "… (4/77, p.54–60)", "status": "ready" }
    ]
  }
}

Parts of an upload could not be made searchable

{
  "type": "note.failed",
  "timestamp": "2026-09-05T04:36:40Z",
  "data": {
    "count": 2,
    "group_id": "9f2c1ab84e7d4f0b93c5a61d2e8b7c40",
    "items": [
      { "id": 44, "title": "… (5/77, p.61–67)", "status": "failed" },
      { "id": 45, "title": "… (6/77, p.68–74)", "status": "failed" }
    ]
  }
}

One file can end up partly searchable and partly not: both events then arrive seconds apart with the same group_id.

Items were deleted

{
  "type": "note.deleted",
  "timestamp": "2026-09-05T05:02:44Z",
  "data": { "count": 2, "items": [{ "id": 42 }, { "id": 43 }] }
}

The connection test

{
  "type": "webhook.ping",
  "timestamp": "2026-09-05T05:10:00Z",
  "data": {}
}

Single saves and uploads

Both flows use the same event types; status and group_id tell you which one you have.

A single save — from MCP, the REST API or your dashboard. One note.created, and it is searchable the moment it arrives.

An upload — the file is split into parts, and every part gets its own note.created. None of them is searchable yet; note.ready follows once they are.

A single saveAn upload (one file)
note.createdone, status: "ready", group_id: nullone per part, status: "pending", with a group_id
note.readynever arrivesarrives as parts become searchable
note.failednever arrivesarrives if a part could not be made searchable
Searchable fromnote.creatednote.ready
Readable fromstraight awaystraight away — only search waits
Part numbernot a field. Only the (3/77) in the title, and the order of data.id
  • status — whether the item is searchable. "ready" means it is; "pending" means it is still being prepared; "failed" means preparing it did not succeed, so search will not find it.
  • group_id — the name of a bundle. It is issued when you upload a file and every part of that file carries the same value. It says which file a part belongs to, not which part it is, and it is the key that links a note.created to the note.ready that follows.
  • If you only want items you can search — act on note.ready and skip any note.created whose status is "pending".
  • A pending part is readable straight away — its body is already in the payload. To read it again use GET /api/v1/notes/{id}, which does not count as an opening, or GET /api/v1/notes/group/{group_id} for the whole bundle.
  • note.ready does not arrive once per file — each one carries the parts that became ready together, so a file whose processing is split, or a part that had to be tried again, produces several events with the same group_id. count is what that event carried, not the file's total, so accumulate items.
  • note.failed does not mean the item was deleted — it is stored and readable, and only preparing it for search failed. Do not remove it from your copy; deletion has its own event, note.deleted. Press Retry on your dashboard to try again, and if that attempt fails too a new note.failed arrives.

Writing a receiver

Answer 2xx straight away and do the work afterwards, on your own queue.

Do thisOr else
Return 2xx immediately, process laterWe time out at 25 seconds and deliver again.
Deduplicate on webhook-idYou process the same item twice.
Answer a duplicate with 200, never 429429 means “slow down”, so we send it again.
Sort by data.idA retry can overtake a later event; order is not guaranteed.
Register the final URLWe do not follow redirects — a 3xx ends the delivery.

Verifying the signature (optional)

Confirms the delivery came from us and that the body was not altered on the way.

  1. Build the signed string: {webhook-id}.{webhook-timestamp}.{raw body}
  2. Derive the key: strip the whsec_ prefix from your secret, then base64-decode the rest
  3. HMAC-SHA256, base64-encode, and compare against the part after v1,
  4. Check webhook-timestamp is within a few minutes of now
  5. Verify the raw bytes you received — parsing the JSON and re-serialising it breaks the signature
  6. Any Standard Webhooks library (standardwebhooks.com) does steps 1–5 for you

Your signing secret is on the account page, where you can copy or replace it.

Retries

If a delivery fails we send it up to three more times — four attempts in all. Only 2xx counts as delivered.

ResponseWhat we do
2xxDone.
408 · 429 · 5xxRetry after 15 minutes, then 1 hour, then 3 hours.
No response at allRetry on the same schedule.
3xx and every other 4xxStop here — the answer will not change.

Deliveries are never switched off automatically. While it keeps failing we email you at most once a day, and your account page shows the failure run and the last error.

If your receiver was down for more than about four hours, catch up with GET /api/v1/notes/changes.

More

Registering a destination, the signing secret and the connection test are all on your account page. Keys, endpoints and errors for the REST API are in the REST API docs. Common questions are answered in the FAQ, and if something is unclear or broken, get in touch — a real person reads every message.