A webhook sends a POST to your endpoint each time something happens in your project, such as a new post or a message from a user. Use it to tell your other tools. Webhooks work on every plan; owners and admins set them up.

Add a webhook

  1. Open Settings → Developers.

  2. Under Webhooks, enter your endpoint's address. It must start with https://.

  3. Untick the events you don't want; they're all ticked to start with. Pick at least one.

  4. Choose Add webhook.

The Webhooks card: the endpoint field, the seven events with their names, and two webhooks with their latest delivery

Copy the signing secret straight away with Copy secret: it's shown only once. You'll need it to check that requests come from Tuti.

A project can have up to 10 webhooks. A webhook's address and events can't be edited: add a new one and delete the old one instead. That's also how you get a new secret.

Events

Event

In settings

Sent when

post.created

New post

Someone posts feedback, on the portal, in the widget or through the API.

post.status_changed

Status changed

A post's status changes. The data has the post and its previousStatus.

comment.created

New comment

A comment is added to a post.

changelog.published

Changelog published

A changelog entry is published.

code_change.ready

Autopilot found updates

A merged pull request has updates to posts or articles waiting for your review. The data says how many wait and links to them in Autopilot.

conversation.created

New conversation

A new conversation starts in your inbox. The data includes its first message.

conversation.message_created

New message from a user

A user writes again in a conversation. Your team's replies don't send it.

Send test, next to a webhook, sends it a ping event.

What Tuti sends

Each request is a POST with a JSON body:

{
  "id": "evt_…",
  "type": "post.created",
  "createdAt": "2026-11-05T09:30:00.000Z",
  "project": { "slug": "fieldnote", "name": "Fieldnote" },
  "data": { "number": 12, "title": "Export notes to PDF", "status": "open" }
}

data is shaped as the API returns the same thing; the API reference has the details. Each request also has these headers:

  • Tuti-Event-Type: the event, such as post.created.

  • Tuti-Event-Id: the same as id, and the same on every retry, so you can skip an event you've already handled.

  • Tuti-Signature: t=<time>,v1=<signature>, where the signature is an HMAC-SHA256, with your secret, of the time, a dot and the raw body.

Check it's from Tuti

Check the signature before you trust a request. In Node:

import { createHmac, timingSafeEqual } from "node:crypto";

// body: the raw request body as a string, before parsing it.
function verify(body, header, secret) {
  const { t, v1 } = Object.fromEntries((header ?? "").split(",").map((part) => part.split("=")));
  if (!/^\d+$/.test(t ?? "") || !/^[0-9a-f]{64}$/.test(v1 ?? "")) return false;
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // older than 5 minutes
  const expected = createHmac("sha256", secret).update(`${t}.${body}`).digest("hex");
  return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}

Deliveries and retries

  • Answer with any 2xx within 10 seconds.

  • Anything else, or no answer, is retried up to 8 times with growing gaps: after a few seconds at first, then up to 8 hours apart, about 21 hours in all.

  • An event can arrive more than once, so use its id to skip repeats.

  • Redirects aren't followed, and the address must point to a public server.

Each webhook in the list shows its events and how its latest delivery went: "last delivered 14 minutes ago", or "last delivery failed 2 minutes ago:" with the reason, such as "Answered 503".

Delete a webhook

Choose Delete next to it, then Delete webhook. It stops getting events right away, including retries still waiting.

To read data rather than wait for events, see Use the API and MCP.